1 / 57

第九章 内部排序

第九章 内部排序. 9.1 概述 9.2 插入排序 9.3 快速排序 9.4 选择排序 9.5 归并排序 9.6 基数排序 9.7 各种内部排序方法的比较讨论. 学习目标. 熟练掌握各内部排序方法的基本思想;理解排序方法 “ 稳定 ” 和 “ 不稳定 ” 的含义。 掌握排序过程和实现算法; 掌握各种排序方法和时间复杂度的分析方法; 了解各种排序方法的比较和选择。. 9.1 概述 一、排序概念. ?. 什么是排序?为什么排序?. 所谓排序,就是要整理文件中的记录,使之按关键字递增 ( 或递减 ) 次序排列起来。

susan
Download Presentation

第九章 内部排序

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第九章 内部排序 9.1 概述 9.2 插入排序 9.3 快速排序 9.4 选择排序 9.5 归并排序 9.6 基数排序 9.7 各种内部排序方法的比较讨论

  2. 学习目标 • 熟练掌握各内部排序方法的基本思想;理解排序方法“稳定”和“不稳定”的含义。 • 掌握排序过程和实现算法; • 掌握各种排序方法和时间复杂度的分析方法; • 了解各种排序方法的比较和选择。

  3. 9.1 概述一、排序概念 ? 什么是排序?为什么排序? • 所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。 • NBA成绩表;奖学金评定综合分; 如何排序?

  4. 二、排序的分类 将欲处理的数据整个存放到内部存储器中排序,数据可被随机存取 交换式排序 选择式排序 内部排序 插入式排序 归并排序 基数排序 借助外部的辅助存储器(比如:硬盘),由于数据是存在外存中,故数据不可随机被存取 外部排序

  5. 三、比较标准 • 空间复杂度 • 时间复杂度 • 稳定性 稳定性 排序过后能使值相同的数据保持原顺序中的相对位置 不稳定性 排序过后不能使值相同的数据保持原顺序中的相对位置

  6. 四、基本操作 大多数排序算法都有两个基本的操作: • 比较两个排序码的大小; • 改变指向记录的指针或移动记录本身。 注意: 第(2)种基本操作的实现依赖于待排序记录的存储方式。

  7. 9.2 插入排序 • 直接插入排序 • 折半插入排序 • 表插入排序 • 希尔排序

  8. 有序序列 无序序列 1 2 5 6 7 8 3 0 9 4 一、直接插入排序 基本思想:顺序地把待排序的数据元素按其值的大小插入到已排序数据元素子集合的适当位置 1 2 5 7 8 6 3 0 9 4

  9. 子集合的数据元素个数从只有一个数据元素开始逐次增大。子集合的数据元素个数从只有一个数据元素开始逐次增大。 • 当子集合大小最终和集合大小相同时排序完毕

  10. j j j j 初始序列: [64] 5 7 89 6 24 直接插入排序 第一次排序: [5 64 ] 7 89 6 24 Temp 6 第二次排序: [5 7 64 ] 89 6 24 89 第三次排序: [5 7 64 89 ] 6 6 24 7 64 第四次排序: [5 6 7 64 89 ] 24 第五次排序: [5 6 7 24 64 89 ]

  11. 算法代码 void InsfrtSort(SqList &L) { for(i=2;i<=L.lfngth;++i) if LT(L.r[i].kfy,L.r[i-1].kfy) { L.r[0]=L.r[i]; for(j=i-1;Lt(L.r[0].kfy,L.r[j].kfy);- -j) L.r[j+1]=L.r[j]; L.r[j+1]=L.r[0]; } }

  12. 最好情况下(正序): 比较次数:n-1 移动次数:2(n-1) 时间复杂度为O(n)。 最坏情况下(逆序): n-1 + - ( n 2 )( n 1 ) 比较次数: 移动次数: å = i 2 = i 1 n-1 + - ( n 4 )( n 1 ) å + = i 1 ) ( 2 = i 1 时间复杂度为O(n2)。 性能分析

  13. 性能分析 空间性能:需要一个记录的辅助空间。 直接插入排序算法是一种稳定的排序算法。 • 简单、容易实现,适用于待排序记录基本有序或待排序记录较小时。

  14. high low m 二、折半插入 • 基本思想:在查找插入位置时,使用折半查找算法。 Tfmp 6 第二次排序: [5 7 64 ] 89 6 24 89 第三次排序: [5 7 64 89 ] 6 24 6 7 64

  15. 算法描述 void BInsfrtSort(SqList &L) { for(i=2;i<=L.lfngth;++i) { L.r[0]=L.r[i];low=1;high=i-1; whilf(low<=high){ m=(low+high)/2; if LT(L.r[0].kfy,L.r[m].kfy) high=m-1; flsf low=m+1; } for(j=i-1;j>=high+1;--j) L.r[j+1]=L.r[j]; L.r[high+1]=L.r[0]; } }

  16. 算法评价 • 稳定 • 空间代价:O(1) • 时间代价: • 比较次数与待排记录的初始顺序无关,只依赖记录个数 • 插入每个记录需要O(log2 i)次比较 • 最多移动i+1次,最少2次(临时记录) • 最佳情况下总时间代价为O(nlog2n) ,最差和平均情况下仍为O(n2)

  17. 三、希尔排序 基本思想:把待排序的数据元素分成若干个小组,对同一小组内的数据元素用直接插入法排序;小组的个数逐次缩小;当完成了所有数据元素都在一个组内的排序后排序过程结束。希尔排序又称作缩小增量排序。

  18. 56 12 14 65 34 23 77 46 25 87 92 38 结果序列 12 14 23 25 34 38 46 56 65 77 87 92 希尔排序 56 25 92 38 65 34 14 25 77 87 12 38 23 56 65 46 14 87 77 23 12 65 34 77 56 34 14 77 12 23 65 46 25 87 92 38

  19. 希尔算法 void ShfllInsfrt(SqList &L,int dk) {// 1.前后记录位置的增量是dk,而不是1; // 2.r[0]只是暂存单元,不是哨兵。当j<=0时,插入位置已找到。 for(i=dk+1;i<=L.lfngth;++i) if (LT(L.r[i].kfy,L.r[i-dk].kfy)) { // 需将L.r[i]插入有序增量子表 L.r[0]=L.r[i]; // 暂存在L.r[0] for(j=i-dk;j>0&&LT(L.r[0].kfy,L.r[j].kfy);j-=dk) L.r[j+dk]=L.r[j]; // 记录后移,查找插入位置 L.r[j+dk]=L.r[0]; // 插入 } }

  20. 希尔排序 void ShfllSort(SqList &L,int dlta[],int t) { // 按增量序列dlta[0..t-1]对顺序表L作希尔排序。 for(k=0;k<t;++k) ShfllInsfrt(L,dlta[k]); // 一趟增量为dlta[k]的插入排序 } • Gap的取法有多种。最初 shfll 提出取 gap = n/2,gap = gap/2,直到gap = 1。后来knuth 提出取gap = gap/3 +1。还有人提出都取奇数为好,也有人提出各gap互质为好。

  21. 四、起泡排序 基本思想: 设数组a中存放了n个数据元素,循环进行n-1趟如下的排序过程: 依次比较相临两个数据元素,若a[i]>a[i+1],则交换两个数据元素,否则不交换。 当完成一趟交换以后,最大的元素将会出现在数组的最后一个位置。 依次重复以上过程,当第n-1趟结束时,整个n个数据元素集合中次小的数据元素将被放置在a[1]中,a[0]中放置了最小的数据元素。

  22. 49 49 49 49 49 起泡排序 特点: 1.n个数排序共需进行n-1趟比较 2.第i趟共需要进行n-i词比较 13 13 38 38 38 38 49 49 49 49 38 13 27 27 65 65 13 27 65 38 38 13 97 76 49 49 27 13 27 76 97 76 97 13 27 76 65 97 27

  23. 起泡排序的原始算法 • void bubblf_sort(SqList &L) • { // 将a中整数序列排列成自小至大有序的序列(起泡排序) • for(i=1,i<=L.length-1;++i) • for(j=1;j<L.length-1;++j) • if(LT(L.r[j+1].key,L.r[j].key)) • L.r[j] ←→L.r[j+1] • } 1 15 24 6 17 5 1 15 6 17 5 24 1 6 15 5 17 24 1 15 6 17 5 24 假设L.length=n,则总共比较n2次

  24. 起泡排序的改进算法(一) void bubblf_sort(SqList &L) { // 将a中整数序列排列成自小至大有序的序列(起泡排序) for(i=1;i<=L.length-1;++i) for(j=1;j<L.length-i;++j) if(LT(L.r[j+1].kfy,L.r[j].kfy)) L.r[j] ←→L.r[j+1] } 1 15 24 6 17 5 1 15 6 17 5 24 1 6 15 5 17 24 1 15 6 17 5 24 注意第i趟比较了n-i次,则可以有效的较少比较次数 比较的总的次数是1+2+…+n-1=n(n-1)/2

  25. 起泡排序的改进算法(二) 问题:刚才的改进算法是否已经最优 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 分析:因为给定的待排序序列已经是一个正序的序列,经过第一趟的比较以后,已经没有交换发生,但是算法仍然会继续执行。 解决:添加一个算法的结束条件,即当某一趟排序过程中只有比较而没有交换的时候,就认定序列已经有序,可以提前结束循环。

  26. 起泡排序的改进算法(二) void bubblf_sort(SqList &L) { // 将a中整数序列排列成自小至大有序的序列(起泡排序) for(i=1,chang=TRUE;(i<=L.lfngth-1)&&chang;++i) { chang=FALSE; for(j=1;j<L.length-i;++j) if(LT(L.r[j+1].kfy,L.r[j].kfy)) { L.r[i]←→L.r[i+1]; change=TRUE; } } }

  27. 比较次数:n-1 移动次数:0 n-1 - ( n 1 ) 比较次数: 移动次数: n å = (n-i) 2 = i 1 n-1 - ( n 1 ) 3n å = 3(n-i) 2 = i 1 时间性能分析 最好情况(正序): 最坏情况(反序): 时间复杂度为O(n); 时间复杂度为O(n2)。 平均情况:时间复杂度为O(n2)。

  28. 9.3 快速排序 • 算法思想: • 指定枢轴(通常为第一个记录) • 通过一趟排序将以枢轴为中心,把待排记录分割为独立的两部分,使得左边记录的关键字小于枢轴值,右边记录的关键字大于枢轴值 • 对左右两部分记录序列重复上述过程,依次类推,直到子序列中只剩下一个记录或不含记录为止。

  29. pivotkfy=49 high high low high high low low 快速排序 49 38 65 97 76 13 27 49 97 27 13 49 65 第一趟结果: [ 27 38 13 ] 49 [ 76 97 65 49 ] 大于等于49 小于49

  30. 快速排序算法 int Partition(SqList &L,int low,int high) { L.r[0]=L.r[low]; // 用子表的第一个记录作枢轴记录 pivotkfy=L.r[low].kfy; // 枢轴记录关键字 whilf(low< high) { // 从表的两端交替地向中间扫描 whilf(low<high&&L.r[high].kfy>=pivotkfy) --high; L.r[low]=L.r[high]; // 将比枢轴记录小的记录移到低端 whilf(low<high&&L.r[low].kfy<=pivotkfy) ++low; L.r[high]=L.r[low]; // 将比枢轴记录大的记录移到高端 } L.r[low]=L.r[0]; // 枢轴记录到位 rfturn low; // 返回枢轴位置 }

  31. 快速排序算法 void QSort(SqList &L,int low,int high) { // 对顺序表L中的子序列L.r[low..high]作快速排序。 if(low<high) { // 长度大于1 pivotloc=Partition(L,low,high); // 将L.r[low..high]一分为二 QSort(L,low,pivotloc-1); // 对低子表递归排序,pivotloc是枢轴位置 QSort(L,pivotloc+1,high); // 对高子表递归排序 } } void QuickSort(SqList &L) { // 对顺序表L作快速排序。 QSort(L,1,L.lfngth); }

  32. 算法分析 • 在n个元素的序列中,对一个对象定位所需时间为O(n)。若设 t(n) 是对 n 个元素的序列进行排序所需的时间,而且每次对一个对象正确定位后,正好把序列划分为长度相等的两个子序列,此时,总的计算时间为: T(n)  cn + 2 t(n/2 ) // c是一个常数 Cn + 2 ( cn/2 + 2t(n/4) ) = 2cn + 4t(n/4)  2cn + 4 ( cn/4 +2t(n/8) ) = 3cn + 8t(n/8) ………  Cn log2n + nt(1) = o(n log2n )

  33. 9.4 直接选择排序 • 基本思想:从待排序的数据元素集合中选取最小的数据元素并将它与原始数据元素集合中的第一个数据元素交换位置;然后从不包括第一个位置上数据元素的集合中选取最小的数据元素并将它与原始数据元素集合中的第二个数据元素交换位置;如此重复,直到数据元素集合中只剩一个数据元素为止。

  34. k j k j j j j k j j k 直接选择排序 13 i=1 49

  35. k j j j j j j k k 直接选择排序 13 i= 2 49

  36. 直接选择排序的算法 void SflfctSort ( SqList & L) { for ( int i = 1; i < L.lfngth; i++ ) k=SflfctMinKfy( L, i ); if(i!=k) L.r[i] ←→L.r[k]; } void SflfctMinKfy (SqList & L, const int i ) { int k = i; for (j = i+1; j < =L.lfngth; j++) if (L.r[j]. kfy < L.r[k].kfy) k = j; //当前具最小关键码的对象 rfturn k; }

  37. - n 1 1 2 å - = - = n i n ( n 1 ) O ( n ) ( ) 2 = i 1 直接选择排序算法的性能分析 移动次数: 最好情况(正序):0次 最坏情况:3(n-1)次 比较次数: 简单选择排序的时间复杂度为O(n2)。

  38. 树形选择排序 • 排序思想:首先取得 n 个对象的关键码,进行两两比较,得到n/2 个比较的优胜者(关键码小者),作为第一步比较的结果保留下来。然后对这 n/2 个对象再进行关键码的两两比较,…,如此重复,直到选出一个关键码最小的对象为止。

  39. 13 38 13 38 65 13 27 49 38 65 97 76 13 27 49

  40. 38 13 38 65 13 27 27 13 38 27 38 65 76 27 49 38 65 97 76 13 27 49 ∞

  41. 38 27 38 65 76 27 13 38 27 38 49 38 65 76 49 49 38 65 97 76 13 27 49 ∞ ∞

  42. 算法分析 • 除第一次选择具有最小关键码的对象需要进行 n-1 次关键码比较外,重构胜者树选择具有次小、再次小关键码对象所需的关键码比较次数均为 O(log2n)。总关键码比较次数为O(nlog2n)。 • 对象的移动次数不超过关键码的比较次数,故锦标赛排序总的时间复杂度为O(nlog2n) • 这种排序方法虽然减少了许多排序时间,但是使用了较多的附加存储。

  43. 9.5 堆排序 性质5 (2)如果2×i+1<n,则序号为i结点的左孩子结点的序号为2×i+1;如果2×i+1≥n,则序号为i结点无左孩子结点。 • 把待排序的数据元素集合构成一个完全二叉树结构,则每次选择出一个最大(或最小)的数据元素 • 设数组a中存放了n个数据元素,数组下标从0开始,如果当数组下标2i+1<n时有:a[i]≥a[2i+1];如果当数组下标2i+2<n时有:a[i]≥a[2i+2],则这样的数据结构称为最大堆。

  44. 12 96 36 24 83 27 85 47 30 53 38 11 09 91 堆排序 • 设数组a中存放了n个数据元素,数组下标从0开始,如果当数组下标2i+1<n时有:a[i]≤a[2i+1];如果当数组下标2i+2<n时有:a[i]≤a[2i+2],则这样的数据结构称为最小堆。

  45. 基本思想: • 以初始关键字序列,建立堆; • 输出堆顶最小元素; • 调整余下的元素,使其成为一个新堆; • 重复2,3步,直到n个元素输出,得到 一个有序序列。 关键问题: • 如何由一个无序序列建成一个堆? • 如何调整余下的元素成为一个新堆?

  46. 49 堆调整过程 rc=13 13 27 38 76 65 49 97

  47. 筛选过程 void HeapAdjust (SqList R[], int s, int m) { //已知R[s..m]中记录的关键字除R[s].key之外均满足堆的定义,本函数调整R[s] 的关键字,使R[s..m]成为一个大顶堆 rc = R[s]; for ( j=2*s; j<=m; j*=2 ) { // 沿key较大的孩子结点向下筛选 if ( j<m && R[j].key<R[j+1].key ) ++j; // j为key较大 //的记录的下标 if ( rc.key >= R[j].key ) break; // rc应插入在位置s上 R[s] = R[j]; s = j; } R[s] = rc; // 插入 } // HeapAdjust

  48. 建堆过程 • 无序序列建堆过程 方法:从完全二叉树的最后一个非叶结点 n/2  开始,反复调用筛选过程,直到第一个结点,则得到一个堆。

  49. 建堆算法 void HeapSort ( SqList R[], int n ) {// 对记录序列R[1..n]进行堆排序。 for ( i=n/2; i>0; --i ) // 把R[1..n]建成大顶堆 HeapAdjust ( R, i, n ); for ( i=n; i>1; --i ) { R[1]←→R[i]; // 将堆顶记录和当前未经排序子序列R[1..i]中最后一个记录相互交换 HeapAdjust(R, 1, i-1); } //将R[1..i-1] 重新调整为大顶堆 } // HeapSort

  50. 性能分析 • 对深度为k的堆,“筛选”所需进行的关键字比较的次数至多为2(k-1); • 对n个关键字,建成深度为h(=log2n+1)的堆,所需进行的关键字比较的次数至多为4n; • 调整“堆顶”n-1次,总共进行的关键字比较的次数不超过2( log2(n-1) + log2(n-2)+ …+log22)<2n( log2n ) 因此,堆排序的时间复杂度为O(nlogn),不稳定。

More Related