默认都是从小到大排序。有必要注释。
总结
分类 | 时间 | 空间 | 稳定性 |
---|---|---|---|
选择 | 好、坏、平均都是O(n^2) | O(1) | 不稳定 |
插入 | 平均、坏O(n^2),好O(n) | O(1) | 稳定 |
冒泡 | 平均、坏O(n^2),好O(n) | O(1) | 稳定 |
快排 | 平均、好O(nlogn),坏O(n^2) | O(logn) | 不稳定 |
堆 | 好、坏、平均都是O(nlogn) | O(1) | 不稳定 |
归并 | 好、坏、平均都是O(nlogn) | O(n) | 稳定 |
选择排序
思想
每次从未排序集合中选出最大的,与未排序集合末端元素交换,直到未排序集合空。
cpp实现
void select_sort(vector<int>& arr){
for(int len = arr.size(); len > 0; --len){//len表示未排序集合大小
int max = 0;//max用来记录未排序集合中最大数的下标
for(int i = 0; i < len; ++i){
if(arr[i]>arr[max]) max = i;
}
swap(arr[max], arr[len-1]);//将最大数与未排序集合末端元素交换
}
}
主要优点
数据移动次数。
若某个元素位于正确的最终位置上,则该元素不会被移动。
复杂度分析
一般是说不稳定。(本实现也是。不过,对于数组,把本实现中的内层for循环倒过来,即从后往前找最大,就稳定了…)
空间O(1) 原地操作(优点);
时间O(n^2) 好、坏、平均都一样。
主要受比较次数制约:n(n-1)/2,与初始状态无关。
移动(交换)次数最好0次,最多n-1次。
与冒泡比较:n值较小时,选择排序快于冒泡。
因为冒泡移动次数为n^2,一般来说,一次移动(交换)时间长于一次比较时间。
插入排序
思想
将无序区间中的元素依次插入有序区间中的适当位置,直到无序区间长度变为零。
cpp实现
void insert_sort(vector<int>& arr){
for(int len = 1; len < arr.size(); ++len){
int i = 0;
while(i<len && arr[len]>arr[i]) ++i;
if(i<len){
int t = arr[len];
for(int j = len; j > i; --j) arr[j] = arr[j-1];
arr[i] = t;
}
}
}
快排
常考中的常考,要会手写,且做到快速、无bug。
思想
白话:“挖洞填数+分治”
(0)首先选择哨兵元素,将该元素“挖”出来赋给x,并将该位置作为第一个“洞”(同一时间只会存在一个“洞”)。本实现选择左界元素为哨兵。
(1)先从右往左,找比哨兵x小的元素a,将其“挖”出,填入“洞”中,此时a的位置变成了新的“洞”。
(2)再从左往右,找比哨兵x大的元素b,将其“挖”出,填入“洞”中,此时b的位置变成了新的“洞”。
(3)重复执行(1-2)直到左右相遇,此时相遇位置即最后一个“洞”就是哨兵元素x最终有序位置。
(4)将哨兵元素x最终有序位置左右分别进行快排,即分治,直到当前区段长度为零。
cpp实现
void quick_sort(vector<int>& arr, int l, int h){
if(l>=h) return;
int i = l, j = h;
int x = arr[l];
while(i<j){
while(i<j && arr[j]>=x) --j;
if(i<j) arr[i++] = arr[j];
while(i<j && arr[i]<=x) ++i;
if(i<j) arr[j--] = arr[i];
}
arr[i] = x;
quick_sort(arr, l, i-1);
quick_sort(arr, i+1, h);
}