![IMG_1866](https://i-blog.csdnimg.cn/blog_migrate/c6b893405141503cf3f6ea6dcbf8621d.png)
C++ 算法竞赛中的排序算法
算法背景
对于给定的由整数组成的长度为 n n n 的数组 a a a,将其重新排列顺序,变成从左到右非递减的数组。
非递减: 即对于排序好的数组中的每一个元素 a i ( 1 < i ⩽ n ) a_i\;(1<i\leqslant n) ai(1<i⩽n),都有 a i ⩾ a i − 1 a_i \geqslant a_{i-1} ai⩾ai−1。
非递增: 即对于排序好的数组中的每一个元素 a i ( 1 < i ⩽ n ) a_i\;(1<i\leqslant n) ai(1<i⩽n),都有 a i ⩽ a i − 1 a_i \leqslant a_{i-1} ai⩽ai−1。
除特殊说明外,如下介绍算法的算法流程默认均为排序成非递减的。
排序算法的稳定性: 若在某个排序算法完成后,数组中的值相同的元素的相对位置不变,则称使用的排序算法是稳定的;反之,则称所使用的的排序算法是不稳定的。
冒泡排序
算法流程
设数组中后 i + 1 ∼ n i+1\sim n i+1∼n 项元素已经排序成整个数组中的第 i + 1 ∼ n i+1 \sim n i+1∼n 大的元素,设指针 j = 1 j=1 j=1。
执行以下操作:
- 若 a j > a j + 1 a_j>a_{j+1} aj>aj+1,则将这两项元素交换;
- 若 j = i j=i j=i,则停止,否则令 j = j + 1 j=j+1 j=j+1。
对于 i = n ∼ 1 i=n \sim 1 i=n∼1 都执行一次该算法流程,即可完成排序。
特殊地,对于 i = n i=n i=n,数组内并没有所谓第 n + 1 n+1 n+1 项元素,但这不影响算法的实现。
算法的正确性与稳定性
正确性:
该算法流程保证了数组中的前 1 ∼ i 1 \sim i 1∼i 项中的最大值移动到数组的第 i i i 个位置,故该算法是正确的。
就像所有数字都沉于水中,而每次最大的数字都会『咕咕冒泡』般地浮上来,因此该算法被称为冒泡排序。
稳定性:
当遇到相同值的元素时,该算法并不交换两项元素的顺序,故该算法是稳定的。
C++ 代码实现
void Bubble_Sort(vector<int> &a) {
int n = a.size();
for (int i = n - 1; i >= 0; --i) {
for (int j = 0; j < i; ++j) {
if (a[j] > a[j + 1]) {
swap(a[j], a[j + 1]);
}
}
}
}
如果读者不熟悉
vector
,可以把它想象成一个不定长的动态数组,而a.size()
是返回a
这个不定长数组长度的值的函数。
- 代码第 1 1 1 行,
vector<int> &a
有两个目的:
防止
a
的改变只在函数内,并不实际改变a
;防止程序复制一遍
a
,降低代码效率。
由于
vector
的下标从 0 0 0 开始,故下标可能与算法流程描述有所不同。算法流程体现在代码中的:
代码第 5 ∼ 6 5 \sim 6 5∼6 行即为算法流程的第 1 1 1 步;
代码第 4 4 4 行,
for
循环的终止条件为j=i
,若不终止则执行++j
,即 j = j + 1 j=j+1 j=j+1,体现了算法流程的第 2 2 2 步。使用方法:
- 传入想要排序的
vector a
即可,函数会将a
自动排序成非递减的。- 如果想要排序成非递增的,则将代码第 5 5 5 行的
if
判断语句内的条件改为a[j]<a[j+1]
即可。- 需要引用的头文件和命名空间:
#include <vector> #include <algorithm> using namespace std;
算法的时间复杂度
代码由两层 for
循环组成,每层的最坏循环次数为 n n n 次,其中 n n n 为数组内的元素个数,故该算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
算法拓展
冒泡排序的交换次数
Q:在冒泡排序中,两项元素的交换次数总共为多少次?
A:交换次数为逆序对的数量。
逆序对:
- 在排序要求是非递减的情况下,对于原始数组中的两个不同的元素 a i , a j a_i,a_j ai,aj,若 a i > a j a_i>a_j ai>aj,则称 ( a i , a j ) (a_i,a_j) (ai,aj) 为 1 1 1 对逆序对;
- 在排序要求是非递增的情况下,对于原始数组中的两个不同的元素 a i , a j a_i,a_j ai,aj,若 a i < a j a_i<a_j ai<aj,则称 ( a i , a j ) (a_i,a_j) (ai,aj) 为 1 1 1 对逆序对;
证明:
- 对于算法流程的第 1 1 1 步来说,若 a j > a j + 1 a_j>a_{j+1} aj>aj+1,则说明 ( a j , a j + 1 ) (a_j,a_{j+1}) (aj,aj+1) 是 1 1 1 对逆序对,经过交换后,则这 1 1 1 对逆序对就消除了;
- 但是对于数组 1 ∼ j − 1 1\sim j-1 1∼j−1 和 j + 2 ∼ n j+2 \sim n j+2∼n 项来说, a j , a j + 1 a_j,\;a_{j+1} aj,aj+1 与它们之间的相对位置没有改变,故只会减少这 1 1 1 对逆序对;
- 所以只要存在逆序对,冒泡排序就不应该停止,但每次交换只会消除 1 1 1 对逆序对,故冒泡排序的交换次数为逆序对的数量。
□ \Box □
归并排序
算法流程
设当前数组 a a a 中需要排序的范围为 [ l , r ] [l,r] [l,r],令 m i d = ⌊ l + r 2 ⌋ mid=\left\lfloor \frac{l+r}{2}\right \rfloor mid=⌊2