经典算法之基础排序算法
计数排序
计数排序算法是一种基于分类而不是比较的排序,不依赖排序对象之间的直接大小比较。
比如:我们在投票的时候,有 n (n <= 1000) 名候选人,m (m <= 200000) 张选票,现在我们需要统计每名候选人的票数。
使用计数排序的思路:只需要开一个大小不小于 n 的数组作为票箱,依次读入选票,然后将选票加到对应的票箱中,每次都将票数 +1,最后输出每个票箱的票数即可。在这个过程中,甚至不需要存储下每一张选票。
代码如下:
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int a[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++){
int x;
cin >> x;
a[x]++;
}
for (int i = 1; i <= n; i++){
cout << a[i] << " ";
}
cout << endl;
return 0;
}
这种排序算法读入选票并统计的时间复杂度为 O(m),输出选票的时间复杂度为 O(n),空间复杂度为 O(n)。效率较低,所以计数排序只能用于排序编号 (值域) 范围不是很大的数字,此外,如果想要对浮点数或字符串进行排序,这种算法也实现不了。
下面三种就是针对元素大小进行排序的算法。
以数列排序为例:
输入 n (n < 1000) 个数字 ai (ai < 109) ,将其从小到大排序后输出。
1.选择排序
选择排序类似于摸牌时将纸牌排序。
思路:从第一张牌到最后一张牌中找到最小的一张,放在最前面的位置;然后从第二张牌到最后一张牌中继续找到最小的一张,放到第二位 … 如此反复,就可以得到从小到大的序列。如果遇到两个相等的数的时候,一般是不会去调换顺序的。
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int a[N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
//选择排序:多次遍历,每次找到最小的放在最前面
for (int i = 1; i <= n - 1; i++)
for (int j = i + 1; j <= n; j++)
if (a[j] < a[i]){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
for (int i = 1; i <= n; i++)
cout << a[i] << " ";
cout << endl;
return 0;
}
时间复杂度为 O(n2),空间复杂度为 O(n).
2.冒泡排序
冒泡排序的核心就是:依次遍历将最大的数放在最后面。在每一轮比较中,最大的元素都会 “咕噜咕噜” 一样从左边移动到右边,像冒泡泡一样,所以得名冒泡排序。
思路:依次比较相邻的两个元素,如果前一项大于后一项就交换位置,再往后移动一位,依次两两比较。
比如,首先比较第1张牌和第2张牌,如果后面的牌比前面的牌小,那么就交换位置;然后比较第2张牌和第3张牌,同样保证第3张牌要大于第2张牌 … 依次类推,直到比较最后一张牌和倒数第二张牌,保证最后面一张牌是最大的牌。
继续和上面一样进行新的一轮比较再交换,需要注意的是,由于最后一张牌已经是最大的了,所以只需要比较到倒数第二张牌就可以了。经过 n - 1 轮排序后,就可以得到有序的序列了。
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int a[N];
int main()
{
int n;
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]){
//依次比较相邻两个元素
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
for (int i = 1; i <= n; i++)
cout << a[i] << " ";
cout << endl;
return 0;
}
时间复杂度为 O(n2),空间复杂度为 O(n).
3.插入排序
边输入边排序:每次读取一个新的元素时,都将其插入到已排好序的序列当中,当读取完输入数据时,序列也就排好序了。
思路:把序列分为有序和无序的两部分。最开始有序的就只有一个元素,也就是第一个元素。要把接下来的无序区中一个元素插入到有序区中,就是从有序区的末尾开始往前比较,如果待插元素比正在比较的元素小,那么就把有序区的正在比较的元素往后放一格,然后继续往前面进行比较,直到待插元素遇到不大于自己的元素或者成为第一个为止。这时,待插元素就可以填入留出来的缺口中。接下来重复将无序区中的待插元素插入到有序区中去,直到所有的元素都在有序区中。
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int a[N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
//插入排序:分成有序和无序两部分,每次都将无序的一个元素插入到有序区中去
for (int i = 1, j; i <= n; i++){
int temp = a[i]; //记录一下待插元素,等下要插入到有序区中
for (j = i - 1; j >= 0; j--){
if (a[j] > temp)
a[j + 1] = a[j];
else break;
}
a[j + 1] = temp;
}
for (int i = 1; i <= n; i++)
cout << a[i] << " ";
cout << endl;
return 0;
}
最坏情况下的时间复杂度为 O(n2),空间复杂度为 O(n),如果能保证序列基本有序,那么插入排序的效率就会高一些。