分治算法
快速排序本质上是一种分治算法。分治算法的思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,然后分而治之。
分治策略:对于一个规模为n的问题,若该问题可以容易的解决,则直接解决,否则将其分为k个规模较小的子问题。这些子问题互相独立并且与原问题形式相同,递归地解决这些子问题,然后将各子问题地解合并得到原问题地解。这种算法设计策略叫做分治法。
分治法基本步骤
- 分解:将原问题分解为若干个规模较小、相互独立、与原问题形式相同的子问题
- 解决:若子问题规模较小而容易被解决掉,则直接解决,否则递归的解决各个子问题。
- 合并:将各个子问题的解合并为原问题的解。
快速排序
将一组要排序的数据分割成独立地两个部分,其中一部分所有数据都比另一个部分的所有数据小,然后按照这个方法对这两个部分再进行快速排序,整个过程递归进行,最后使数据变成有序。
步骤
- 分解:从数列中选出一个元素作为基准元素,使基准元素左边的数据小于等于基准元素,右边的大于基准元素。
- 治理:对两个子序列快速排序
- 合并:将排好的子序列合并
基准元素选取
- 选取第一个或者最后一个元素:数组已经有序的时候是一个效率很低的方法
- 随机选一个数
- 选中间的数
- 三数取中:取第一个数、最后一个数和中间数中数值中间的那个数
快速排序代码
class Solution {
public:
void quickSort(vector<int>&nums,int left,int right){
int mid=nums[(left+right)/2];
int i=left;
int j=right;
while(i<=j){
while(nums[i]<mid) i++;
while(nums[j]>mid) j--;
if(i<=j){
swap(nums[i],nums[j]);
i++;
j--;
}
}
if(left<j) quickSort(nums,left,j);
if(i<right) quickSort(nums,i,right);
}
vector<int> sortArray(vector<int>& nums) {
quickSort(nums,0,nums.size()-1);
return nums;
}
};
运行结果
优化
- 当待排序序列的长度分割到一定大小之后,使用插入排序
- 在依次排序之后,可以将与基准值相等的数放在一起,下次分割的时候不考虑这些数。
- 优化尾递归
复杂度和稳定性
时间复杂度
最好情况
每次划分所选的关键数据为所在序列的中位数,经过
log 2 n \log_2n log2n 趟划分就可以得到长度为1的子序列,时间复杂度为
O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
最坏情况
每次划分所选择的关键数据为所在序列最大或者最小,需要n趟划分
O ( n 2 ) O(n^2) O(n2)
平均时间复杂度
O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
空间复杂度
最优
每一次都平分数组
O ( l o g n ) O(logn) O(logn)
最差
退化为冒泡排序
O ( n ) O(n) O(n)
稳定性
参考深刻剖析快速排序为什么不稳定
不稳定
不稳定的原因
- 选取基准数字时随机选择
- 在遍历过程中,将小于基准的数字与第一个大于基准的数字位置交换
- 在每次遍历完后,将头部的基准数字与最后一个小于基准的数字交换位置
使算法稳定
使用额外辅助空间,此时空间复杂度将会变成O(logn+n)