最近在慢慢梳理,对于常见的一些排序算法,简单的例如冒泡,插入,选择这种就不写了,这里写三种感觉有可能问到的:堆排,归并,快排

1.堆排

简单总结下堆排序的基本思路:

  a.将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

  b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

  c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

其中建堆的时间复杂度为O(n),而排序时候与二叉树类似,它的最坏,最好,平均时间复杂度均为O(nlogn),总体还是为O(nlogn)

#include <bits/stdc++.h>
using namespace std;

void adjust(vector<int>& nums,int size,int idx){ //堆的调整
    int left=idx*2+1;
    int right=idx*2+2;
    int max_idx=idx; //选定的最大节点的序号
    if(left<size && nums[left]>nums[max_idx]){
        max_idx=left;
    }
    if(right<size && nums[right]>nums[max_idx]){
        max_idx=right;
    }
    if(max_idx!=idx){ //如果此时进行过改变了
        swap(nums[max_idx],nums[idx]);
        adjust(nums,size,max_idx); //此时是需要进行调整子树中的所有节点,使得仍然满足开始时堆的条件
    }
}

void heap_sort(vector<int>& nums,int size,int idx){
    for(int i=idx;i>=0;--i){ //从后往前进行
        adjust(nums,size,i);
    }
    for(int i=size-1;i>=1;--i){ //因为至少要有1个节点,因此循环到i=1即停止
        swap(nums[i],nums[0]); //最大元素沉底
        adjust(nums,i,0); //此时进行的size大小需要修改为i,并且从根开始进行调整堆
    }
}



int main(){
    vector<int> nums{1,224,5,6,54,4,61,9,11,14,18};
    int size=nums.size();
    int idx=size/2-1; //这是从后往前的第一个非叶子节点的坐标
    heap_sort(nums,size,idx);
    for(int i=0;i<size;++i){
        cout<<nums[i]<<endl;
    }
    system("pause");
    return 0;
}

2.归并

典型的递归回溯,leetcode上也有很多类似题,基本思路为二分到只剩1个后进行合并,然后反着合并直到整体有序

总的时间复杂度O(nlogn),其中merge的合并时间复杂度为O(n),并且基归冒插,这是稳定的排序方法

class Solution{
public:
    int Merge_sort(vector<int>& nums,int first,int last){
    	if(first<last){
    		int mid=(last+first)/2;
    		Merge_sort(nums,first,mid);//左边有序
		Merge_sort(nums,mid+1,last);//右边有序
		merge(nums,first,mid,last);//将两个有序的数组进行合并 
	}
    }
    void merge(vector<int>& nums,int first,int mid,int last){
    	vector<int> res(last-first+1,0);//我每次在合并之前先申请相应长度的数组来存储
    	int l=first,r=mid+1,k=0;
    	while(l<=mid && r<=last){
    		if(nums[l]<=nums[r]){
    			res[k++]=nums[l++];
		}
		else{
			res[k++]=nums[r++];
		}
	}
	while(l<=mid){
		res[k++]=nums[l++];
	}
	while(r<=last){
		res[k++]=nums[r++];
	}
	//用合并完成的数组来覆盖原数组的值 
	for(int i=0;i<res.size();i++){
		nums[i+first]=res[i];
	    }
	}
};

3.快排

跟归并其实有点类似,也是递归进行。

基本思想就是挖坑+填坑,一般选取第一个数为基准。之后i,j从两头往中间走,首先观察j的一侧是否值比target小,如果是的话填到第一个位置去,之后从i开始走,找第一个大于target的值,填到前面j空出的位置,结束循环后,target填到最终间的位置,然后左右两部分递归进行,直到整体有序。

void quick_sort(int s[], int l, int r){
    if (l < r){
    //Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换(如果要求以中间数作为基准数的话)
    int i = 0, j = r, x = s[0];
    while (i < j){
        while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
            j--;
        if(i < j)
            s[i++] = s[j];
        while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
            i++;
        if(i < j)
            s[j--] = s[i];
    }
    s[i] = x;
    quick_sort(s, l, i - 1); // 递归调用
    quick_sort(s, i + 1, r);
    }
}

4.基数排序 

再加上一个base_sort吧,基本思想为计数+放桶,从低位开始进行比较排序,之后一直往高位走,并且排序的次数是和最大值有关的,也就是和位数有关