排序是数据处理中经常使用的一种操作,其主要目的是便于查找。
1.没有特殊说明,本文排序后的结果都是从小到大(正序)
2.以下所用的算法,除桶式排序和基数排序外其r[0]作为哨兵或者闲置,数组从1开始存储。
3.主函数和输入样例如下:
#include<iostream>
using namespace std;
int main() {
cin >> n;
for (int i = 1; i <= n; i++)cin >> r[i];
//Insertsort();
//Shellsort();
//Bubblesort();
//Quicksort(1, n);
//Quickselectsort();
//Selectsort();
//Heapsort();
//Mergesort1();
//Mergesort2(1, n);
for (int i = 1; i <= n; i++)cout << r[i]<<" ";
return 0;
}
/*
10 7 1 4 6 8 9 5 2 3 10
—— 预期输出 ——
1 2 3 4 5 6 7 8 9 10
15 3 44 38 5 47 15 36 26 27 2 46 4 19 50 48
—— 预期输出 ——
2 3 4 5 15 19 26 27 36 38 44 46 47 48 50
*/
下面来介绍几种常用的排序。(教科书式开头 )
直接插入排序
具体的排序过程如下:
1)初始时有序区为待排序记录序列中的第一个记录,无序区包括所有剩余待排序的记录;
2)将无序区中的记录依次一个个的插入到有序区的合适位置中。
for(int i=2;i<=n;i++){
//插入第n个记录
}
完整代码如下:
int r[100];//排序数列
int n;//数列长度
void Insertsort() {
int i, j;
for (i = 2; i <= n; i++) {//无序数列依次插入
r[0] = r[i];//利用哨兵存储待排记录,并防止数组下标越界
for (j = i - 1; r[0] < r[j]; j--)
r[j + 1] = r[j];//若没有找到待插点,就让有序数列依次覆盖后移
r[j + 1] = r[0];//插入待插记录
}
}
性能分析
最好情况:3(n-1)------O(n)
最坏情况:(n-1)(n+3)------O(n^2)
平均情况:(n-1)(n+3)/2------O(n^2)
直接插入排序算法简单、容易实现,但是效率并不算突出,当序列中的记录基本有序或待排序记录较少时,它是最佳的排序算法。
另外,直接插入排序还是一种稳定的排序方法。
(稳定:相同元素的相对位置经过排序后不变)
希尔排序
希尔排序是对直接插入排序的一种改进。
改进的着眼点是:
1)若待排序记录按关键码基本有序,直接插入排序的效率很高;
2)由于直接插入排序算法简单,则在待排序记录个数较少时效率也很高
其基本思想是:先将整个待排序记录序列分割成若干个子序列,在子序列中分别进行直接插入排序,待整个序列基本有序时,再对全体记录进行一次直接插入排序。
注意:子序列的构成不能是简单地“逐段分割”,而是将相距某个“增量”的记录组成一个子序列,这样才能有效地保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
完整代码如下:
int r[100];//排序数列
int n;//数列长度
void Shellsort() {
int i, j, d;
for (d = n / 2; d >= 1; d /= 2) {//设定间隔d,d代表子序列个数,n/d代表每个序列的长度
for (i = d + 1; i <= n; i++) {//无序数列按间隔d依次插入
r[0] = r[i];//利用哨兵存储待排记录,并防止数组下标越界
for (j = i - d; j > 0 && r[0] < r[j]; j -= d)
r[j + d] = r[j];//若没有找到待插点,就让按间隔d有序的数列依次覆盖后移
r[j + d] = r[0];//插入待插记录
}
}
}
性能分析
最好情况:O(nlog2 n)
最坏情况:O(n^2)
平均情况:O(n^1.3)
希尔排序是一种不稳定的排序方法。
冒泡排序(起泡排序)
其基本思想是:两两比较相邻记录的关键码,如果反序则交换,直到没有反序的记录为止。
下面是一个简单的冒泡排序算法:
//简易的冒泡排序
for (int i = 1; i < n; i++){
for (int j = 1; j <= n-i; j++) {
if(r[j]>r[j+1])
//交换
}
}
然后我们再想想能不能进行优化。
其中,一个最大的优化点在于该算法的比较是不是存在“浪费现象”?
每次冒泡一趟真需要比较n-i次吗?
如果设置一个exchange变量记录交换处,设置一个bound变量表示冒泡一趟需进行的比较次数。
那么每次排序前让bound=exchange是不是可以降低比较的“浪费程度”?
下面是改进后的算法:
int r[100];//排序数列
int n;//数列长度
void Bubblesort() {
//当一趟不再有交换时,即已排序完成
//最后一次交换点就是下一趟排序的终止点(因为后面的皆已有序)
int exchange = n, bound;//初始化exchange=n
while (exchange != 0) {
bound = exchange;//将接下来的排序终点设为上一次排序最后的交换处
exchange = 0;//在排序之前将exchange初始化为0
for(int i=1;i<bound;i++)
if (r[i] > r[i + 1]){
int tmp = r[i];
r[i] = r[i + 1];
r[i + 1] = tmp;
exchange = i;//记录交换位置
}
}
}
性能分析
在平均情况下,起泡排序的时间复杂度与最坏情况同数量级O(n^2)
起泡排序是一种稳定的排序方法
快速排序
快速排序是对冒泡排序的一种改进,改进的着眼点是:在冒泡排序中,记录的比较和移动是在相邻位置进行的,记录每次交换只能后移一个位置,因而总的比较次数和移动次数较多。而在快速排序中,记录的比较和移动是从两端向中间进行的。
快速排序涉及到选择基准轴值的问题,可以选择中间记录的关键码,或者在每次划分之前比较待排序序列的第一个记录,最后一个记录和中间记录的关键码,选取居中的关键码作为轴值并调换到第一个记录的位置
下面提供选取第一个记录和最后一个记录作为基准轴值的情形。
int Partition(int first, int end) {
//此处选择数列第一个记录作为基准
while (first < end) {
while (first < end&&a