数据排序(一)
冒泡排序的改进:
为了标志在比较中是否进行了数据交换,设一个布尔量flag,在每趟比较前,将flag置为true,如果在比较中发生了数据交换,则将flag置为false,在一趟比较结束后,判断flag,如果它仍为true(表明该趟排序未发生数据交换)则排序结束,否则进行下一趟比较。
1 const N=10;
2 Var a: array[1..N] of integer;
3 i,j,t: integer;flag:boolean;
4 Begin
5 for i:=1 to N do Readln(a[ i ]);
6 for j:=1 to N-1 do begin
7 flag:=true;
8 for i:=1 to N-j do
9 if a[ i ] < a[i+1] then begin
10 t:=a[i];a[i]:=a[i+1];a[i+1]:=t;
11 flag:=false;
12 end;
13 if flag then break;
14 end;
15 for i:=1 to N do write(a[ i ]:6);
16 end.
选择排序的改进:
由于每次交换两个数据元素要执行3个语句,过多的交换必定要花费许多时间。改进方案是在内循环的比较中找出最大元素的下标,在内循环结束时,才考虑是否交换。
1 Const n=10;
2 var a:array[1..n]of integer;
3 i,j,t,k:integer;
4 Begin
5 for i:=1 to n do readln(a[i]);
6 for i:=1 to n-1 do
7 begin
8 k:=i;
9 for j:=i+1 to n do
10 if a[k]<a[j] then k:=j;
11 if k<>i then begin
12 t:=a[k];a[k]:=a[i];a[i]:=t;
13 end;
14 end;
15 for i:=1 to n do write(a[i]:5);
16 end.
竞赛中常用的几种排序算法:
- 冒泡排序
- 选择排序
- 桶排序
- 插入排序
- 快速排序
- 归并排序
- 堆排序
- 二叉排序树
桶排序
- 桶排序的思想是若待排序的数据在一个明显的或可以预估的有限范围内,可设计有限个有序桶,每个桶装入一个值(当然也可以装入多个值),顺序输出各桶的值,将得到有序的序列。
- 算法优点:时间效率高,时间复杂度仅为O(n),当然这个n不是待排序数据的数量,而是待排序数据的范围跨度;
- 算法缺点:显然,当数据比较少,但范围跨度又比较大时,这个算法造成空间和时间上的巨大浪费。
1 输入n个0~100之间的整数,由小到大排序输出。 2 主要代码: 3 for i:=0 to 100 do b[i]:=0; 4 for i:=1 to n do 5 begin 6 read(k); 7 inc(b[k]); 8 end; 9 for i:=0 to 100 do 10 while b[i]>0 do begin 11 write(i:6); 12 dec(b[i]); 13 end;
1 输入n个小写字母,按字典序输出。 2 主要代码: 3 for ch:='a' to 'z' do b[ch]:=0; 4 for i:=1 to n do 5 begin 6 read(c); 7 inc(b[c]); 8 end; 9 for ch:='a' to 'z' do 10 while b[ch]>0 do begin 11 write(ch:6); 12 dec(b[ch]); 13 end;
- 算法优点:时间效率高,时间复杂度仅为O(n),当然这个n不是待排序数据的数量,而是待排序数据的范围跨度;
插入排序
算法思想:有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。
将n个元素的数列分为已有序和无序两个部分,如下所示:
{{a1},{a2,a3,a4,…,an}}
{{a1(1),a2(1)},{a3(1),a4(1) …,an(1)}}
…
{{a1(n-1),a2(n-1) ,…}, {an(n-1)}}
每次处理就是将无序数列的第一个元素与有序数列的元素从后往前逐个进行比较,找出插入位置,将该元素插入到有序数列的合适位置中。
1 //对r[1..n]按递增序进行插入排序,r[0]是监视哨
2 procedure inssort(var r:arraytype);
3 begin
4 r[0]:=-maxint;
5 for i:=2 to n do //依次插入r[2],…,r[n]
6 begin
7 j:=i-1; x:=r[i];
8 while x<r[j] do //查找r[i]的插入位置
9 begin
10 r[j+1]:=r[j];
11 j:=j-1;
12 end;
13 r[j+1]:=x; //插入r[i]
14 end;
15 end;
插入排序效率分析:
- 如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。
- 插入排序的一个优势是,待排序数据可以不必预先全部产生,允许对动态产生的数据进行排序,类似的还有二叉排序树和平衡二叉树等,但后两者效率更高,排序算法更复杂,我们将在以后的学习中介绍。
- 插入排序还可以进行适当优化,采用二分法插入排序,在后面的学习中我们介绍过二分查找技术后,同学们可以自己改进插入排序的查找比较过程。
快速排序
基本思想: 快排的基本思想实质上是一种分治策略。在当前无序区R[1..H]中任取一个数据元素作为比较的"基准"(不妨记为 X),用此基准将当前无序区划分为左右两个较小的无序区:R[1..I-1]和 R[I+1..H],且左边的无序子区中数据元素均小于等于基准元素,右边的无序子区中数据元素均大于等于基准元素,而基准X则位于最终排序的位置上,即R[1..I-1]≤X.Key≤R[I+1..H](1≤I≤H),当 R[1..I-1]和R[I+1..H]均非空时,分别对它们进行上述的划分过程,直至所有无序子区中的数据元素均已排序为止。
为了防止快排在极端数据(例如数据原来就是有序的或者基本有序)情况下退化成冒泡排序,一般取无序区间中间位置的数作为比较基准,排序过程如下:
初 始 关键字 [38 65 97 49 76 13 27 48]
第一次交换后 [38 48 97 49 76 13 27 65]
第二次交换后 [38 48 27 49 76 13 97 65]
第三次交换后 [38 48 27 13 76 49 97 65] 交换后i=5,j=5,i=j
继续扫描,i=5,j=4,此时i>j,第一趟排序结束
【示例】:
初始关键字 [38 65 97 49 76 13 27 49]
一趟排序之后 [38 48 27 13] [76 49 97 65]
二趟排序之后 [38 13 27] [48] [49] [76 97 65]
三趟排序之后 [13] [38 27] [48] [49] [76 65] [97]
最后的结果 [13] [27] [38] [48] [49] [65] [76] [97]
1 procedure sort(l,r:longint);
2 var
3 tmp,i,j,mid:longint; //实际应用中,这里的变量可定义成全局变量以节省栈空间
4 begin
5 i:=l;
6 j:=r;
7 mid:=a[(i+j)div 2]; {将当前序列在中间位置的数定义为中间数基准}
8 repeat
9 while a[i]<mid do inc(i); {在左半部分寻找比中间数大的数}
10 while a[j]>mid do dec(j); {在右半部分寻找比中间数小的数}
11 if i<=j then {若找到一组与排序目标不一致的数对则交换它们}
12 begin
13 tmp:=a[i];a[i]:=a[j];a[j]:=tmp;
14 inc(i); {继续找}
15 dec(j);
16 end;
17 until i>j; {注意这里不能有等号}
18 if l<j then sort(l,j); {若序列有1个以上的无素,则递归搜索左右区间}
19 if i<r then sort(i,r);
20 end;
- 快速排序的时间复杂度是O(nlog2n),速度快,但它是不稳定的排序方法。因此,就平均时间而言,快速排序是目前被认为是最好的一种内部排序方法。若初始记录序列按关键字有序或基本有序时,快速排序将退化为冒泡排序,其时间复杂度为O(n2)。
- 如果我们选择标准关键字时采取一定的策略,就可以避免快速排序的退化,比如我们随机产生一个值x,以序列中的第x个位置的记录的关键字作为标准关键字,这样的随机化快速排序能很好地防止退化。一般都以序列中间位置的记录的关键字作为标准关键字,这样既可能简化代码,又能防止退化,是一种折中的常用方法。
- 从时间上看,快速排序的平均性能优于前面讨论过的冒泡排序,选择排序、插入排序列方法,但快速排序需要一个栈空间来实现递归。