排序算法一般有下面几类常用的
基本排序算法
- 冒泡排序
- 插入排序
常考的
- 归并排序
- 快速排序
- 拓扑排序
其他排序算法
- 堆排序
- 桶排序
冒泡排序
每一轮,从杂乱的数组头部开始,每两个元素比较大小并交换,直到这一轮中最大活最小的元素被放置在数组的尾部。然后不断重复这个过程,直到所有元素排序好。
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
bool hasChange=true;
for(int i=0;i<nums.size()&&hasChange;i++){//有改变说明这一轮过后可能还没排完
hasChange=false;
for(int j=0;j<nums.size()-i-1;j++){
if(nums[j]>nums[j+1]){
int temp=nums[j];
nums[j]=nums[j+1];
nums[j+1]=temp;
}
hasChange=true;
}
}
return nums;
}
};
空间复杂度
O(1)
时间复杂度
- 已经排好序的数组:O(n)
- 完全逆序:O(n^2)
- 杂乱:平均复杂度:O(n^2)
稳定性:稳定
插入排序
不断将未排序的数插入到已经排好的部分
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
for(int i=1;i<nums.size();i++){
int current=nums[i];//current是待插入的元素
for(int j=i-1;j>=0&&nums[j]>current;j--){
nums[j+1]=nums[j];
}
nums[j+1]=current;
}
return nums;
}
};
时间空间复杂度与冒泡相同
归并排序
分治思想
把一个复杂的问题拆分成若干个子问题进行求解
算法思路
把数组从中间划分为两个子数组
一直递归地将数组划分为更小的子数组,直到数组里只有一个元素
依次按照递归的返回顺序,不断合并排序好的子数组,直到最后把整个数组排序完毕。
class Solution {
public:
void merge(vector<int>nums ,int lo, int mid, int hi) {
vector<int> copy = nums;
//k为合并后数组的下标
//i为左数组下标
//j为右数组下标
int k = lo, i = lo, j = mid + 1;
while (k <= hi) {
if (i > mid) {//左半边都已处理,复制右半边即可
nums[k++] = copy[j++];
}
else if (j > hi) {//右半边都已处理,复制左半边即可
nums[k++] = copy[i++];
}
else if (copy[j] < copy[i]) {//右边的数小于左边的数,拷贝右边的数
nums[k++] = copy[j++];
}
else//左边的数小于右边的数,就拷贝右边的数
nums[k++] = copy[i++];
}
}
void sort(vector<int> A, int lo, int hi) {
if (lo >= hi)return;
int mid = lo + (hi - lo) / 2;
//递归对左右部分排序
sort(A, lo, mid);
sort(A, mid + 1, hi);
merge(A, lo, mid, hi);
}
};
时间复杂度
对于规模为n的问题,一共要进行log(n)层大小切分;
每一层的合并复杂度都是O(n);
整体的复杂度就是O(nlogn)
空间复杂度 O(n)
稳定性 稳定
快速排序
基本算法思想
也采用分治思想;
把原始数组筛选成较大和较小的两个子数组,然后递归地排序两个子数组。
在分成较小和较大的俩个子数组过程中,如何选定一共基准值尤为关键。
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
sort(nums,0,nums.size()-1);
return nums;
}
int partition(vector<int> &nums, int lo, int hi) {
//随机选择一个值放到最右边
int randi= (rand() % (hi - lo + 1)) + lo;
swap(nums[randi], nums[hi]);
int i, j;
for (i = lo, j = lo; j < hi; j++) {
if (nums[j] <= nums[hi]) {
swap(nums[i++], nums[j]);//发现j小于基准值就换到左边
}
}
swap(nums[i], nums[j]);//将末尾的基准值放回i
return i;//返回基准点位置
}
void sort(vector<int> &nums, int lo, int hi) {
if (lo > hi)return;//只剩下一个元素
int p = partition(nums, lo, hi);//找基准点,基准值左边都小于这个数,基准值右边都大于这个数
sort(nums, lo, p - 1);
sort(nums, p + 1, hi);
}
};
最优情况下
即每次基准值都是中间数
T(n)=2*T(n/2)+O(n)
复杂度:O(nlogn)
最坏情况
每次选基准值的时候,都选择了最大或最小值,其中一个的子数组长度为1,另一个之比父数组少1,这样跟冒泡就差不多了。
复杂度:O(n^2)
空间复杂度:O(logn)
在递归过程中,只需要开辟O(1)的空间来交换数组
递归次数为logn,因此它的整体空间复杂度取决于压堆栈的次数。
最经典应用:寻找第k大的数。返回基准值就可以了。时间复杂度为O(n)
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
srand(time(NULL));
return find(nums, 0, nums.size() - 1, nums.size() - k);
}
int partition(vector<int>& nums, int lo, int hi) {
int randi = rand() % (hi - lo + 1) + lo;
swap(nums[randi], nums[hi]);
int i, j;
for (i = lo, j = lo; j < hi; j++) {
if (nums[j] <= nums[hi]) {
swap(nums[i++], nums[j]);
}
}
swap(nums[i], nums[hi]);
return i;
}
int find(vector<int> &nums, int lo, int hi, int k) {
int begin = partition(nums, lo, hi);
if (begin == k) return nums[begin];
if (begin < k) return find(nums, begin+1, hi, k);//这里+1很重要 不然无法处理元素相同的数组
if (begin > k) return find(nums, lo, begin-1, k);//-1很重要 同上
return -1;
}
};
拓扑排序
应用场合
要将图论里的顶点按照相连的性质进行排序
前提
必须是有向图
图没有环
例:
得到有向图:
找每个顶点的入度。看入度为0的点。作为输出结果。删除掉相应的有向边,改变相应节点的入度,再次输出入度为0的节点,消除相应边,重复此过程。
时间复杂度:O(n)
统计顶点的入度需要O(n)的时间
接下来每个顶点被遍历一次,也要O(n) 时间