1 / 53

《 数据结构 》

《 数据结构 》. 第十章 内部排序. 第十章 内部排序. 10.1 概述 10.2 插入排序(知识点一) 10.2.1 直接插入排序 10.2.2 其它插入排序 10.2.3 希尔排序 10.3 快速排序(知识点二) 10.4 选择排序 10.4.1 简单选择排序 10.4.3 堆排序 10.5 归并排序(知识点三) 10.6 基数排序 10.6.1 多关键字的排序 10.6.2 链式基数排序 10.7 各种内部排序方法的比较讨论. 10.1 概 述. 一、排序的定义. 二、内部排序和外部排序.

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. 《数据结构》 第十章 内部排序

  2. 第十章 内部排序 • 10.1 概述 • 10.2 插入排序(知识点一) 10.2.1 直接插入排序 10.2.2 其它插入排序 10.2.3 希尔排序 • 10.3 快速排序(知识点二) • 10.4 选择排序 10.4.1 简单选择排序 10.4.3 堆排序 • 10.5 归并排序(知识点三) • 10.6 基数排序 • 10.6.1 多关键字的排序 • 10.6.2 链式基数排序 • 10.7 各种内部排序方法的比较讨论

  3. 10.1 概 述 一、排序的定义 二、内部排序和外部排序 三、内部排序方法的分类

  4. 一、排序的定义 什么是排序? 排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。 例1:将下列关键字序列 {52, 49, 80, 36, 14, 58, 61, 23, 97, 75} 调整为 {14, 23, 36, 49, 52, 58, 61 ,75, 80, 97}

  5. 一般情况下, 假设含n个记录的序列为{ R1, R2, …, Rn } 其相应的关键字序列为 { K1, K2, …,Kn } 这些关键字相互之间可以进行比较,即在 它们之间存在着这样一个关系 : Kp1≤Kp2≤…≤Kpn 按此固有关系将上式记录序列重新排列为 { Rp1, Rp2,…,Rpn } 的操作称作排序。

  6. 二、内部排序和外部排序  若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序; 反之,若参加排序的记录数量很大, 整个序列的排序过程不可能在内存中 完成,则称此类排序问题为外部排序。

  7. 三、内部排序方法的分类 内部排序的过程是一个逐步扩大 记录的有序序列长度的过程。 有序序列区 无 序 序 列 区 经过一趟排序 有序序列区 无 序 序 列 区

  8. 基于不同的“扩大” 有序序列长度的方法,内部排序方法大致可分下列几种类型: 1、 插入类 2、 交换类 3、 选择类 4、 归并类 5、 其它方法

  9. 1、 插入类 将无序子序列中的一个或几个记录“插入”到有序序列中,从而增加记录的有序子序列的长度。

  10. 2、 交换类 通过“交换”无序序列中的记录从而得到其中关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。

  11. 3、 选择类 从记录的无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。

  12. 4、 归并类 通过“归并”两个或两个以上的记录有序子序列,逐步增加记录有序序列的长度。 5、 其它方法

  13. 10. 2 插 入 排 序(知识点一) 1、直接插入排序(基于顺序查找) 2、折半插入排序(基于折半查找) 3、表插入排序 (基于链表存储) 4、希尔排序 (基于逐趟缩小增量)

  14. 一趟直接插入排序的基本思想: 有序序列R[1..i-1] 无序序列 R[i..n] R[i] 有序序列R[1..i] 无序序列 R[i+1..n] 1.在R[1..i-1]中查找R[i]的插入位置, R[1..j].key  R[i].key < R[j+1..i-1].key;

  15. 一趟直接插入排序的基本思想-(1) 有序序列R[1..i-1] 无序序列 R[i..n] R[i] 有序序列R[1..i] 无序序列 R[i+1..n] 2.将R[j+1..i-1]中的所有记录均后移 一个位置;

  16. 一趟直接插入排序的基本思想-(2) 有序序列R[1..i-1] 无序序列 R[i..n] R[i] 有序序列R[1..i] 无序序列 R[i+1..n] 3.将R[i] 插入(复制)到R[j+1]的位置上。

  17. 实现“一趟插入排序”可分三步进行: 1.在R[1..i-1]中查找R[i]的插入位置, R[1..j].key  R[i].key < R[j+1..i-1].key; 2.将R[j+1..i-1]中的所有记录均后移 一个位置; 3.将R[i] 插入(复制)到R[j+1]的位置上。

  18. 小改进 大改进 不同的具体实现方法导致不同的算法描述 直接插入排序(基于顺序查找) 折半插入排序(基于折半查找) 表插入排序(基于链表存储) 希尔排序(基于逐趟缩小增量)

  19. 1、直接插入排序(基于顺序查找) 利用 “顺序查找”实现 “在R[1..i-1]中查找R[i]的插入位置”

  20. 算法的实现要点: (1)从R[i-1]起向前进行顺序查找,监视哨设置在R[0]; R[0] R[i] j j=i-1 插入位置 R[0] = R[i]; // 设置“哨兵” for (j=i-1; R[0].key<R[j].key; --j); // 从后往前找 循环结束表明R[i]的插入位置为 j +1

  21. (2)对于在查找过程中找到的那些关键字不小于R[i].key的记录,并在查找的同时实现记录向后移动;(2)对于在查找过程中找到的那些关键字不小于R[i].key的记录,并在查找的同时实现记录向后移动; for (j=i-1; R[0].key<R[j].key; --j); R[j+1] = R[j] 插入位置 R[0] R[i] j j= i-1 (3)上述循环结束后可以直接进行“插入”

  22. (4)令 i = 2,3,…, n, 实现整个序列的排序。 for ( i=2; i<=n; ++i ) if (R[i].key<R[i-1].key) {在R[1..i-1]中查找R[i]的插入位置; 插入R[i] ; }

  23. 例:序列为 {49, 38, 65,97,76,13,27,49} {(49),38,65,97,76,13,27,49} i=2 {(38,49),65,97,76,13,27,49} i=3 {(38,49,65),97,76,13,27,49} i=4 {(38,49,65,97),76,13,27,49} i=5 {(38,49,65,76,97),13,27,49} i=6 {(13,38,49,65,76,97),27,49} i=7 {(13,27,38,49,65,76,97),49} i=8 {(13,27,38,49,49,65,76,97)}

  24. 算法10.1 P265,对顺序表L作直接插入排序。 void InsertSort(SqList &L) { int i, j; // 升序排序 for( i = 2; i <= L.length; ++i) if (L.r[i].key < L.r[i-1].key ) { L.r[0] = L.r[i]; // 复制为哨兵 for(j = i-1; L.r[0].key < L.r[j].key; --j) L.r[j+1]=L.r[j]; // 记录后移 L.r[j+1] =L.r[0]; // 插入到正确位置 print L ; // 打印线性表 } } //InsertSort

  25. 内部排序的时间分析: 实现内部排序的基本操作有两个: (1)“比较”序列中两个关键字的大小; (2)“移动”记录。

  26. 对于直接插入排序: 最好的情况(关键字在记录序列中顺序有序): “移动”的次数:0 “比较”的次数: 最坏的情况(关键字在记录序列中逆序有序): “移动”的次数: “比较”的次数: 若待排序列是随机的,则直接插入排序的时间复杂度为O(n2)

  27. 2、折半插入排序(基于折半查找) 因为 R[1..i-1] 是一个按关键字有序的有序序列,则可以利用折半查找实现“在R[1..i-1]中查找R[i]的插入位置”,如此实现的插入排序为折半插入排序。

  28. 例: 插入 位置 i 14 36 49 52 58 61 80 23 97 75 L.r low high high high low m m m 14 36 49 52 58 61 80 23 36 49 52 58 61 23 97 75 80

  29. 插入 位置 例如: i L.r 14 36 49 52 80 58 61 23 97 75 high high low low low m m 插入 位置 m 再如: i 14 36 49 52 58 61 80 23 97 75 L.r low high high high low m m m 14 36 49 52 58 61 80 23 36 49 52 58 61 23 97 75 80

  30. 算法10.2 P267 对顺序表L作折半插入排序-(1) void BInsertSort(SqList &L) { int i,j,m,low,high; for(i = 2; i <= L.length; ++i) {L.r[0] = L.r[i]; // 将L.r[i]暂存到L.r[0] low = 1; high = i-1; // 在r[low..high]中折半查找有序插入的位置 while(low <= high)

  31. 算法10.2 P267 对顺序表L作折半插入排序-(2) { m = (low + high) / 2; // 折半 if(L.r[0].key <L.r[m].key) high = m-1; // 小于插入点在低半区 else 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]; // 插入 print(*L); } }

  32. 算法分析 折半插入排序只能减少排序过程中关键字比较的时间,并不能减少记录移动的时间,因此折半插入排序的时间复杂度仍为O(n )。 2

  33. 3、表插入排序 (基于链表存储) 为了减少在排序过程中进行的 “移动”记录的操作,必须改变排序过程中采用的存储结构。利用静态链表进行排序,并在排序完成之后,一次性地调整各个记录相互之间的位置,即将每个记录都调整到它们所应该在的位置上。

  34. 静态链表类型说明如下: #define SIZE 100 //静态链表容量 typedef struct { RcdType rc; //记录项 int next; //指针项 }SLNode; //表结点类型 typedef struct{ SLNode r[SIZE]; //0号单元为表头结点 int length; //链表当前长度 }SLinkListType; //静态链表类型

  35. j=0 k=3 23 68 45 0 1 3 2 for ( j=0, k = SL[0].next;SL[i].key>= SL[k].key ; j=k, k=SL[k].next ); { SL[j].next = i; SL[i].next = k; } K指向当前关键字最小的位置;j指向K的前驱;i指向要插入的值。

  36. i=4 插入37之前 37 j=3 k=2 23 68 45 0 1 3 2 for ( j=0, k = SL[0].next;SL[i].key>= SL[k].key ; j=k, k=SL[k].next ); { SL[j].next = i; SL[i].next = k; } //插入操作

  37. SL[i].next i=4 2 37 k=2 j=3 4 2 23 45 1 4 2 { SL[j].next = i; SL[i].next = k; } //将“37”即i结点插入到“23”j和“45”k之间;

  38. void LInsertionSort (Elem SL[ ], int n){ // 对记录序列SL[1..n]作表插入排序 SL[0].key = MAXINT ; SL[0].next = 1; SL[1].next = 0; for ( i=2; i<=n; ++i ) for ( j=0, k = SL[0].next;SL[k].key<= SL[i].key ; j=k, k=SL[k].next ); { SL[j].next = i; SL[i].next = k;} // 结点i插入在结点j和结点k之间 }// LinsertionSort

  39. 如何在排序之后调整记录序列? 算法中使用了三个指针: 其中:p指示第i个记录的当前位置 i指示第i个记录应在的位置 q指示第i+1个记录的当前位置

  40. 0 1 2 3 4 5 初始 0 1 2 3 4 5 i=1 P=4 q=3 0 1 2 3 4 5 i=2 P=3 q=5 0 1 2 3 4 5 q=2 i=3 P=5

  41. 0 1 2 3 4 5 i=3 P=5 q=2 0 1 2 3 4 5 i=4 P=2 q=1 0 1 2 3 4 5 i=5 P=1 q=0

  42. void Arrange ( Elem SL[ ], int n ) { p = SL[0].next; // p指示第一个记录的当前位置 for ( i=1; i<n; ++i ) { while (p<i) p = SL[p].next; q = SL[p].next; // q指示尚未调整的表尾 if ( p!= i ) { SL[p]←→SL[i]; // 交换记录,使第i个记录到位 SL[i].next = p; // 指向被移走的记录 } p = q; // p指示尚未调整的表尾, // 为找第i+1个记录作准备 } } // Arrange

  43. 四、希尔排序(又称缩小增量排序) 1959年由D.L. Shell提出,又称缩小增量排序. (Diminishing-increment sort)

  44. 基本思想:将整个待排序记录分割成若干个子序列,在子序列内分别进行直接插入排序,待整个序列中的记录基本有序时,对全体记录进行直接插入排序。基本思想:将整个待排序记录分割成若干个子序列,在子序列内分别进行直接插入排序,待整个序列中的记录基本有序时,对全体记录进行直接插入排序。 • 需解决的关键问题? • (1)应如何分割待排序记录,才能保证整个序列逐步向基本有序发展? • (2)子序列内如何进行直接插入排序?

  45. 分割待排序记录的目的? • 1. 减少待排序记录个数; • 2. 使整个序列向基本有序发展。基本有序:例如{1, 2, 8, 4, 5, 6, 7, 3, 9};局部有序:例如{6, 7, 8, 9, 1, 2, 3, 4, 5}。局部有序不能提高直接插入排序算法的时间性能。 • 启示? • 子序列的构成不能是简单地“逐段分割”,而是将相距某个“增量”的记录组成一个子序列。

  46. 13 08 30 40 25 49 25* 16 21 d = 4 40 49 30 13 21 08 25* 16 25 d = 2 40 49 30 13 21 08 25* 16 25 49 40 30 08 21 13 25* 16 25 49 d = 1 40 30 08 21 13 25* 16 25 49 30 40 08 13 16 21 25* 25 希尔插入排序过程示例 1 2 3 4 5 6 7 8 9 初始序列 08 30 40 25 49 25* 16 21 13

  47. 问题1:应如何分割待排序记录? • 解决方法: • 将相隔某个“增量”的记录组成一个子序列。 • 增量应如何取? • 希尔最早提出的方法是d1=n/2,di+1=di/2。 算法描述: for (d=n/2; d>=1; d=d/2) { 以d为增量,进行组内直接插入排序; }

  48. 问题2:子序列内如何进行直接插入排序? 解决方法: (1) 在插入记录r[i]时,自r[i-d]起往前跳跃式(跳跃幅度为d)搜索待插入位置,并且r[0]只是暂存单元,不是哨兵。当搜索位置<0,表示插入位置已找到。 (2) 在搜索过程中,记录后移也是跳跃d个位置。 (3) 在整个序列中,前d个记录分别是d个子序列中的第一个记录,所以从第d+1个记录开始进行插入。

  49. 将记录序列分成若干子序列,分别对每个子序列进行插入排序。将记录序列分成若干子序列,分别对每个子序列进行插入排序。 例如:将 n 个记录分成 d 个子序列: { R[1],R[1+d],R[1+2d],…,R[1+kd] } { R[2],R[2+d],R[2+2d],…,R[2+kd] } … { R[d],R[2d],R[3d],…,R[kd],R[(k+1)d] } 其中,d称为增量,它的值在排序过程中从大到小逐渐缩小,直至最后一趟排序减为 1。

  50. 技巧:子序列的构成不是简单地“逐段分割”,而是将相隔某个增量dk的记录组成一个子序列,让增量dk逐趟缩短(例如依次取5,3,1),直到dk=1为止。技巧:子序列的构成不是简单地“逐段分割”,而是将相隔某个增量dk的记录组成一个子序列,让增量dk逐趟缩短(例如依次取5,3,1),直到dk=1为止。 优点:让关键字值小的元素能很快前移;关键字值大的元素能很快后移,且序列若基本有序时,再用直接插入排序处理,时间效率会高很多。 算法复杂度:O(n ) 3/2

More Related