给你一个整数数组 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]中等于枢轴的元素部分无需排序,已经在正确的位置上了
}
};