比较类排序:
平均时间复杂度,最坏时间复杂度,最好时间复杂度。
稳定性:是否改变相对位置。针对a==b,本来a在b前,不稳定可能排序后a在b后。
1. 冒泡排序:
每次两两交换将较大(较小)放后面。
平均O(n2),最坏O(n2),最好(已排序好)O(n)。稳定
void bubblesort(vector<int> &nums){
for(int i=0;i<nums.size();++i){
for(int j=0;j<nums.size()-i-1;++j){
if(nums[j]>nums[j+1])
swap(nums[j],nums[j+1]);
}
}
}
2. 选择排序
每次选择最大(最小)值放至最后(最前).
平均O(n2),最坏O(n2),最好O(n2)。不稳定
void selectionsort(vector<int> &nums){
for(int i=0;i<nums.size();++i){
int maxn=0;
for(int j=0;j<nums.size()-i;++j){
if(nums[j]>nums[maxn]){
maxn=j;
}
}
swap(nums[maxn],nums[nums.size()-i-1]);
}
}
3. 插入排序
将未排序部分首位从后向前搜索插入已排序部分。
平均O(n2),最坏O(n2),最好O(n)。稳定
void insertionsort(vector<int> &nums){
for(int i=0;i<nums.size();++i){
int tmp=nums[i],idx=i-1;
while( idx>=0 && tmp<nums[idx]) {
nums[idx+1]=nums[idx];
idx--;
}
nums[idx+1]=tmp;
}
}
4. 希尔排序
插入排序的改进版。将待排序数组分为若干步长的子数组,对子数组进行插入排序。
平均O(n1.3),最坏O(n2),最好O(n)。不稳定:由于不同子数组的插入排序,导致之间稳定性破坏。
void shellsort(vector<int> &nums){
for(int gap=nums.size()/2;gap>0;gap/=2){
for(int i=0;i<nums.size();++i){
int tmp=nums[i],idx=i-gap;
while( idx>=0 && nums[idx]>tmp){
nums[idx+gap]=nums[idx];
idx-=gap;
}
nums[idx+gap]=tmp;
}
}
}
5. 归并排序
分治法。 对每个子序列排序,合并子序列。
分治法:平均O(nlogn),最坏O(nlogn),最好O(nlogn)。稳定。
空间复杂度O(n)。空间换时间。
vector<int> merge(vector<int> l, vector<int> r) {
vector<int> result;
while (l.size() > 0 && r.size() > 0) {
if (l[0] < r[0]) {
result.push_back(l[0]);
l.erase(l.begin());
}
else {
result.push_back(r[0]);
r.erase(r.begin());
}
}
if (l.size() > 0) result.insert(result.end(), l.begin(), l.end());
else result.insert(result.end(), r.begin(), r.end());
return result;
}
vector<int> mergesort(vector<int> &nums) {
int len = nums.size();
if (len < 2) return nums;
int mid = len / 2;
vector<int> left = vector<int>(nums.begin(), nums.begin()+mid);
vector<int> right = vector<int>(nums.begin()+mid, nums.end());
return merge(mergesort(left), mergesort(right));
}
6. 快速排序
分治法。选择基准将其调整至正确位置,然后分别对左右递归排序。
平均O(nlogn),最坏O(n2),最好O(nlogn)。不稳定.
空间O(nlogn)
int partition(vector<int> &nums,int left,int right){
int pivot=left,index=left+1;
for(int i=left+1;i<=right;++i){
if(nums[i]<nums[pivot]){
swap(nums[i],nums[index++]);
}
}
swap(nums[pivot],nums[index-1]);
return index-1;
}
// or
int partition(vector<int> &nums,int left,int right){
int pivot=nums[left];
while(left<right){
while(left<right&&nums[right]>=pivot) right--;
if(left<right) nums[left++]=nums[right];
while(left<right&&nums[left]<=pivot) left++;
if(left<right) nums[right--]=nums[left];
}
nums[left]=pivot;
return left;
}
void quicksort(vector<int> &nums,int left,int right){
if(left<right){
int mid=partition(nums,left,right);
quicksort(nums,left,mid-1);
quicksort(nums,mid+1,right);
}
}
7. 堆排序
使用堆节点保存数据以完成排序。堆是近似完全二叉树的结构,满足子节点的数值小于(大于)父节点。
大顶堆:父节点值大于等于子节点值,用于升序排列(用于易取最大值放最后)
小顶堆:父节点值小于等于子节点值,用于降序排列
平均O(nlogn),最坏O(nlogn),最好O(nlogn)。不稳定.
空间O(1)。
// 调整最大堆
void max_heapify(vector<int> &nums,int start,int end){
int dad=start;
int son=dad*2+1;//左子节点
while(son<=end){
if(son+1<=end&&nums[son]<nums[son+1])
son++;// 先比较两个子节点选择最大的
if(nums[son]<nums[dad]) return;// 如果子节点都比父节点小,无需调整,退出循环
else{// 否则交换父子节点。此时原本父节点调整为子节点可能影响子节点所在子树的结构,重新调整子节点子树(将子节点作为新的父节点)。
swap(nums[son],nums[dad]);
dad=son;
son=dad*2+1;
}
}
}
void heapsort(vector<int> &nums,int len){
// 将原数组构建为大顶堆
for(int i=len/2-1;i>=0;--i)
// 从最后一个非叶子节点调整结构
max_heapify(nums,i,len-1);
// 交换堆顶与末尾元素+调整剩余区间堆结构
for(int i=len-1;i>0;--i){
swap(nums[0],nums[i]);
max_heapify(nums,0,i-1);// 因为与头结点交换了位置,所以改变了头结点所在子树的结构
}
}