初等排序 讲义
这里我们简述一下初等排序的学习内容:
- 选择排序
- 冒泡排序
- 比较函数
- 结构体函数
选择排序
(1)基本思想:每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序排在待排序的数列的最前,直到全部待排序的数据元素排完。
(2)排序过程:
假设现在有 \(n\) 个数,他们分别是 \(a_1, a_2, a_3, a_4, ……, a_n\) ,我要将它们按照从小到大排序。
- 第 \(1\) 轮:我要选出最小的数作为 \(a_1\) ,于是我从 \(2\) 到 \(n\) 遍历 \(j\) ,如果 \(a_1 \lt a_j\) ,则交换 \(a_1\) 和 \(a_j\) ,遍历结束后我们保证 \(a_1\) 是最小的数。
- 第 \(2\) 轮:我要选出次小的数作为 \(a_2\) ,于是我从 \(3\) 到 \(n\) 遍历 \(j\) ,如果 \(a_2 \lt a_j\) ,则交换 \(a_2\) 和 \(a_j\) ,遍历结束后我们保证 \(a_2\) 是最次的数。
- 如是循环……
(3)选择排序代码示例:
#include <bits/stdc++.h>
using namespace std;
int n, a[100];
int main() {
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i < n; i ++)
for (int j = i+1; j <= n; j ++)
if (a[i] > a[j])
swap(a[i], a[j]);
for (int i = 1; i <= n; i ++) cout << a[i] << " ";
return 0;
}
这样我能保证在 \(n-1\) 轮比较后,我的数组能够按照从小到大的顺序排列好。这就是选择排序。
看上去有点像排老大——“我想当老大,不服的上来比一比,比过了你就当老大”。然后排完老大排老二,排完老二排老三,如是循环……
选择排序的时间复杂度是 \(O(n^2)\) 。
冒泡排序
(1)基本思想:以 \(n\) 个人排队为例,从第 \(1\) 个开始,一次比较相邻的两个是否 逆序(“逆序”的意思是说:比如要按照元素从小到大排序,而此时相邻的两个元素中前者比后者大,则需要调换这两者的值),若逆序就交换两人,接着第 \(2\) 个和第 \(3\) 个比,若逆序就交换两人,接着第 \(3\) 个和第 \(4\) 比,若逆序就交换两人,……,直到 \(n-1\) 和 \(n\) 比较,经过一轮比较后,则把最高的人排到最后,即将冒泡一样逐步冒到相应的位置。原 \(n\) 个人的排序就转换成 \(n-1\) 个人的排序问题。第二轮从第 \(1\) 个开始,一次比较相邻的两个人是否逆序对,若逆序就交换两人,直到 \(n-1\) 和 \(n\) 比较。如此,进行 \(n-1\) 轮之后,这个数列就会变成有序的数列。
(2)排序过程:
假设现在有 \(n\) 个数,他们分别是 \(a_1, a_2, a_3, a_4, ……, a_n\) ,我要将它们按照从小到大排序。
- 第 \(1\) 轮:我开一个循环变量 \(j\) 从 \(1\) 到 \(n-1\) ,每次比较 \(a_j\) 和 \(a_{j+1}\) ,如果 \(a_j > a_{j+1}\) ,则交换 \(a_j\) 和 \(a_{j+1}\) ;
其实我这么循环是有原因的:一开始 \(a_1\) 和 \(a_2\) 比较(如果逆序则交换)之后我能够保证 \(a_2\) 是前 \(2\) 个数中最大的;然后再拿 \(a_2\) 和 \(a_3\) 比较(如果逆序则交换)之后我能够保证 \(a_3\) 是前 \(3\) 个数中最大的;……,最后我们拿 \(a_{n-1}\) 和 \(a_n\) 比较,其实是拿前 \(n-1\) 个数中的最大的和 \(a_n\) 比(如果逆序则交换),那么我们在第 \(1\) 轮结束的时候 \(a_n\) 就是我们数列中最大的数,并且它也恰好存储在了 \(a_n\) 中。 - 第 \(2\) 轮:我开一个循环变量 \(j\) 从 \(1\) 到 \(n-2\) ,每次比较 \(a_j\) 和 \(a_{j+1}\) ,如果 \(a_j > a_{j+1}\) ,则交换 \(a_j\) 和 \(a_{j+1}\) ;
同时,我这么操作一遍之后能够保证 \(a_{n-1}\) 是我们数列中次大的; - 第 \(3\) 轮,我开一个循环变量 \(j\) 从 \(1\) 到 \(n-3\) ,每次比较 \(a_j\) 和 \(a_{j+1}\) ,如果 \(a_j > a_{j+1}\) ,则交换 \(a_j\) 和 \(a_{j+1}\) ;
- 如是循环……
(3)冒泡排序代码示例:
#include <bits/stdc++.h>
using namespace std;
int n, a[100];
int main() {
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i < n; i ++)
for (int j = 1; j <= n-i; j ++)
if (a[j] > a[j+1])
swap(a[j], a[j+1]);
for (int i = 1; i <= n; i ++) cout << a[i] << " ";
return 0;
}
这样我能保证在 \(n-1\) 轮比较后,我的数组能够按照从小到大的顺序排列好。这就是冒泡排序。
比较函数和sort函数
比较函数用于确定数组中的排序规则,比如给我一个 int a[]
数组,我可以使用 algorithm
库提供给我的 sort 函数给他排序,比如,我调用:
sort(a, a+n);
能够给数组从 a[0]
到 a[n-1]
按从小到大的顺序进行排序。因为 sort
函数就是按照默认从小到大的顺序来给数组中的元素进行排序的。
但是如果我想要使用 sort
函数来给数组中的元素按照从大到小的顺序排序呢?这个是用我们需要自定义一个比较函数,并且将函数名作为 sort
函数的第 \(3\) 个参数传递进去。
例如,我下面定义了一个名为 cmp
的比较函数,他里面有两个参数 a
和 b
,这个函数返回 true
说明 a
应该排在 b
前面,返回 false
说明 b
应该排在 a
后面:
bool cmp(int a, int b) {
return a > b;
}
然后将这个函数的名字作为参数传递给 sort
函数调用即可:
sort(a, a+n, cmp);
这样就能保证将 a[0]
到 a[n-1]
按照从大到小排序了。
特别的,对于结构体来说,因为结构体不知道怎么比较大小,所以需要你编写比较函数并放到 sort
函数里面去给他进行排序。
说明:其实对于结构体来说,也有办法让它直到怎么比较大小,就是给结构体定义一个小于号( \(\lt\) ),这种方法叫做“运算符重载”,但是运算符重载放在我们普及组初赛相对来说超纲了,所以这里就不过多的介绍“运算符重载”这个C++高级知识点了,目前的阶段是好好掌握比较函数。