912.排序数组(快排)

给你一个整数数组 nums,请你将该数组升序排列。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]
示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

快速排序是先将一个元素排好序,然后再将剩下的元素排好序

思路:快速排序的正确理解方式及运用 

先对数组nums进行随机洗牌,得到无序数组;然后开始快排(先序遍历、自顶向下),

用 partition 函数把 nums[p] 这个元素排到正确的位置(左边都比它小,右边都比它大——二叉搜

索树),然后对[low,p-1]、[p+1,high]这两个子数组进行递归,用 partition 函数把剩下的元素也

排好序。

随机洗牌:洗牌算法详解:你会排序,但你会打乱吗?

注意partition函数最后一次的交换    swap(nums,low,j);

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) 
    {
        shuffle(nums);//随机洗牌
        sort(nums,0,nums.size()-1);
        return nums;
    }
    void shuffle(vector<int>& nums)
    {
        srand(time(NULL));
        int n=nums.size();
        for(int i=0;i<n;i++)//产生n!种可能
        {
          int index=i+rand()%(n-i);//随机生成[i,n-1]范围内的一个数作为数组下标
          swap(nums,i,index);
        }
    }
    void swap(vector<int>& nums,int i,int index)
    {
        int tmp=0;
        tmp=nums[i];
        nums[i]=nums[index];
        nums[index]=tmp;
    }
    void sort(vector<int>& nums,int low,int high)
    {
        if(low>=high)
        return ;
        //对nums[low..high]进行切分,使得nums[low..p-1]<=nums[p]<nums[p+1..high]
        int p=partition(nums,low,high);
        sort(nums,low,p-1);
        sort(nums,p+1,high);
    }
    int partition(vector<int>& nums,int low,int high)
    {
        int pivot=nums[low];//以第一个元素作为枢轴
        int i=low+1,j=high;
        while(i<=j)
        {
            while(i<high&&nums[i]<=pivot)//此while结束时恰好nums[i]>pivot
            {
                i++;
            }
            while(j>low&&nums[j]>=pivot)//此while结束时恰好nums[j]<pivot
            {
                j--;
            }
            if(i>=j)//如果i>=j,跳出循环,j的位置就是pivot应该被放到的正确位置 
            {
                break;
            }
            swap(nums,i,j);//交换不符合枢轴左边小、右边大规则的两个元素
        }
        swap(nums,low,j); //将pivot放到合适的位置,即pivot左边元素小,右边元素大
        /*
            为什么说j指向的位置就是pivot应该放的位置?
            因为 j是从后往前找,找到的比枢轴小的元素,所以交换了nums[low]和nums[j]之后,
            就是将这个比枢轴小的元素nums[j]放到了数组的第一个位置,即在pivot的左边。
        */
        return j;//返回pivot的位置
    }
};

上述快排的核心思想是选择一个枢轴,让左边的都比枢轴小,右边的都比枢轴大。然后对以枢轴分开的两部分继续递归排序。

但是如果数组中有很多重复元素,那么快排会对一个都是重复元素的数组进行递归,其实这时就可以直接返回了,因为它已经是有序的了。为了达成这样的目的,下面是改进的快速排序算法——三向切分的快速排序

算法2.5.2快速排序(三向切分) - 知乎 (zhihu.com)

三向切分即有三个指针。lt 为低指针,gt为高指针,i为当前遍历元素。

首先仍然选取数组的第一个元素为枢轴 v =nums[low]。

接下来要利用这三个指针和 nums [ low .... high ] 中标识数组范围的low,high这两个指针,把数组arr分成以下三个部分。

1、nums[ low]~nums[ lt-1]为小于v的元素。

2、nums[ lt]~nums[ gt]为等于v的元素。

3、nums[ gt+1]~nums[ hi]为大于v的元素。

最后在遍历的过程中:nums[ i]~nums[ gt]为还没处理的元素。

通过这样的优化,我们把重复元素集中起来,无需对重复元素的数组进行递归,减少了递归的次数,提高了算法效率。而标准快排的算法仅仅是从前往后找到一个比枢轴大的,从后往前找一个比枢轴小的,然后交换它们。

class Solution {
public:
    void shuffle(vector<int>& nums)//随机打乱
    {
        srand(time(NULL));
        int n=nums.size();
        for(int i=0;i<n;i++)
        {
            int index=i+rand()%(n-i);//生成i~n-1的随机下标
            swap(nums[index],nums[i]);
        }
    }
    vector<int> sortArray(vector<int>& nums) {
        shuffle(nums);
        int n=nums.size();
        quickSort(nums,0,n-1);
        return nums;
    }
    void quickSort(vector<int>& nums,int low,int high)
    {
        if(low>=high)//base case
            return ;
        int pivot=nums[low];//以第一个元素为枢轴
        int lt=low,gt=high;
        int i=low+1;
        while(i<=gt)//nums[i..gt]是还未处理的元素
        {
            if(nums[i]<pivot)//当前元素比枢轴小,所以要放到lt左边
            {
                swap(nums[i],nums[lt]);//先和nums[lt]交换
                lt++;//lt向右移动
                i++;//i向右移动
            }
            else if(nums[i]>pivot)//当前元素比枢轴大,所以要放到gt右边
            {
                swap(nums[i],nums[gt]);//先和nums[gt]交换
                gt--;//gt向左移动
                //这里为什么i不向右移动了呢?
                //因为刚刚交换过来的nums[gt]与枢轴的大小还没有判断过,所以i不能向右移动
            }
            else//当前元素和枢轴一样大,因为在lt~gt之间的是等于枢轴的元素,所以不移动nums[i]
            {
                i++;//i向右移动
            }
        }
        quickSort(nums,low,lt-1);//排序小于枢轴的元素部分
        quickSort(nums,gt+1,high);//排序大于枢轴的元素部分
        //nums[lt...gt]中等于枢轴的元素部分无需排序,已经在正确的位置上了
    }
};

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值