排序算法是比较基础也是很重要的一类算法,如何根据实际情况采用高效的排序对于实际应用十分关键
虽然很多排序算法都集成到了语言的核心库中,但是理解其基本原理对我们还是很有帮助。
先给出一个交换函数,因为排序算法大多涉及两个数交换,所以先写一个交换函数以便更好地复用
template<typename E>
inline void swap(E A[], int i, int j) {
E temp = A[i];
A[i] = A[j];
A[j] = temp;
}
1、冒泡排序
冒泡排序的原理如其名字一样,是参考现实生活中泡泡在水中不断往上冒的情形,在数组当中不断遍历,小的元素往上冒,随后就得到一个有序的数组。
冒泡排序是一种稳定排序,时间复杂度为O(n^2)
template<typename E>
void bubbleSort(E A[],int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = n - 1; j > i; j--) {
if (A[j] < A[j - 1])swap(A, j, j - 1);
}
}
}
2、插入排序
插入排序的原理是对于一个无序数组,从第一个遍历到最后一个元素,把该元素放到其应该在的位置,遍历到最后一个元素时,数组便排序好了。
插入排序是一种稳定排序,时间复杂度为O(n^2)
template<typename E>
void insertionSort(E A[],int n) {
for (int i = 1; i < n; i++) {
for (int j = i; j > 0; j--) {
if (A[j] < A[j - 1])swap(A, j, j - 1);
}
}
}
3、选择排序
选择排序的原理是跟插入排序类似,但是其内层循环的目的是找到第lowindex小的元素,并把它与该数组的第lowindex个位置上面,算法运行n-1遍时数组就排好了序,因为前n-1个元素排好序时,第n个元素也就在其该有的位置也就是最后了。
选择排序也是一种稳定排序,时间复杂度为O(n^2)
template<typename E>
void selectionSort(E A[], int n) {
for (int i = 0; i < n - 1; i++) {
int lowindex = i;
for (int j = n - 1; j > i; j--) {
if (A[j] < A[lowindex]) {
lowindex = j;
}
}
swap(A, i, lowindex);
}
}
4、希尔排序
希尔排序算是一种改进版的插入排序,它设置了一个gap值,目的是把数组分为gap组,然后对于这gap个组进行小范围的插入排序,比如说第一轮把数组A分为三个小数组a,b,c,第一轮时候a[0],b[0],c[0]之间就是有序的,同样的a[1],b[1],c[1]...同样是有序的,每排好一次gap的值便减小,数组分的越来越大,当gap为1时整个数组就拍好了序。
希尔排序是一种不稳定排序,时间复杂度为O(n^1.5)
template<typename E>
void shellSort(E A[],int n) {
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int temp = A[i];
int j = i - gap;
for (; j >= 0 && A[j] > temp; j -= gap) {
A[j + gap] = A[j];
}
A[j + gap] = temp;
}
}
}
5、快速排序
快速排序的原理是在数组当中选一个基准值,基准值可以是数组中的任意一个元素,并把比基准值大的数放到该基准值的右边,比它小的放到其左边,再对基准值左边和基准值右边的序列作同样操作,当分到子序列元素个数为1时便排好序,这是分治法的一个很经典的应用。
快速排序是一种不稳定排序,时间复杂度为O(nlogn)
需要注意的是如果每次基准值的选取都是最大或者最小的一个元素的话,那么就会退化成冒泡排序,时间复杂度就会降为O(n^2)
template<typename E>
void qsort(E A[],int left,int right) {
if (left < right - 1) {
int lleft = left;
int rleft = right - 1;
while (lleft < rleft) {
while (A[rleft] >= A[left] && lleft < rleft)
rleft--;
while (A[lleft] <= A[left]&&lleft < rleft)
lleft++;
swap(A, lleft, rleft);
}
swap(A, left, lleft);
qsort(A, left, lleft);
qsort(A, lleft + 1, right);
}
}
template<typename E>
void quickSort(E A[],int n) {
qsort(A, 0, n);
}
6、归并排序
归并排序也是运用分治法思想的一种排序方法,思想是把数组不断地一分为二,对两个小的序列进行排序,然后合成一个大的序列,对于一个8个元素的数组来说,第一遍把12、34、56、78排好序,然后合并成1234、5678,最后合并成12345678也即排好序的数组。
归并排序是一种稳定排序,时间复杂度为O(nlogn)
template<typename E>
void merge(E A[], E temp[], int left, int right) {
if (left == right)return;
int mid = (left + right) / 2;
merge<E>(A, temp, left, mid);
merge<E>(A, temp, mid + 1, right);
for (int i = left; i <= right; i++) {
temp[i] = A[i];
}
int li = left;
int ri = mid + 1;
for (int curr = left; curr <= right; curr++) {
if (li == mid + 1)
A[curr] = temp[ri++];
else if (ri > right)
A[curr] = temp[li++];
else if (temp[li] < temp[ri])
A[curr] = temp[li++];
else A[curr] = temp[ri++];
}
}
template<typename E>
void mergeSort(E A[],int n) {
E *temp = new E[n];
for (int i = 0; i < n; i++) {
temp[i] = A[i];
}
merge(A, temp, 0, n - 1);
delete temp;
}
7、堆排序
堆排序的原理是首先根据数组建立一个最大或最小堆,最大(小)堆实际上就是一个元素大(小)的排在上面,是一个完全二叉树,然后通过不断地删除最大(小)节点并放到数组相应的位置上面,最后形成一个有序数组。
堆排序是一种不稳定排序,时间复杂的为O(nlogn)
template<typename E>
void heapCreate(E A[], int s,int m) {
int temp = A[s];
for (int j = 2 * s + 1; j <= m; j = 2 * j + 1) {
if (A[j] < A[j + 1] && j < m)j++;
if (temp > A[j])break;
A[s] = A[j];
s = j;
}
A[s] = temp;
}
template<typename E>
void heapSort(E A[],int n) {
for (int s = n / 2 - 1; s >= 0; s--) {
heapCreate(A, s, n - 1);
}
for (int i = n - 1; i >= 1; i--) {
swap(A, 0, i);
heapCreate(A, 0, i - 1);
}
}
8、基数排序
基数排序跟前面的排序算法不太一样,前面的排序算法都是基于比较并交换的原理进行排序,而基数排序不是,所以其实现不需要依赖不停的swap。原理是找到数组中最大元素的位数,然后先对最高位排序,然后再对下一位排序,直到个位该数组就排好序了,比如对一个最大元素是326的元素来说,先排百位,再拍十位,最后是个位。
基数排序是一种稳定排序,其时间复杂度受到数组元素稀疏性等的影响较大
template<typename E>
void radixSort(E A[],int n) {
int exp;
int max = 0;
for (int i = 0; i < n; i++) {
if (max < A[i])max = A[i];
}
for (exp = 1; max / exp > 0; exp *= 10) {
int *output = new int[n];
int i, buckets[10] = { 0 };
for (i = 0; i < n; i++) {
buckets[(A[i] / exp) % 10]++;
}
for (i = 1; i < 10; i++) {
buckets[i] += buckets[i - 1];
}
for (i = n - 1; i >= 0; i--) {
output[buckets[(A[i] / exp) % 10] - 1] = A[i];
buckets[(A[i] / exp) % 10]--;
}
for (i = 0; i < n; i++) {
A[i] = output[i];
}
delete output;
}
}
9、桶排序
桶排序的原理是对于一个最大值为max的数组,创建max个桶,并把数组相应的元素放到相应的桶中,比如说对于值为300的元素就放到编号为300的桶中,最后根据桶的排列就可以得出数组的排序结果。
桶排序也是一种稳定排序,其时间复杂度为O(n),但空间复杂度受元素稀疏性的影响很大,如果数组元素的值范围很大,其空间复杂度就会很大,是一种牺牲空间换时间的排序算法。
template<typename E>
void binSort(E A[],int n) {
int max = 0;
for (int i = 0; i < n; i++) {
if (max < A[i])max = A[i];
}
max++;
int *bucket = new int[max];
for (int i = 0; i < max; i++)bucket[i] = 0;
for (int i = 0; i < n; i++)bucket[A[i]] = 1;
int start = 0;
for (int i = 0; i < max; i++) {
if (bucket[i] != 0)A[start++] = i;
}
}