第 10 章 排序

58
第 10 第 第第 7.1 概概 7.2 概概概概 7.3 概概概概 7.4 概概概概 7.5 概概概概 7.6 概概概概 7.7 概概概概概概概概概概 第第第第第第第 第第第第第 ( 第第第 ) 第第第第第 , 第第第第第第第第第第第第第第第 .

Upload: shay

Post on 27-Jan-2016

69 views

Category:

Documents


1 download

DESCRIPTION

第 10 章 排序. 排序 的功能是将一个数据元素 ( 或记录 ) 的任意序列 , 重新排列成一个按关键字有序的序列. 7.1 概述 7.2 插入排序 7.3 交换排序 7.4 选择排序 7.5 归并排序 7.6 基数排序 7.7 各种内部排序方法比较. Return. 7.1 概述. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第  10  章 排序

第 10 章 排序

7.1 概述7.2 插入排序7.3 交换排序7.4 选择排序7.5 归并排序7.6 基数排序7.7 各种内部排序方法比较

排序的功能是将一个数据元素 (或记录 )的任意序列 ,重新排列成一个按关键字有序的序列 .

Page 2: 第  10  章 排序

7.1 概述

排序:有 n个记录的序列 {R1 , R2 ,…,Rn} ,其相应关键字的序列是 {K1 , K2 , …, Kn } ,相应的下标序列为 1 , 2 ,…, n 。通过排序,要求找出当前下标序列 1 ,2 ,…, n 的一种排列 p1 , p2 , …, pn ,使得相应关键字满足如下的非递减(或非递增)关系,即: Kp1≤ Kp2≤…≤ Kpn ,这样就得到一个按关键字有序的记录序列: {Rp1 , Rp2 , …, Rpn} 。

Return

Page 3: 第  10  章 排序

7.1 概述

内部排序与外部排序:根据排序时数据所占用存储器的不同,可将排序分为两类。一类是整个排序过程完全在内存中进行,称为内部排序;另一类是由于待排序记录数据量太大,内存无法容纳全部数据,排序需要借助外部存储设备才能完成,成为外部排序。

Page 4: 第  10  章 排序

稳定排序与不稳定排序:

假设 Ki=Kj(1≤i≤n , 1≤j≤n, i≠j) ,若在排序前的序列中 Ri 领先于 Rj( 即 i<j) ,经过排序后得到的序列中 Ri仍领先于 Rj ,则称所用的排序方法是稳定的;反之,当相同关键字的领先关系在排序过程中发生变化者,则称所用的排序方法是不稳定的。12,45,123,67,45,89

Page 5: 第  10  章 排序

在排序过程中,一般进行两种基本操作:( 1)比较两个关键字的大小;( 2)将记录从一个位置移动到另一个位置。

本章主要讨论在向量结构上各种排序方法的实现。为了讨论方便,假设待排记录的关键字均为整数,均从数组中下标为 1的位置开始存储,下标为 0的位置存储监视哨,或空闲不用。

Page 6: 第  10  章 排序

#define MAXSIZE 20typedef int KeyType;typedef struct { KeyType key; // 关键字项 OtherType other_data;// 其他数据项 } RedType; // 记录类型typedef struct { RedType r[MAXSIZE+1];//r[0] 闲置或作哨兵 int length; // 顺序表长度 }Sqlist; // 顺序表类型

Page 7: 第  10  章 排序

7.2 插入排序

7.2.1 直接插入排序7.2.2 折半插入排序7.2.3 希尔排序

基本方法基本方法 :: 将待排序文件中的记录, 将待排序文件中的记录, 逐个地按其关键字值的大小插入到目前逐个地按其关键字值的大小插入到目前已经排好序的若干个记录组成的文件中已经排好序的若干个记录组成的文件中的适当位置,并保持新文件有序。的适当位置,并保持新文件有序。

Return

Page 8: 第  10  章 排序

7.2.1 直接插入排序

直接插入排序算法的直接插入排序算法的思路思路是是 :: 初始可认为文初始可认为文件中的第件中的第 11 个记录己排好序,然后将第个记录己排好序,然后将第 22 个个到第到第 nn 个记录依次插入已排序的记录组成的个记录依次插入已排序的记录组成的文件中。在对第文件中。在对第 ii 个记录个记录 RiRi 进行插入时,进行插入时,R1R1 ,, R2R2 ,…,,…, Ri-1Ri-1 已排序,将记录已排序,将记录 RiRi 的的关键字关键字 keyikeyi 与已经排好序的关键字从右向与已经排好序的关键字从右向左依次比较,找到左依次比较,找到 RiRi 应插入的位置,将该应插入的位置,将该位置以后直到位置以后直到 Ri-1Ri-1 各记录顺序后移,空出各记录顺序后移,空出该位置让该位置让 RiRi 插入。 插入。

Page 9: 第  10  章 排序

图 7.1 直接插入排序的例子。

1)     { 48 }    62       35      77        55        14        35        98    2)    { 48      62 }     35       77        55       14        35        98  3)     { 35      48       62 }     77       55       14        35        98   4)     { 35      48       62       77 }      55       14        35        98    5)     { 35      48       55        62       77 }     14        35        98   6)     { 14      35     48        55       62        77 }     35        98    7)     { 14      35       35        48       55        62       77 }     98     8)     { 14      35       35        48       55        62        77      98  }  

Page 10: 第  10  章 排序

假设待排序记录存放在 r[1..n] 之中,为了提高效率,我们附设一个监视哨 r[0] ,使得 r[0] 始终存放待插入的记录。监视哨的作用有两个:一是备份待插入的记录,以便前面关键字较大的记录后移;二是防止越界 .

Page 11: 第  10  章 排序

void   InsertSort(Sqlist &L){// 对顺序表 L做直接插入排序 for (  i=2 ;  i<=L. length ; ++ i ) {if(L.r[i].key<L.r[i-1].key)           L.r[0]=L.r[i];//  将待插入记录复制为哨兵 L.r[i]=L.r[i-1]; for( j=i-2;L.r[0].key< r[ j ].key;--j ) L.r[ j+1]=L.r[ j ];     // 记录后移  L.r[ j+1]=L.r[0];      // 插入到正确位置 } }

算法描述

Page 12: 第  10  章 排序

算法分析

该算法的要点是:①使用监视哨 r[0] 临时保存待插入的记录。②从后往前查找应插入的位置。③查找与移动用同一循环完成。直接插入排序算法分析:首先从空间角度来看,它只需要一个辅助空间 r[0],空间复杂度为 S(n)=O(1)

Page 13: 第  10  章 排序

算法分析

从时间耗费角度来看,主要时间耗费在关键字比较和移动元素上。直接插入排序的时间复杂度 :最好情况 :初始有序 ,为 O(n);最坏情况 :初始逆序 ,为 O(n2);平均时间复杂度 T(n)= O(n2) 直接插入排序方法是稳定的排序方法。 直接插入排序算法简便,比较适用于待排序记录数目较少且基本有序的情况。当待排记录数目较大时,直接插入排序的性能就不好

Page 14: 第  10  章 排序

7.2.2 折半插入排序

在直接插入排序中,我们采用顺序查找法来确定记录的插入位置。由于( R1, R2,…, Ri-1)是有序子文件,我们可以采用折半查找法来确定 Ri的插入位置,这种排序称为折半插入排序 (二分插入排序 )。

Page 15: 第  10  章 排序

排序思想排序思想

根据插入排序的基本思想,在找第根据插入排序的基本思想,在找第 ii 个记录个记录的插入位置时,前的插入位置时,前 i-li-l 个记录已排序,将第个记录已排序,将第ii 个记录的排序码个记录的排序码 key[i]key[i] 和已排序的前和已排序的前 i-1i-1个的中间位置记录的排序码进行比较,如个的中间位置记录的排序码进行比较,如果果 key[i]key[i] 小于中间位置记录排序码,则可小于中间位置记录排序码,则可以在前半部继续使用二分法查找,否则在以在前半部继续使用二分法查找,否则在后半部继续使用二分法查找,直到查找范后半部继续使用二分法查找,直到查找范围为空,即可确定围为空,即可确定 key[i]key[i] 的插入位置。 的插入位置。

Page 16: 第  10  章 排序

图 7.2 折半插入排序的例子。1)    { 48 }    62       35      77        55        14        35     98    2)    { 48      62 }     35       77        55       14        35     98  3)      { 35      48       62 }     77       55       14        35     98   4)   { 35      48       62       77 }      55       14        35     98  

  5)     { 35      48       55        62       77 }     14        35     98   6)      { 14      35     48        55       62        77 }     35     98    7)      { 14      35       35        48       55        62       77 }   98     8)     { 14      35       35        48       55        62        77    98  }  

55

low high m low mhigh

Page 17: 第  10  章 排序

算法实现 :

void    BInsertSort (Sqlist &L){// 对顺序表 L 作折半插入排序for (  i=2  ; i<=L.length ; ++i ) { L.r[0]=L.r[i];low=1;  high=i-1;  while (low<=high )                  // 确定插入位置 {m=(low+high) / 2;    if (  L.r[0].key<L. r[m].key  )    high=m-1; else   if (  L.r[0].key>L. r[m].key  ) low=m+1; else {high=m;break;}} //while for (  j=i-1  ; j>= high+1; --j )   L.r[j+1]=L. r[j];   //   记录后移 L.r[high+1]=L.r[0];                   // 插入记录 } //for}

Page 18: 第  10  章 排序

算法分析

采用折半插入排序法,可减少关键字的比较次数。每插入一个元素,需要比较的次数最大为折半判定树的深度,因此插入 n-1 个元素的平均关键字的比较次数为 O(nlog2n) 。虽然折半插入排序法与直接插入排序法相比较,改善了算法中比较次数的数量级,但其并未改变移动元素的时间耗费,所以折半插入排序的总的时间复杂度仍然是 O(n2) 。

Page 19: 第  10  章 排序

7.2.3 希尔排序

希尔排序( Shell’s Method )又称“缩小增量排序”( Diminishing Increment Sort ),是由 D.L.Shell 在 1959 年提出来的。希尔排序的基本思想是:先将待排序记录序列分割成若干个“较稀疏的”子序列,分别进行直接插入排序。经过上述粗略调整,整个序列中的记录已经基本有序,最后再对全部记录进行一次直接插入排序。

Page 20: 第  10  章 排序

具体实现 :首先选定两个记录间的距离 d1 ,在整个待排序记录序列中将所有间隔为 d1 的记录分成一组,进行组内直接插入排序,然后再取两个记录间的距离 d2<d1 ,在整个待排序记录序列中,将所有间隔为 d2 的记录分成一组,进行组内直接插入排序直至选定两个记录间的距离 dt=1 为止,此时只有一个子序列,即整个待排序记录序列。

Page 21: 第  10  章 排序

图 7.3 希尔排序过程的实例

Page 22: 第  10  章 排序

void  ShellInsert(Sqlist L,int dk) {

//对顺序表 L做一趟希尔插入排序   for(i=dk+1 ; i<=L. length; ++ i)        

  if(L.r[i].key < L.r[i-dk].key)

  {L.r[0]=L. r[i];   //暂存 L.的 r[i]

 for(j=i-dk;j>0 &&L.r[0].key <L. r[j].key;j-=dk)

  L.r[j+dk]=L. r[j];  L.r[j+dk]= L.r[0]; }

}/*ShellInsert*/

算法描述

Page 23: 第  10  章 排序

void  ShellSort(Sqlist &L,int dlta[],int t)

/* 按增量序列 dlta[0..t-1] 对顺序表 L做希尔排序 */

{

  for(k=0 ; k<t; ++k)

         ShellInsert(L, dlta[k]);}

算法描述续

Page 24: 第  10  章 排序

算法分析

希尔排序的分析是一个复杂的问题,因为它的时间耗费是所取的“增量”序列的函数。到目前为止,尚未有人求得一种最好的增量序列。但大量研究也得出了一些局部的结论。在排序过程中,相同关键字记录的领先关系发生变化,则说明该排序方法是不稳定的。

Page 25: 第  10  章 排序

7.3 交换排序

7.3.1 冒泡排序7.3.2 快速排序

Return

Page 26: 第  10  章 排序

7.3.1 冒泡排序

冒泡排序是一种简单的交换类排序方法,它是通过相邻的数据元素的交换,逐步将待排序序列变成有序序列的过程。冒泡排序的基本思想是:从头扫描待排序记录序列,在扫描的过程中顺次比较相邻的两个元素的大小。以升序为例:在第一趟排序中,对 n 个记录进行如下操作:若相邻的两个记录的关键字比较,逆序时就交换位置。在扫描的过程中,不断的将相邻两个记录中关键字大的记录向后移动,最后将待排序记录序列中的最大关键字记录换到了待排序记录序列的末尾,这也是最大关键字记录应在的位置。然后进行第二趟冒泡排序,对前 n-1个记录进行同样的操作,其结果是使次大的记录被放在第 n-1个记录的位置上。如此反复,直到排好序为止(若在某一趟冒泡过程中,没有发现一个逆序,则可结束冒泡排序),所以 冒泡过程最多进行 n-1趟。 

Page 27: 第  10  章 排序

图 7.3 冒泡排序示例

48

35

62

55

14

35

77

22

40

98

48

35

62

55

62

14

62

35

77

22

77

40

1 趟 2 趟 3 趟 4 趟 5 趟 6 趟

Page 28: 第  10  章 排序

算法描述

void  Bubble_Sort(Sqlist &L )//对顺序表 L做冒泡排序 {change=TRUE;for ( i=1 ; i<= n-1 && change ;++i ) {  change=FALSE; for ( j=1 ; j<= n-i; ++j)   if (L.r[j].key>L.r[j+1].key )  {  L.r[0]=L. r[j];      L. r[j]=L. r[j+1];       L.r[j+1]= L.r[0]; change=TRUE;    } }

Page 29: 第  10  章 排序

算法分析

最坏情况下,待排序记录按关键字的逆序进行排列,此时,每一趟冒泡排序需进行 i次比较,3i次移动。经过 n-1趟冒泡排序后,总的比较次数为

总的移动次数为 3n(n-1)/2 次,因此该算法的时间复杂度为 O(n2) ,空间复杂度为 O(1) 。另外,冒泡排序法是一种稳定的排序方法。

Page 30: 第  10  章 排序

7.3.2 快速排序

快速排序的基本思想是:从待排序记录序列中选取一个记录(通常选取第一个记录),其关键字设为K1,然后将其余关键字小于 K1的记录移到前面,而将关键字大于 K1的记录移到后面,结果将待排序记录序列分成两个子表,最后将关键字为 K1的记录插到其分界线的位置处。我们将这个过程称作一趟快速排序。通过一次划分后,就以关键字为 K1的记录为分界线,将待排序序列分成了两个子表,且前面子表中所有记录的关键字均不大于 K1,而后面子表中的所有记录的关键字均不小于 K1。对分割后的子表继续按上述原则进行分割,直到所有子表的表长不超过 1为止,此时待排序记录序列就变成了一个有序表。

Page 31: 第  10  章 排序

3849 65 97 76 13 27 49初始关键字

49pivotkey

i j

27 6513 9749

27 38 13 49 76 97 65 49{ {} }1 趟

i jij

27

13 3827

76

49 9765 76

2 趟 4913 27 38 49 65 76 97{ } } }}{ { {

4913 27 38 49 65 76 97}{3 趟

有序序列 4913 27 38 49 65 76 97

图 7.4 快速排序图示

Page 32: 第  10  章 排序

快速排序算法描述void QSort(Sqlist &L,int low, int high )/*对顺序表L用快速排序算法进行排序 */{ if(1ow<high) {pivotloc=Partition(L, low, high); QSort(L, low, pivotloc-1);

QSort(L, pivotloc+1, high); } }void QuickSort(Sqlist &L){Qsort(L,1,L.length);}

Page 33: 第  10  章 排序

一趟快速排序算法描述 :

int   Partition(Sqlist L,int low , int hith)/* 对顺序表 L 中的子表 L.r[low..high] 部分进行一趟排序,

并得到基准的位置,使得排序后的结果满足其之后(前)的记录的关键字均不小于(大于)于基准记录 */

{L.r[0]=L.r[low] ; pivotkey=L.r[low].key;while ( low<high ){while (low< high &&L.r[high].key>=pivotkey )  high-- ;  L.r[row]=L.r[high];while (low<high &&L.r[low].key<pivotkey  )    low++;   L.r[high]=L.r[low];}     L.r[low]=L.r[0] ; /* 将基准记录保存到 low=high 的

位置 */return low;                     /*返回基准记录的位置 */}

Page 34: 第  10  章 排序

算法分析

分析快速排序的时间耗费 , 共需进行多少趟,取决于递归调用深度。

快速排序所需时间的平均值为 Targ(n) ≤ Knln(n) ,这是目前内部排序方法中所能达到的最好平均时间复杂度。

但是若初始记录序列按关键字有序或基本有序时,快速排序将蜕变为冒泡排序,其时间复杂度为 O(n2) 。为改进之,可采用其他方法选取枢轴元素,以弥补缺陷。

Page 35: 第  10  章 排序

7.4 选择排序

选择排序的基本思想是:每一趟在n-i+1 ( i=1 , 2,… n-1 )个记录中选取关键字最小的记录作为有序序列中第 i个记录。我们主要介绍简单选择排序、树型选择排序和堆排序。

Return

Page 36: 第  10  章 排序

7.4.1 简单选择排序

简单选择排序的基本思想:第 i趟简单选择排序是指通过 n-i 次关键字的比较,从 n-i+1 个记录中选出关键字最小的记录,并和第 i个记录进行交换。共需进行 i-1趟比较,直到所有记录排序完成为止。例如:进行第 i趟选择时,从当前候选记录中选出关键字最小的 k号记录,并和第 i个记录进行交换。

Page 37: 第  10  章 排序

图 7.5 简单选择排序图示

49 6538 4976 13 2797

13 6538 4976 49 2797

13 6527 4976 49 3897

13 3827 4976 49 6597

13 3827 4976 97 6549

13 3827 7649 97 6549

13 3827 7649 65 9749

13 3827 9749 65 7649

初始关键字1 趟

3 趟

2 趟

4 趟

5 趟

6 趟

7 趟

Page 38: 第  10  章 排序

算法描述void  SelectSort(Sqlist &L)// 对顺序表 L 做简单选择排序 { for ( i=1 ; i<L.length; ++i)  {k=i ; for ( j=i+1 ; j<= L.length ; ++j)    if (L.r[ j ].key <L.r[ k ].key )  k=j ; if ( k!=i)   { L.r[0]= L.r[i] ; L.r[ i ]=L.r[ k ] ; L.r[k]=L.r[0]; } }  } /* SelectSort  */

Page 39: 第  10  章 排序

算法分析

简单选择排序算法分析:在简单选择排序过程中,所需移动记录的次数比较少。最好情况下,即待排序记录初始状态就已经是正序排列了,则不需要移动记录。最坏情况下,即待排序记录初始状态是按逆序排列的,则需要移动记录的次数最多为 3( n-1)。简单选择排序过程中需要进行的比较次数与初始状态下待排序的记录序列的排列情况无关。当 i=1时,需进行 n-1次比较;当 i=2时,需进行 n-2次比较;依次类推,共需要进行的比较次数是

  =(n-1)+(n-2)+…+2+1=n(n-1)/2,

即进行比较操作的时间复杂度为 O(n2)。 

Page 40: 第  10  章 排序

7.4.2 树型选择排序

树型选择排序也称作锦标赛排序。它的基本思想是:先把待排序的 n个记录的关键字两两进行比较,取出较小者。然后再在 n/2 个较小者中,采用同样的方法进行比较选出每两个中的较小者,如此反复,直至选出最小关键字记录为止。我们可以用一棵有 n个结点的树来表示,选出的最小关键字记录就是这棵树的根结点。在输出最小关键字之后,为选出次小关键字,将根结点即最小关键字记录所对应的叶子结点的关键字的值置为∞,再进行上述的过程,直到所有的记录全部输出为止。

Page 41: 第  10  章 排序

图 7.5 树型选择排序示例

(a) 选出最小关键字 13

Page 42: 第  10  章 排序

图 7.5 树型选择排序示例

(b) 选出次小关键字 27

Page 43: 第  10  章 排序

算法分析

在树型选择排序中,被选中的关键字都是走了一条由叶子结点到根结点的比较的过程 ,由于含有 n个叶子节点的完全二叉数的深度 log2n +1 ,则在树型选择排序中,每选择一个小关键字需要进行 log2n 次比较,因此其时间复杂度为 O(nlog2n) 。移动记录次数不超过比较次数,故总的算法时间复杂度为 O(nlog2n) 。与简单选择排序相比较降低了比较次数的数量级,增加了 n-1 个额外的存储空间存放中间比较结果,同时附加了与∞进行比较的时间耗费。为了弥补,威洛姆斯在 1964年提出了进一步的改进方法,即另外一种形式的选择排序方法——堆排序。

Page 44: 第  10  章 排序

7.4.3 堆排序

堆排序是对树型选择排序的进一步改进。采用堆排序时,只需要一个记录大小的辅助空间。堆排序是在排序过程中,将向量中存储的数据看成一棵完全二叉树,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择关键字最小的记录,即待排序记录仍采用向量数组方式存储,并非采用树的存储结构,而仅仅是采用完全二叉树的顺序结构的特征进行分析而已。

Page 45: 第  10  章 排序

具体做法是:把待排序的记录的关键字存放在数组 r[1..n] 之中,将 r 看成是一棵完全二叉树的顺序表示,每个结点表示一个记录,第一个记录 r[1]作为二叉树的根,以下各记录 r[2...n]依次逐层从左到右顺序排列,任意结点r[i] 的左孩子是 r[2i],右孩子是r[2i+1],双亲是r[ i/2 ] 。对这棵完全二叉树进行调整,使各结点的关键字值满足下列条件:r[i].key≥r[2i].key并且 r[i].key≥r[2i+1].key(i=1,2, ... n/2 ) ,满足这个条件的完全二叉树为堆。这个堆中根结点的关键字最大,称为大根堆。反之,如果这棵完全二叉树中任意结点的关键字大于或者等于其左孩子和右孩子的关键字(当有左孩子或右孩子时),对应的堆为小根堆。

Page 46: 第  10  章 排序

图 7.6 堆示例

(a)大根椎 (b)小根椎

Page 47: 第  10  章 排序

图 7.7 堆排序过程

49 6538 4976 13 2797

49

38 65

97 76 13 27

49

49

97

13

65

13

49

13

97

27

49

97

27

97

49

27

97

97

38

97 38

65

65

4976

49

49

49

97

97

49

65

65

97

7697

76

76

97

关键字初始序列

Page 48: 第  10  章 排序

算法分析

堆排序方法对记录数较少的文件并不值得提倡 ,但对 n较大的文件还是很有效的 .因为其运行时间主要耗费在建初始堆和调整新堆时进行的反复“筛选”上。堆排序在最坏情况下,其时间复杂度也为O( nlogn),相对于快速排序来说,这是堆排序的最大优点。此外,堆排序仅需一个记录大小供交换有的辅助存储空间。

Page 49: 第  10  章 排序

7.5 归并排序

前面介绍的三类排序方法:插入排序、交换排序和选择排序,都是将一组记录按关键字大小排成一个有序的序列。而本节介绍的归并排序法,它的基本思想是将两个或两个以上有序表合并成一个新的有序表。假设初始序列含有 n个记录,首先将这 n个记录看成 n个有序的子序列,每个子序列的长度为 1,然后两两归并,得到 个长度为 2( n为奇数时,最后一个序列的长度为 1)的有序子序列;在此基础上,再进行两两归并,如此重复,直至得到一个长度为 n的有序序列为止。这种方法被称作 2-路归并排序。 

Return

Page 50: 第  10  章 排序

归并的思想主要用于外部排序。外部排序可分两步,①待排序记录分批读入内存,用某种方法在内存排序,组成有序的子文件,再按某种策略存入外存。②子文件多路归并,成为较长有序子文件,再记入外存,如此反复,直到整个待排序文件有序。

外部排序可使用外存、磁带、磁盘,最初形成有序子文件长取决于内存所能提供排序区大小和最初排序策略,归并路数取决于所能提供排序的外部设备数。

Page 51: 第  10  章 排序

7.6 基数排序 前介绍的排序方法都是根据关键字值的大小来进行排序的。本节介绍的方法是按组成关键字的各个位的值来实现的排序的,这种方法称为基数排序( radix sort)。采用基数排序法需要使用一批桶(或箱子),故这种方法又称为桶排序列。下面以十进制数为例来说明基数排充的过程。假定待排序文件中所有记录的关键字为不超过 d位的非负整数,从最高位到最低位(个位)的编号依次为 1, 2,…, d。设置 10个队列(即上面所说的桶),它们的编号分别为 0, 1, 2,…, 9。当第一遍扫描文字时,将记录按关键字的个位(即第 d位)数分别放到相应的队列中:个位数为 0的关键字,其记录依次放入 0号队列中 i个位数为 1的关键字,其记录放入 1号队列中…;个位数为9的关键字,其记录放入 9号队列中。这一过程叫做按个位数分配。现在把这 10个队列中的记录,按 0号, 1号, 9号队列的顺序收集和排列起来,同一队列中的记录按先进先出的次序排列。这是第 1遍。第 2遍排序使用同样的办法,将第 1遍排序后的记录按其关键字的十位数(第 d—1位)分配到相应的队列中,再把队列中的记录收集和排列起来。继续进行下去。第 d遍排序时,按第 d—1遍排序后记录的关键字的最高位(第 1位)进行分配,再收集和排列各队列中的记录,医得到了原文件的有序文件,这就是以 10为基的关键字的基数排序法。 

Return

Page 52: 第  10  章 排序

关键字

1 022 773 704 545 646 217 558 119 3810 21

初始状态

7021,11,2102

54,6455

7738

70211121025464557738

021121,2138

54,556470,77

02112121385455647077

10个队列 关键字

第一遍,按个位数分配 收集后 收集后

第二遍,按十位数分配

Page 53: 第  10  章 排序

例如,给出关键字序列( 02, 77, 70, 54, 64, 21, 55, 11, 38,21),其中关键字 02用 1个 0在 2的前面补足到 2位,余关键字均为 2位的正整数。进行基数排序的过程如图 9-9所示。

在这个例子中,文件和所有的队列都表示成向量(一维数组)。显然,关键字的某一位有可能均为同一个数字(例如,个数都为 0),这时所有的记录都同时装入同一个队列中(例如,同时装入 0号队列中)。因此,如果每个队列的大小和文件大小相同,则需要一个 10倍于文件大小的附加空间。此外,排序时需要进行反复的分配和收集记录。所以,采用顺序表示是不方便的。

基数排序所需的计算时间不仅与文件的大小 n有关,而且还与关键字的位数、关键字的基有关。设关键字的基为 r(十进制数的基为 10,二进制数的基为 2),为建立 r个空队列所需的时间为O( r)。把 n个记录分放到各个队列中并重新收集起来所需的时间为O( n),因此一遍排序所需的时间为O( n+r)。若每个关键字有 d位,则总共要进行 d遍排,所以基数排序的时间复杂度为O( d( n+r))。由于关键字的位数 d直接与基数 r以及最大关键字的值有关,因此不同的 r和关键字将需要不同的时间。 

Page 54: 第  10  章 排序

7.7 各种内部排序方法比较

在已介绍的上述各种内部排序方法中,就所需要的计算时间来看,快速排序、归并排序、堆排序是很好的方法。但是,归并排序需要大小为 n的辅助空间,快速排序需要一个栈。除了快速排序、堆排序、选择排序不稳定外,基它排序方法都是稳定的。评价一个排序算法性能好坏的主要标准是它所需的计算时间和存储空间。影响计算时间的两个景要因素是比较关键字的次数和记录的移动次数。在实际应用中,究竟应该选用何种排序方法,取决于具体的应用和机器条件。

Return

Page 55: 第  10  章 排序

迄今为止,已有的排序方法远远不止本章讨论的这些方法,人们之所以热衷于研究多种排序方法,不仅是由于排序在计算机中所处的重要地位,而且还因为不同的方法各有其优缺点,可适用于不同的场合。选取排序方法时需要考虑的因素有:

待排序的记录数目 n;记录本身信息量的大小;关键字的结构及分布情况;对排序稳定性的要求;语言工具的条件,辅助空间的大小等。依据这些因素,可得出如下几点结论:

( 1)若 n较小(譬如 n50),可采用直接插入排序或直接选。由于直接插入排序所需记录移动操作较直接选择排序多,因此若记录本身信息量较大时,则选用直接选择排序为宜。

( 2)若文件的初始状态已是按关键字基本有序,则选用直接插入排序泡排序为宜。

Page 56: 第  10  章 排序

( 3)若 N较大,则应采用时间复杂度为的排序方法:快速排序 \堆排序或归并排序,快速排序是目前基于内部排序的中被认为是最好的方法,档待排序的关键字是随机人布时,快速排序的平均时间最少,但堆排序所需的辅助窨少于快速排序,并且不会出现序可能出现的最坏情况,这两种排序方法都是不稳定的,若要求排序稳定则可选用归并排序。但本文章结合介绍的单个记录起进行两两归并排算法并不值得提倡,通常可以将它和直接排序结合在一起用。先利用直接插入排序求得的子文件,然后,再两 两 归并之。因为直接插入排序是稳定的,所以,改进后的归并排序是稳定的。

Page 57: 第  10  章 排序

( 4)在基于比较的排序方法中,每次比较两个关键字的大小之后,仅仅出现两种可能的转移,因此,可以利用一棵二叉树来描述比较判定过程,由此可以证明 ;当文件的 N个关键字分布时,任何借助于比较的排序算法,至少要的时间,由于箱排序和基数排序只需一步就会引起 M种可能的转移,即把一个记录半装入M个箱子之一,因此,在一般情况下,箱乔序和排序可能在时间内完成对 N个记录的。但踞的是,箱排序和排序只适用于象字符串和整数这类有明显的结构特征的关键字,当关键字的取值范围属于某个无穷集合时,无法使用箱排序和排序,这时只有借助于比较方法来排序。由此可知,若 N较大,记录的关键字倍数较少时且可以分解时采用排序较好。

Page 58: 第  10  章 排序

( 5)前面讨论的排序算法,除排序外,都是在一维数组上实现的,当记录本身信息量较大时,为了避免浪费大量时间移动记录,可以用链表作为存储结构,如插入排序和归并排序都易于在链表上实现,并分别称之为表和归并表,但有的方法,如快速排序和堆排序,在链表上难于实现,在这种情况下,可以提取关键字建立索引表,然后,对索引表进行排序。然而更为简单的方法是 ;引入一个整形向量作为辅助表,排序前,若排序算法中要求交换,则只需交换 R[I]和 R[j]即可,排序结束后,向量就指示了记录之间的顺序关系:

无论是用链表还是用辅助空间来实现排序,都有可能要求最终结果是 :

若上述要求,则可以在排序结束后,再按链表或辅助窨表所规定的次序重排各记录,完成这种 重排时间是 o(n)。