排序的基础知识
概念
-
排序:P317
-
内部排序和外部排序
根据排序时数据所占用存储器的不同,可将排序分为两类。一-类是整个排序过程完全在内存中进行,称为内部排序;另一类是由于待排序记录数据量太大,内存无法容纳全部数据,排序需要借助外部存储设备才能完成,称为外部排序。 -
主关键字与次关键字
-
排序的稳定性:排序前后,具有相同关键字值的元素的相对位置不变(即只要值相同,那么若排序A前在B前面,排序后A也在B前面)
-
待排序的记录序列:
- 向量结构
- 链表结构
- 记录向量与地址向量结合(地址排序)
排序的类型
插入类排序
-
基本思想:在一个已排好序的记录子集的基础上,每一步将下一-个待排序的记录有序插入到已排好序的记录子集中,直到将所有待排记录全部插入为止。
-
直接插入:
-
算法思想:
直接插人排序是一种最基本的插人排序方法,其基本操作是将第i个记录插人到前面i-1个已排好序的记录中; -
直接插入排序法,在待排序的关键字序列基本有序且关键字个数n较少时,其算法的性能最佳。
-
具体过程为:
将第i个记录的关键字 K i K_{i} Ki顺次与其前面记录的关键字 K i − 1 , K i − 2 , . . . , K 1 K_{i-1},K_{i-2},..., K_{1} Ki−1,Ki−2,...,K1进行比较,将所有关键字大于Ki的记录依次向后移动一个位置,直到遇见一个关键字小于或者等于 K i K_{i} Ki的记录 K j K_{j} Kj,此时 K j K_{j} Kj后面必为空位置,将第i个记录插入空位置即可。完整的直接插人排序是从i=2开始的,也就是说,将第一个记录视为已排好序的单元素子集合,然后将第二个记录插入到单元素子集合中。i从2循环到n,即可实现完整的直接插入排序。 -
时间复杂度 O ( n 2 ) O(n^2) O(n2)
空间复杂度 O ( 1 ) O(1) O(1)
-
-
折半插入
-
对于有序表进行折半查找,其性能优于顺序查找。所以,可以将折半查找思想用于在有序记录r[1…i-1]中确定应插人位置,相应的排序法称为折半插人排序法。
-
时间复杂度 O ( n 2 ) O(n^2) O(n2)
空间复杂度 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)
-
-
希尔排序
-
又称缩小增量排序法,是一种基于插入思想的排序方法,它利用了直接插入排序的最佳性质,首先,将待排序的关键字序列分成若干个较小的子序列,对子序列进行直接插入排序,使整个待排序序列排好序。在时间耗费上,较直接插人排序法的性能有较大的改进。然后,在进行直接插人排序时,若待排序记录序列已经有序,直接插人排序的时间复杂度可以提高到O(n)。若待排序记录序列基本有序,即序列中具有下列特性的记录较少时: r[i].key < Max{r[j].key},(1 ≤ j < i),直接插人排序的效率会大大提高。希尔排序正是基于以上两点对直接插人排序进行了改进。
-
算法思想:先将待排序记录序列分割成若干个“较稀疏的"子序列,分别进行直接插人排序。经过上述粗略调整,整个序列中的记录已经基本有序,最后再对全部记录进行一次直接插入排序。
- 首先选定记录间的距离为d,(i=1),在整个待排序记录序列中将所有间隔为 d i d_{i} di的记录分成一组,进行组内直接插入排序。
- 然后取i=i+1,记录间的距离为 d i , ( d i < d i − 1 ) d_{i} , (d_{i}<d_{i-1}) di,(di<di−1) ,在整个待排序记录序列中,将所有间隔为 d i d_{i} di的记录分成一组,进行组内直接插入排序。
- 重复步骤2多次,直至记录间的距离 d i d_{i} di=1,此时整个只有一个子序列,对该序列进行直接插入排序,完成整个排序过程。
-
时间复杂度 O ( n 1.5 ) O(n^{1.5}) O(n1.5)
-
不稳定的排序
-
交换类排序
-
冒泡排序
- 反复扫描,顺次比较,逆序则交换
-
快速排序
-
算法思想:从待排序记录序列中选取一一个记录(通常选取第一个记录)为枢轴,其关键字设为 K i K_{i} Ki,然后将其余关键字小于 K i K_{i} Ki的记录移到前面,而将关键字大于或等于 K i K_{i} Ki的记录移到后面,结果将待排序记录序列分成两个子表,最后将关键字为 K i K_{i} Ki的记录插到其分界线的位置处。将这个过程称为一趟快速排序。通过一-次划分后,就以关键字为 K i K_{i} Ki的记录为界,将待排序序列分成了两个子表,且前面子表中所有记录的关键字均小于 K i K_{i} Ki而后面子表中的所有记录的关键字均大于或等于 K i K_{i} Ki。对分割后的子表继续按上述原则进行分割,直到所有子表,的表长不超过1为止,此时待排序记录序列就变成了一个有序表。
-
算法步骤:
- 假设待划分序列为r[low], r[low+1], … ,r[high]。首先将基准记录r[low]移至变量x中,使r[low]相当于空单元,然后反复进行如下两个扫描过程,直到low和high相遇。
- high从右向左扫描,直到r[high].key < x.key时,将r[high]移至空单元r[low], 此时τ[high]相当于空单元。
- low从左向右扫描,直到r[low].key ≥ x.key时,将r[low]移至空单元r[high] ,此时r[low]相当于空单元。
- 当low和high相遇时,r[low] (或r[high])相当于空单元,且r[low]左边所有记录的关键字均小于基准记录的关键字,而r[low]右边所有记录的关键字均大于或等于基准记录的关键字。最后将基准记录移至r[low]中,就完成了一次划分过程。对于r[low]左边的子表和r[low]右边的子表可采用同样的方法进行进一步划分。
- 假设待划分序列为r[low], r[low+1], … ,r[high]。首先将基准记录r[low]移至变量x中,使r[low]相当于空单元,然后反复进行如下两个扫描过程,直到low和high相遇。
-
选择类排序
-
简单选择排序
-
第i趟简单选择排序时,从第i个记录开始,通过n-i次关键字的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录进行交换。
-
如此反复,经过n-1趟简单选择排序,将把n-1个记录排到位,剩下一-个最小记录直接在最后,所以共需进行n-1趟简单选择排序。
-
-
树型选择排序
- 算法思想:树形选择排序也称作锦标赛排序。基本思想是先把待排序的n个记录的关键字两两进行比较,取出较小者,然后再在 n 2 \dfrac{n}{2} 2n 个较小者中,采用同样的方法进行比较,选出每两个中的较小者,如此反复,直至选出最小关键字记录为止。这个过程可以用一棵满二叉树来表示,不满时用关键字为∞的结点补满,选出的最小关键字记录就是这棵树的根结点。在输出最小关键字之后,为选出次小关键字,将最小关键字记录所对应的叶子结点的关键字值置为∞,然后从该叶结点开始和其兄弟结点的关键字比较,修改从该叶结点到根结点路径上各结点的值,则根结点的值即为次小关键字。重复进行上述过程,直到所有的记录全部输出
-
堆排序
-
概念:
- 大根堆:称各结点的关键字值满足条件:r[i].key ≥ r[2i].key 并且 r[i].key ≥ r[2i+1].key (i=1,2,…,n/2)的完全二叉树
- 小根堆:这棵完全二叉树中任意结点的关键字小于或者等于其左孩子和右孩子的关键字(当有左孩子或右孩子时)
-
算法思想:把待排序的记录的关键字存放在数组r[1…n]之中,将r看成是一棵完全二叉树的顺序表示,每个结点表示一个记录,第一个记录r[1]作为二叉树的根,以下各记录 r[2] ~ r[n] 依次逐层从左到右顺序排列,任意结点r[i]的左孩子是r[2i],右孩子是r[2i+1], 双亲是r[ / d f r a c i 2 /dfrac{i}{2} /dfraci2]。对这棵完全二叉树进行调整建堆。
-
关键步骤
-
重建堆-- 当堆顶记录改变时,重建堆
-
首先将与堆相应的完全二叉树根结点中的记录移出,该记录称为待调整记录。此时根结点相当于空结点,从空结点的左、右子树中选出一个关键字较大的记录,如果该记录的关键字大于待调整记录的关键字,则将该记录上移至空结点中。
-
此时,原来那个关键字较大的子结点相当于空结点,从空结点的左、右子树中选出一个关键字较大的记录,如果该记录的关键字仍大于待调整记录的关键字,则将该记录上移至空结点中。
-
重复上述移动过程,直到空结点左、右子树的关键字均小于待调整记录的关键字。此时,将待调整记录放人空结点即可。
- 上述调整方法相当于把待调整记录逐步向下“筛”的过程,所以一般称其为“筛选”法。
-
-
建初堆-- 由任意序列建初堆
- 将一个任意序列看成是对应的完全二叉树,由于叶结点可以视为单元素的堆,因而可以反复利用上述调整堆算法(“筛选”法),自底向上逐层把所有子树调整为堆,直到将整个完全二叉树调整为堆。
- 可以证明,上述完全二叉树中,最后一个非叶结点位于第 / d f r a c i 2 /dfrac{i}{2} /dfraci2个位置,n为二叉树结点数目。因此,“筛选”需从第 / d f r a c i 2 /dfrac{i}{2} /dfraci2个结点开始,逐层向上倒退,直到根结点。
-
具体操作
- 将待排序记录按照堆的定义建初堆,并输出堆顶元素。
- 调整剩余的记录序列,利用筛选法将前n-i个元素重新筛选建成为一个新堆,再输出堆顶元素。
- 重复(2),进行n-1次筛选,新筛选成的堆会越来越小,而新堆后面的有序关键字会越来越多,最后使待排序记录序列成为一个有序的序列,这个过程称之为堆排序。
-
-
归并排序
-
思想:基于合并,将两个及以上的子表合并成一个新的有序表
-
以2-路归并为例介绍
-
算法思想:假设初始序列含有n个记录,首先将这n个记录看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到 n 2 \dfrac{n}{2} 2n个长度为2(n为奇数时,最后一个序列的长度为1)的有序子序列。在此基础.上,再对长度为2的有序子序列进行两两归并,得到若干个长度为4的有序子序列。如此重复,直至得到一个长度为n的有序序列为止。
-
递归解决:
- 将 r1[ ]中的记录用归并法排序后放到r3[]中,可以分为下面三个步骤。
- 首先将rI[ ]前半段的记录用归并法排序后放到r2[ ]的前半段中。
- 将r1[ ]后半段的记录用归并法排序后放到r2[ ]的后半段中。
- 将r2[ ]的前半段和后半段合并到r3[ ]中。
-
分配排序
-
利用分配和收集进行排序
-
多关键字排序
- 低位优先的排序。例如对扑克按照花色和点数排序
-
链式基数排序
- 算法思想:基数排序属于上述“低位优先"排序法,通过反复进行分配与收集操作完成排序。假设记录r[i]的关键字为
k
e
y
i
key_{i}
keyi,
k
e
y
i
key_{i}
keyi是由d位十进制数字构成的,即
k
e
y
i
key_{i}
keyi =
K
i
1
K
i
2
K
i
3
.
.
.
K
i
i
K^1_{i}K^2_{i}K^3_{i}...K^i_{i}
Ki1Ki2Ki3...Kii,则每一位可以视为一个子关键字,其中
K
i
1
K^1_{i}
Ki1是最高位,
K
i
i
K^i_{i}
Kii是最低位,每一位的值都在0~9的范围内,此时基数rd=10。
排序时先按最低位的值对记录进行初步排序,在此基础上再按次低位的值进行进一.步排序。以此类推,由低位到高位,每一趟都是在前一趟的基础上,根据关键字的某一位对所有记录进行排序,直至最高位,这样就完成了基数排序的全过程。
- 算法思想:基数排序属于上述“低位优先"排序法,通过反复进行分配与收集操作完成排序。假设记录r[i]的关键字为
k
e
y
i
key_{i}
keyi,
k
e
y
i
key_{i}
keyi是由d位十进制数字构成的,即
k
e
y
i
key_{i}
keyi =
K
i
1
K
i
2
K
i
3
.
.
.
K
i
i
K^1_{i}K^2_{i}K^3_{i}...K^i_{i}
Ki1Ki2Ki3...Kii,则每一位可以视为一个子关键字,其中
K
i
1
K^1_{i}
Ki1是最高位,
K
i
i
K^i_{i}
Kii是最低位,每一位的值都在0~9的范围内,此时基数rd=10。
排序的总结
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
直接插入 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 是 |
冒泡 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 是 |
简单选择 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 否 |
希尔 | O ( n 1.5 ) O(n^{1.5}) O(n1.5) | O ( n 1.5 ) O(n^{1.5}) O(n1.5) | O ( 1 ) O(1) O(1) | 否 |
快速 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n 2 ) O(n^2) O(n2) | O ( l o g n ) O(logn) O(logn) | 否 |
堆 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( 1 ) O(1) O(1) | 否 |
归并 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n ) O(n) O(n) | 是 |
基数 | O ( d ( n + r d ) ) O(d(n+rd)) O(d(n+rd)) | O ( d ( n + r d ) ) O(d(n+rd)) O(d(n+rd)) | O ( r d ) O(rd) O(rd) | 是 |