快速排序 归并排序

思想
通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

partition函数
分割函数,快排的核心,思想就是在数组中找一个数作为中间值,把数组中比它小的放左边,大的放右边。这个函数有两种写法,单指针版和双指针版。

核心思想: 每次都取数组的第一个元素作为比较标准(哨兵元素),凡是大于这个哨兵元素的都放在它的右边,凡是小于这个哨兵元素的都放在它的左边;

  1. 以数组的第一个元素为哨兵元素,让其他元素和它比较大小;(记住这时候第一个元素位置是的,因为里面的值被作为哨兵元素保存起来了)

  2. 开始从数组尾部**(很重要)**往前循环得到一个小于哨兵元素的元素A ,把该元素A 放到第一个元素位置(也就是哨兵元素位置上,因为哨兵元素位置是空的,这时候要记住 元素A 的位置是空的了)

  3. 开始从数组头部往后循环得到一个大于哨兵元素的 元素B ,把该 元素B 放在上一步中移出的 元素A 的位置上;

  4. 依次循环上面2、3步,直到左右指针相遇,完成一次数组分割(此时左右两边分别是大于和小于基准的元素),然后将相遇的索引处 置为 哨兵。

//双指针方法
int quick_sort(vector<int> &nums,int left,int right){
    int tmp = nums[left];
    while(left<right){
        while (left<right&&nums[right]>tmp)  right--;
        nums[left] = nums[right];
        while (left<right&&nums[left]<=tmp) left++;
        nums[right] = nums[left];
    }
    nums[left] = tmp;
    return left;
}

这个图解快排很易懂

在这里插入图片描述

递归与非递归

  • 递归就是: 对当前数组进行一次分割后,继续递归对 左半部分和有半部分 进行相同的递归操作,知道数组大小减为1停止;
  • 非递归:使用一个栈保存 左右两边的下标,然后不断进行 进出栈 来获取左右边界,然后对每个 子数组 进行排序。
// 递归版本
// 结束:数组只有一个元素时,有序,结束
// 返回值: 已经有序的 子数组
// 处理函数:分割函数,对每次递归的子数组  进行分割排序 
void dfs(vector<int> &nums,int left,int right){
    if(left >= right) return ;
    int k = quick_sort(nums,left,right);
    dfs(nums,left,k-1);
    dfs(nums,k+1,right);
}

// 非递归版本  保存 左右边界索引信息
vector<int> Solution::sortArray(vector<int> &nums) {
    int len = nums.size();
    typedef pair<int,int> P;
    stack<P>  ms;
    ms.push(P(0,len-1));
    while(!ms.empty()){
        int left = ms.top().first;
        int right = ms.top().second;
        ms.pop();
        if(left >= right) continue;
        int k = quick_sort(nums,left,right);
        ms.push(P(left,k-1));
        ms.push(P(k+1,right));
    }

稳定性: 因为在快速排序的时候,即使待排序元素可基数相等也需要移动待排序元素的位置使得有序,所以快速排序是不稳定的。

4_ 0 3 4^ 2 5 --> 2 0 3 4

关于递归转循环需要知道的事情
通过归并排序和快速排序非递归实现的讲解,似乎将其转化为循环是一个更佳的做法,其实不然,它只适用于特定的场景。

关于这种方法,需要有如下的认知:

  • 递归的代码,在很多时候比循环的代码更加容易理解。
  • 递归转循环,在效率上并没有提高。相反,由于增加了构造堆栈模型的过程,其消耗的时间更多。
  • 只有当递归的层数过多,而导致 StackOverFlow 的问题出现,才考虑使用递归转循环的方法。
  • 可以通过调整 JVM 参数,来达到扩充堆栈空间的目的,但是一般不推荐这么做,因为这个影响是整体的。
  • 从代码的角度,如果循环能够解决问题,那么就使用循环;如果递归能解决问题,那么就使用递归,没有必要特意去做两者的转换。

归并排序

在这里插入图片描述


void merge(vector<int>& nums,int l,int m,int r){
    vector<int> ans;
    int p1=l,p2=m+1,tmp;

    while(p1<=m && p2<=r){
        int tmp = nums[p1]<nums[p2]?nums[p1++]:nums[p2++];
        ans.push_back(tmp);
    }
    while(p1<=m){
        ans.push_back(nums[p1++]);
    }
    while(p2<=r){
        ans.push_back(nums[p2++]);
    }

    for(int i=0;i<ans.size();i++)
        nums[i+l] = ans[i];
}

void sort(vector<int>& nums,int l,int r){
    if(l>=r) return ;
    int mid = l + (r-l)/2;
    sort(nums,l,mid);
    sort(nums,mid+1,r);
    merge(nums,l,mid,r);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值