引言
排序中主要包含数据元素的比较和交换,本文以C++实现以下七种排序算法,以从小到大排序为例。
如有错误,万望指正。笔者的邮箱为:wuxiaofang555555@163.com 。本文的代码亦可以从笔者的GitHub上获取:https://github.com/wuerfang/Sort/tree/master。
-
排序分类一
- 简单算法:
冒泡排序
、简单选择排序
、直接插入排序
- 改进算法:
希尔排序
、堆排序
、归并排序
、快速排序
- 简单算法:
-
排序分类二
-
复杂度对比
排序方法 平均情况 最好情况 最坏情况 辅助空间 稳定性 冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1) 稳定 简单选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1) 稳定 直接插入排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1) 稳定 希尔排序 O ( n l o g n ) O(nlogn) O(nlogn)~ O ( n 2 ) O(n^2) O(n2) O ( n 1.3 ) O(n^{1.3}) O(n1.3) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1) 不稳定 堆排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( 1 ) O(1) O(1) 不稳定 归并排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n ) O(n) 稳定 快速排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n 2 ) O(n^2 ) O(n2) O ( l o g n ) O(logn) O(logn)~ O ( n ) O(n) O(n) 不稳定 -
交换函数
swap
void swap(vector<int> &v, int i, int j) { int temp = v[i]; v[i] = v[j]; v[j] = temp; }
冒泡排序
冒泡排序:两两比较相邻记录的关键字,如果反序则交换,直到没有反序。较小的数字如同气泡般慢慢浮到上面(或是较大的数字慢慢沉到下面)。
时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),有三种实现方法,如下所示。
-
BubbleSort0
(不是真正意义上的冒泡排序)void BubbleSort0(vector<int> &v) { for (int i = 0; i < v.size(); ++i) { for (int j = i + 1; j < v.size(); ++j) { if (v[i] > v[j]) swap(v, i, j); } } }
-
BubbleSort
(正宗的冒泡排序)void BubbleSort(vector<int> &v) { for (int i = 0; i < v.size(); ++i) { for (int j = v.size() - 1; j > i; --j) { if (v[j - 1] > v[j]) swap(v, j - 1, j); } } }
-
BubbleSort2
(优化的冒泡排序方法)void BubbleSort2(vector<int> &v) { bool flag = true; for (int i = 0; i < v.size() && flag; ++i) { flag = false; for (int j = v.size() - 1; j > i; --j) { if (v[j - 1] > v[j]) { swap(v, j - 1, j); flag = true; } } } }
简单选择排序
选择排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2),但性能略优于冒泡排序,因为选择排序交换次数少
-
SelectionSort
void SelectionSort(vector<int> &v) { int min; for (int i = 0; i < v.size(); ++i) { min = i; for (int j = i + 1; j < v.size(); ++j) { if (v[min] > v[j]) min = j; } if (min != i) swap(v, i, min); } }
直接插入排序
插入排序的基本思想是将待排序的元素逐个插入已经安排好的序列中,时间复杂度为 O ( n 2 ) O(n^2) O(n2),但性能略优于冒泡排序和选择排序。
-
InsertSort
(两种实现方式)//方式一 void InsertSort(vector<int> &v) { for (int i = 1, j; i < v.size(); ++i) { int t = v[i]; //待插入的元素 for (j = i; j > 0; --j) { //查找插入的位置 if (v[j-1] < t) break; v[j] = v[j-1]; //逐个向后移动元素 } v[j] = t; //将待插入的元素放入正确的位置 } }
//方式二 void InsertSort(vector<int> &v) { for (int i = 1, j; i < v.size(); ++i) { if (v[i] < v[i-1]) { //判断待插入元素是否移动位置 int t = v[i]; //待插入的元素 for (j = i; j > 0 && v[j - 1] > t; --j) { //查找插入的位置 v[j] = v[j - 1]; //逐个向后移动元素 } v[j] = t; //将待插入的元素放入正确的位置 } } }
希尔排序
希尔排序是对插入排序的一种改进,其将排序的元素分组,分别使用插入排序方法后得到基本有序的排列,时间复杂度为 O ( n 3 / 2 ) O(n^{3/2}) O(n3/2)
- 排序原理
-
ShellSort
void ShellSort(vector<int> &v) { int gap = v.size(); do { gap = gap / 3 + 1; //gap = gap / 2; for (int i = gap, j; i < v.size(); ++i) { int t = v[i]; for (j = i - gap; j >= 0; j -= gap) { if (v[j] < t) break; v[j + gap] = v[j]; } v[j + gap] = t; } } while (gap > 1); }
堆排序
堆排序是对简单选择排序的一种改进,其利用堆进行排序,将待排序的数据构造成一个大顶堆,此时整个序列最大的最大值就是堆顶的根结点,将其移走(即将其与数组尾元素交换),此时末尾元素就是最大值,然后再将前n-1个数据进行重新构造成一个大顶堆,依次进行。 时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
- 排序原理
-
HeapSort
void HeapSort(vector<int> &v) { for (int i = v.size() / 2 - 1; i >= 0; --i) { //构建大顶堆(利用了完全二叉树的概念) HeapAdjust(v, i, v.size() - 1); } for (int i = v.size() - 1; i > 0; --i) { swap(v, 0, i); //将堆顶元素与当前未经排序的尾元素交换 HeapAdjust(v, 0, i - 1); //将子数组v[0...i-1]重新调整为大顶堆 } }
-
HeapAdjust
void HeapAdjust(vector<int> &v, int s, int m) { int temp = v[s]; for (int j = 2 * s + 1; j <= m; j = 2 * j + 1) {//完全二叉树中左节点为2s+1,右节点为2s+2 if (j < m && v[j] < v[j + 1]) j++; if (v[j] < temp) break; v[s] = v[j]; s = j; } v[s] = temp; }
归并排序
归并排序就是利用归并的思想实现的排序方法,其假设初始序列含有n个元素,则可以堪称是n个有序的子序列,每个子序列的长度为1,然后两两合并,得到n/2个长度为2或1 的有序序列,再两两合并,…,如此重复,亦称谓2路归并排序。 时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。
- 排序原理
-
MergeSort
void MergeSort(vector<int> &v) { MSort(v, v, 0, v.size() - 1); }
-
MSort
void MSort(const vector<int> &v, vector<int> &v1, int s, int t) { int m; vector<int> v2(v.size()); if (s == t) { v1[s] = v[s]; } else { m = (s + t) / 2; MSort(v, v2, s, m); //递归将v[s...m]归并为有序的v2[s...m] MSort(v, v2, m + 1, t); //递归将v[m+1...t]归并为有序的v2[m+1...t] Merge(v2, v1, s, m, t); //将有序的v2[s...m]和v2[m+1...t]归并为v1中 } }
-
Merge
void Merge(const vector<int> &v, vector<int> &v1, int s, int m, int t) { int j, k; for (j = m + 1, k = s; s <= m && j <= t; ++k) { if (v[s] < v[j]) v1[k] = v[s++]; else v1[k] = v[j++]; } if (s <= m) { for (; s <= m; ++s, ++k) v1[k] = v[s]; } if (j <= t) { for (; j <= t; ++j, ++k) v1[k] = v[j]; } }
快速排序
快速排序是对冒泡排序的一种改进,其通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行。 时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
- 排序原理
-
QuickSort
void QuickSort(vector<int> &v) { QSort(v, 0, v.size() - 1); }
-
QSort
//对子数组进行快速排序 void QSort(vector<int> &v, int low, int high) { int pivot; //枢轴 if (low < high) { pivot = Partition(v, low, high); //将v[low...high]一分为二,并计算出枢轴值pivot QSort(v, low, pivot - 1); //对低子数组递归排序 QSort(v, pivot + 1, high); //对高子数组递归排序 } }
-
Partition
//交换待排序数组中的顺序,是位于枢轴pivot左边的值都小于该值,使其右边的值都大于该值 int Partition(vector<int> &v, int low, int high) { int pivotvalue = v[low]; while (low < high) { while (low < high&&v[high] >= pivotvalue) high--; swap(v, low, high); while (low < high&&v[low] <= pivotvalue) low++; swap(v, low, high); } return low; }