前言
快速排序算法是必须掌握的一种基础算法,在一些比较出名的竞赛acm、蓝桥杯,并且在一些公司面试题中都可能会出现,而且作为简单题我们必须要拿下,所以我们要引起重视,下面让我们来深入了解归并快速算法。
一、什么是快速排序
快速排序(Quick Sort)是一种高效的排序算法,采用分治法(Divide and Conquer)策略来把一个序列分为较小和较大的两个子序列,然后递归地排序两个子序列。
二、算法步骤
1.选择基准(Pivot):
从数组中选择一个元素作为基准。通常选择第一个元素、最后一个元素、中间元素或随机元素作为基准。
2.分区(Partitioning):
重新排列数组,所有比基准小的元素摆放在基准的前面,所有比基准大的元素摆在基准的后面(相同的数可以到任何一边)。在这个分区退出之后,该基准就处于数组的中间位置。
递归排序子数组:
递归地将小于基准值元素的子数组和大于基准值元素的子数组排序。
三、算法思想
第一步、选择基准元素:从数组中选择一个元素作为基准。
第二步、分割数组:将比基准小的元素放在基准的左边,比基准大的元素放在基准的右边。
第三步、递归排序:对基准左边和右边的子数组分别进行快速排序。
重复步骤 一 至 三,直到子数组的长度为 1 或 0。
四、算法分析
时间复杂度
最优情况:当每次选择的基准元素正好将数组分成两等分时,快速排序的时间复杂度是 O(nlogn)。
最坏情况:当每次选择的基准元素是最大或最小元素时,快速排序的时间复杂度是 O(n^2)。
平均情况:在平均情况下,快速排序的时间复杂度也是 O(nlogn)。
空间复杂度
快速排序的空间复杂度是 O(logn),因为在递归调用中需要使用栈来存储中间结果。这意味着在排序过程中,最多需要 O(logn) 的额外空间来保存递归调用的栈帧。
五、算法优点
1.高效性:快速排序在大多数情况下具有较高的排序效率。
2.适应性:适用于各种数据类型的排序。
3.原地排序:不需要额外的存储空间。
六、算法缺点
4.最坏情况性能:在最坏情况下,快速排序的时间复杂度可能退化为 O(n^2)。
5.不稳定性:快速排序可能会破坏排序的稳定性,即相同元素的相对顺序可能会改变。
七、优化方案
6.选择合适的基准:选择合适的基准元素可以提高算法的性能。
7.三数取中:通过选择中间元素作为基准,可以避免最坏情况的出现。
8.分区的改进:可以使用双指针或其他方法来改进分区的过程,提高算法的效率。
9.小数组使用插入排序:对于小数组,可以直接使用插入排序,避免不必要的递归。
八、c++代码模板
#include <iostream>
#include <vector>
// 分区函数,返回基准元素的最终位置
int partition(std::vector<int>& arr, int low, int high) {
int pivot = arr[high]; // 选择最右边的元素作为基准
int i = low - 1; // i 是较小元素的索引
for (int j = low; j < high; ++j) {
if (arr[j] <= pivot) {
++i;
std::swap(arr[i], arr[j]); // 交换元素
}
}
std::swap(arr[i + 1], arr[high]); // 把基准放到正确的位置
return i + 1;
}
// 快速排序的递归函数
void quickSort(std::vector<int>& arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high); // 获取分区索引
quickSort(arr, low, pi - 1); // 递归排序左子数组
quickSort(arr, pi + 1, high); // 递归排序右子数组
}
}
// 打印数组
void printArray(const std::vector<int>& arr) {
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> arr = {10, 7, 8, 9, 1, 5};
int arr_size = arr.size();
std::cout << "Given array is \n";
printArray(arr);
quickSort(arr, 0, arr_size - 1);
std::cout << "\nSorted array is \n";
printArray(arr);
return 0;
}
九、算法动态图解
十、经典真题
1.存在重复元素
(帅哥们这个蓝色字体可以点进去看原题)
代码题解
class Solution {
int partition(vector<int>&a,int l,int r){
int idx=l+rand()%(r-l+1);//rand最小值是0,所以idx的最小值是l,rand的最大值是r-l所以idx最大值是r
swap(a[l],a[idx]);//idx这个值与这个范围最前面的值交换
int i=l,j=r;
int x=a[i];
while(i<j){
while(i<j&&a[j]>x)j--;//如果a[j]>x满足条件,j往前走;如果不满足条件就跳出循环
if(i<j){//如果i<j说明这个区域没有遍历完
swap(a[i],a[j]);
i++;
}
while(i<j&&a[i]<x)i++;//同理满足a[i]<x条件,i往后走;不满足条件跳出循环
if(i<j){
swap(a[i],a[j]);
j--;
}
}
return i;
}
void quickSort(vector<int>&a,int l,int r){
if(l>=r)return ;
int pivox=partition(a,l,r);//找锚点
quickSort(a,l,pivox-1);//从锚点的左部分继续找锚点
quickSort(a,pivox,r);//从锚点右部分继续找锚点
}
public:
bool containsDuplicate(vector<int>& nums) {
int n=nums.size();
quickSort(nums,0,n-1);
for(int i=1;i<n;i++){
if(nums[i]==nums[i-1])return true;
}
return false;
}
};
2.多数元素
(帅哥们这个蓝色字体可以点进去看原题)
class Solution {
int partition(vector<int>&a,int l,int r){
int i=l,j=r;
int idx=l+rand()%(r-l+1);
int x=a[idx];
swap(a[l],a[idx]);
while(i<j){
while(i<j&&x<a[j])--j;
if(i<j){
swap(a[i],a[j]);
i++;
}
while(i<j&&x>a[i])i++;
if(i<j){
swap(a[i],a[j]);
j--;
}
}
return i;
}
void quickSort(vector<int>&a,int l,int r){
if(l>=r)return;
int pivox=partition(a,l,r);
quickSort(a,l,pivox-1);
quickSort(a,pivox+1,r);
}
public:
int majorityElement(vector<int>& nums) {
int n=nums.size();
quickSort(nums,0,n-1);
return nums[n/2];//排完序以后从n/2的下标开始后面的元素都是该值
}
};
十、结语
学习算法是一个很艰难,漫长的过程,我们要克服一切困难,学习算法本就是由易到难,我相信通过我们坚持不懈的努力一定能理解并熟练掌握其中细节,加油。
关注我让我们一起学习编程,希望大家能点赞关注支持一下,让我有持续更新的动力,谢谢大家