排序算法总结
冒泡排序:从无序区通过交换找出最大元素放到有序区前端。
1 void BubbleSort(vector<int> &v) { 2 int n = v.size(); 3 for (int i = 0; i < n - 1; ++i) 4 for (int j = 0; j < n - 1 - i; ++j) 5 if (v[j] > v[j + 1]) 6 swap(v[j], v[j + 1]); 7 }
选择排序:从未排序序列中找到最小(大)元素,存到已排序序列的起始位置。
1 void SelectSort(vector<int> &v) { 2 int n = v.size(); 3 for (int i = 0; i < n - 1; ++i) { 4 min = i; 5 for (int j = i + 1; j < n; ++j) { 6 if (v[j] < v[min]) 7 min = j; 8 } 9 if (i != min) 10 swap(v[j], v[min]); 11 } 12 }
插入排序:
1. 从第一个元素开始,该元素可以认为已经被排序
2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5. 将新元素插入到该位置后
6. 重复步骤2~5
1 void InsertSort(vector<int> &v) { 2 int n = v.size(); 3 for (int i = 1; i < n; ++i) { 4 int tmp = v[i]; 5 for (int j = i - 1; j >= 0; --j) { 6 if (v[j] > tmp) { 7 v[j + 1] = v[j]; 8 v[j] = tmp; 9 } else break; 10 } 11 } 12 }
快速排序:(小数,基准元素,大数)。在区间中随机挑选一个元素作基准,将小于基准的元素放在基准之前,大于基准的元素放在基准之后,再分别对小数区与大数区进行排序。
1 void QuickSort(vector<int> &v, int left, int right) { 2 if (left >= right) return; 3 int i = left + 1, j = right, key = v[left]; 4 while (i < j) { 5 while (v[i] < key) 6 i++; 7 while (v[j] > key) 8 j--; 9 if (i < j) 10 swap(v[i++], v[j--]); 11 else i++; 12 } 13 swap(v[j], v[left]); 14 QuickSort(v, left, j - 1); 15 QuickSort(v, j + 1, right); 16 }
归并排序:归并排序是将数组分成两半,这两半分别排序后,再归并在一起。排序某一半时,继续沿用
同样的排序算法,最终,你将归并两个只含一个元素的数组。
1 void Merge(vector<int> &v, int left, int mid, int right) { 2 int i = left, j = mid + 1; 3 vector<int> help(right - left + 1, 0); 4 for (int k = 0; k < right - left; ++k) 5 help[k] = v[left + k]; 6 for (int k = 0; k < rigfht - left; ++k) { 7 if (i > mid) v[left + k] = help[j++]; 8 else if (j > right) v[lqft + k] = help[i++]; 9 else if (help[j - left] < help[i - left]) v[left + k] = help[j++ - left]; 10 else v[left + k] = help[i++ - left]; 11 } 12 } 13 void MergeSort(vector<int> &v, int l, int r) { 14 if (r <= l) return; 15 int mid = l + (r - l) / 2; 16 MergeSort(v, l, mid); 17 MergeSort(v, mid + 1, r); 18 merge(v, l, mid, r); 19 }
希尔排序:每一轮按照事先决定的间隔进行插入排序,间隔会依次缩小,最后一次一定要是1。
1 void ShellSort(vector<int> &v, int length) { 2 int h = 1; 3 while (h < length / 3) { 4 h = 3 * h + 1; 5 } 6 while (h >= 1) { 7 for (int i = h; i < length; i++) { 8 for (int j = i; j >= h && v[j] < v[j - h]; j -= h) { 9 swap(a[j], a[j - h]); 10 } 11 } 12 h = h / 3; 13 } 14 }
非比较排序算法:
堆排序:堆是一种特殊的树形数据结构,即完全二叉树。堆分为大根堆和小根堆,大根堆为根节点的值大于两个子节点的值;小根堆为根节点的值小于两个子节点的值,同时根节点的两个子树也分别是一个堆。
- 步骤一:建立大根堆--将n个元素组成的无序序列构建一个大根堆,
- 步骤二:交换堆元素--交换堆尾元素和堆首元素,使堆尾元素为最大元素;
- 步骤三:重建大根堆--将前n-1个元素组成的无序序列调整为大根堆
重复执行步骤二和步骤三,直到整个序列有序。
计数排序:计数排序基于一个假设,待排序数列的所有数均为整数,且出现在(0,k)的区间之内。如果 k(待排数组的最大值) 过大则会引起较大的空间复杂度,一般是用来排序 0 到 100 之间的数字的最好的算法,但是它不适合按字母顺序排序人名。计数排序不是比较排序,排序的速度快于任何比较排序算法。时间复杂度为 O(n+k),空间复杂度为 O(n+k)。
桶排序:
- 将待排序元素划分到不同的桶。先扫描一遍序列求出最大值 maxV 和最小值 minV ,设桶的个数为 k ,则把区间 [minV, maxV] 均匀划分成 k 个区间,每个区间就是一个桶。将序列中的元素分配到各自的桶。
- 对每个桶内的元素进行排序。可以选择任意一种排序算法。
- 将各个桶中的元素合并成一个大的有序序列。
- 假设数据是均匀分布的,则每个桶的元素平均个数为 n/k 。假设选择用快速排序对每个桶内的元素进行排序,那么每次排序的时间复杂度为 O(n/klog(n/k)) 。总的时间复杂度为 O(n)+O(m)O(n/klog(n/k)) = O(n+nlog(n/k)) = O(n+nlogn-nlogk 。当 k 接近于 n 时,桶排序的时间复杂度就可以金斯认为是 O(n) 的。即桶越多,时间效率就越高,而桶越多,空间就越大
基数排序:
1. 将所有待排序整数(注意,必须是非负整数)统一为位数相同的整数,位数较少的前面补零
2. 从最低位开始,依次进行一次稳定排序。这样从最低位一直到最高位排序完成以后,整个序列就变成了一个有序序列。
为什么同一数位的排序子程序要用稳定排序?因为稳定排序能将上一次排序的成果保留下来。
排序算法 | 平均时间复杂度 | 最差时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
冒泡排序 | O(n2) | O(n2) | O(1) | 稳定 | |
选择排序 | O(n2) | O(n2) | O(1) | 数据不稳定,链表稳定 | |
插入排序 | O(n2) | O(n2) | O(1) | 稳定 | 不适合对于数据量比较大的排序应用 |
快速排序 | O(nlog2n) | O(n2) | O(log2n) | 不稳定 | |
归并排序 | O(nlog2n) | O(nlog2n) | O(n) | 稳定 | |
希尔排序 | O(nlog2n) | O(n2) | O(1) | 不稳定 | |
堆排序 | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 | |
计数排序 | O(n+m) | O(n+m) | O(n+m) | 稳定 | 最大值和最小值差距尽可能小 |
桶排序 | O(n) | O(n) | O(m) | 稳定 | 元素尽可能均匀分布 |
基数排序 | O(kn) | O(n2) | 稳定 | 非负整数,最大值和最小值的差距尽可能小 |
其中m代表数据最大值减去最小值。