408答疑
文章目录
三、交换排序
交换排序算法
交换的定义
- 所谓交换,是指根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。
基于交换的排序算法
- 基于交换的排序算法很多,这里我们主要介绍冒泡排序和快速排序,其中冒泡排序算法比较简单,一般很少直接考查,通常会重点考查快速排序算法的相关内容。
冒泡排序
冒泡排序的基本思想
- 冒泡排序的基本思想是从后往前(或从前往后)两两两比较相邻元素的值,若为逆序(即 A [ i − 1 ] > A [ i ] A[i-1] > A[i] A[i−1]>A[i]),则交换它们,直到序列比较完。我们称它为第一趟冒泡,结果是将最小的元素交换到待排序列的第一个位置(或将最大的元素交换到待排序列的最后一个位置),关键字最小的元素如气泡一般逐渐往上“漂浮”至“水面”(或关键字最大的元素如石头一般下沉至水底)。下一趟冒泡时,前一趟确定的最小元素不再参与比较,每趟冒泡的结果是把序列中的最小元素(或最大元素)放到了序列的最终位置……这样最多做 n − 1 n-1 n−1 趟冒泡就能把所有元素排好序。
冒泡排序的过程
- 初始序列: 4 9 1 , 38 , 65 , 97 , 76 , 13 , 27 , 4 9 2 49_1, 38, 65, 97, 76, 13, 27, 49_2 491,38,65,97,76,13,27,492。
- 下图所示为冒泡排序的过程,第一趟冒泡时:27 < 4 9 2 49_2 492,不交换;13 < 27,不交换;76 > 13,交换;97 > 13,交换;65 > 13,交换;38 > 13,交换; 4 9 1 49_1 491 > 13,交换。通过第一趟冒泡后,最小元素已交换到第一个位置,也是它的最终位置。第二趟冒泡时对剩余子序列采用同样方法进行排序,如此重复,到第五趟结束后没有发生交换,说明表已有序,冒泡排序结束。
冒泡排序的全局有序性
- 冒泡排序中所产生的有序子序列一定是全局有序的(不同于直接插入排序),也就是说,有序子序列中的所有元素的关键字一定小于(或大于)无序子序列中所有元素的关键字,这样每趟排序都会将一个元素放置到其最终的位置上。
冒泡排序的效率
-
空间效率:仅使用了常数个辅助单元,因而空间复杂度为 O ( 1 ) O(1) O(1)。
-
时间效率:
- 当初始序列有序时,显然第一趟冒泡后 flag 依然为 false(本趟没有元素交换),从而直接跳出循环,比较次数为 n − 1 n-1 n−1,移动次数为 0,从而最好情况下的时间复杂度为 O ( n ) O(n) O(n)。
- 当初始序列为逆序时,需要进行 n − 1 n-1 n−1 趟排序,第 i i i 趟排序要进行 n − i n-i n−i 次关键字的比较,而且每次比较后都必须移动元素 3 次来交换元素位置。这种情况下,比较次数为 ∑ i = 1 n − 1 ( n − i ) = n ( n − 1 ) 2 \sum_{i=1}^{n-1} (n-i) = \frac{n(n-1)}{2} ∑i=1n−1(n−i)=2n(n−1),移动次数为 ∑ i = 1 n − 1 3 ( n − i ) = 3 n ( n − 1 ) 2 \sum_{i=1}^{n-1} 3(n-i)= \frac{3n(n-1)}{2} ∑i=1n−13(n−i)=23n(n−1)。
- 从而,最坏情况下的时间复杂度为 O ( n 2 ) O(n^2) O(n2),平均时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
-
稳定性: i > j i > j i>j 且 A [ i ] = A [ j ] A[i] = A[j] A[i]=A[j] 时,不会发生交换,因此冒泡排序是一种稳定的排序算法。
-
适用性:冒泡排序适用于顺序存储和链式存储的线性表。
快速排序
快速排序的基本思想
- 快速排序(有时简称快排)的基本思想是基于分治法的:在待排序表 L [ 1.. n ] L[1..n] L[1..n] 中任取一个元素 pivot 作为枢轴(或称基准,通常取首元素),通过一趟排序将待排序表划分为独立的两部分 L [ 1... k − 1 ] L[1...k-1] L[1...k−1] 和 L [ k + 1... n ] L[k+1...n] L[k+1...n],使得 L [ 1... k − 1 ] L[1...k-1] L[1...k−1] 中的所有元素小于 pivot, L [ k + 1... n ] L[k+1...n] L[k+1...n] 中的所有元素大于或等于 pivot,则 pivot 放在了其最终位置 L ( k ) L(k) L(k) 上,这个过程称为一次划分。然后分别递归地对两个子表重复上述过程,直至每部分内只有一个元素或为空为止,即所有元素放在了其最终位置上。
快速排序的中间过程分析
- 假设划分算法已知,记为 Partition(),返回的是上述的 k,则 L ( k ) L(k) L(k) 已放在其最终位置。因此可以先对表进行划分,然后对两个子表递归地调用快速排序算法进行排序。
- 快速排序算法的性能主要取决于划分操作的好坏。考研所考查的快速排序的划分操作通常总以表中第一个元素作为枢轴来对表进行划分,则将表中比枢轴大的元素向右移动,将比枢轴小的元素向左移动,使得一趟 Partition() 操作后,表中的元素被枢轴一分为二。
快速排序的过程
- 一趟快速排序的过程是一个交替搜索和交换的过程,通过实例来介绍,附设两个指针
i
i
i 和
j
j
j,初值分别为 low 和 high,取第一个元素 49 为枢轴赋值到变量 pivot。
-
第一次交换:指针 j j j 从 high 往前搜索找到第一个小于枢轴的元素 27,将 27 交换到 i i i 所指位置。
-
第二次交换:指针 i i i 从 low 往后搜索找到第一个大于枢轴的元素 65,将 65 交换到 j j j 所指位置。
-
第三次交换:指针 j j j 继续往前搜索找到小于枢轴的元素 13,将 13 交换到 i i i 所指位置。
-
第四次交换:指针 i i i 继续往后搜索找到大于枢轴的元素 97,将 97 交换到 j j j 所指位置。
-
继续搜索:指针 j j j 继续往前搜索小于枢轴的元素,直至 i = = j i == j i==j。
-
此时,指针 i i i(== j j j)之前的元素均小于 49,指针 i i i 之后的元素均大于或等于 49,将 49 放在 i i i 所指位置即其最终位置,经过第一趟排序后,将原序列分割成了前后两个子序列。
-
- 按照同样的方法对各子序列进行快速排序,若待排序列中只有一个元素,显然已有序。
- 用二叉树的形式描述这个快速排序示例的递归调用过程,如下图所示
快速排序的全局有序性
- 在快速排序算法中,并不产生有序子序列,但每一趟排序后会将上一趟划分的各个无序子表的枢轴(基准)元素放到其最终的位置上。
快速排序的效率
-
空间效率:由于快速排序是递归的,因此需要借助一个递归工作栈来保存每层递归调用的必要信息,其容量与递归调用的最大层数一致。最好情况下为 O ( log 2 n ) O(\log_2 n) O(log2n);最坏情况下,要进行 n − 1 n-1 n−1 次递归调用,因此栈的深度为 O ( n ) O(n) O(n);平均情况下,栈的深度为 O ( log 2 n ) O(\log_2 n) O(log2n)。
-
时间效率:
- 快速排序的运行时间与划分是否对称有关,快速排序的最坏情况发生在两个区域分别包含 n − 1 n-1 n−1 个元素和 0 个元素时,这种最大限度的不对称性若发生在每层递归上,即对应于初始排序表基本有序或基本逆序时,就得到最坏情况下的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 有很多方法可以提高算法的效率:一种方法是尽量选取一个可以将数组划分中分的枢轴元素,如从序列的头尾及中间选取三个元素,再取这三个元素的中间值作为最终的枢轴元素;或者随机地从当前表中选取枢轴元素,这样做可使得最坏情况在实际排序中几乎不会发生。
- 在最理想的状态下,即 Partition() 能做到最平衡的划分,得到的两个子问题的大小都不可能大于 n / 2 n/2 n/2,在这种情况下,快速排序的运行速度将大大提升,此时,时间复杂度为 O ( n log 2 n ) O(n\log_2 n) O(nlog2n)。好在快速排序平均情况下的运行时间与其最佳情况下的运行时间很接近,而不是接近其最坏情况下的运行时间。快速排序是所有内部排序算法中平均性能最优的排序算法。
-
稳定性:在划分算法中,若右端区间有两个关键字相同,且均小于基准值的记录,则在交换到左端区间后,它们的相对位置会发生变化,即快速排序是一种不稳定的排序算法。
- 例如,表 L = { 3 , 2 1 , 2 2 } L = \{3, 2_1, 2_2\} L={3,21,22},经过一趟排序后 L = { 2 2 , 2 1 , 3 } L = \{2_2, 2_1, 3\} L={22,21,3},最终排序序列也是 L = { 2 2 , 2 1 , 3 } L = \{2_2, 2_1, 3\} L={22,21,3},显然, 2 1 2_1 21 与 2 2 2_2 22 的相对次序已发生了变化。
-
适用性:快速排序仅适用于顺序存储的线性表。
九、参考资料
鲍鱼科技课件
b站免费王道课后题讲解:
网课全程班: