当前位置: 首页 > news >正文

028.快速排序与快速选择算法

快速排序就是二叉树的前序遍历

Quick Sort

二叉树前序遍历

  • 先处理当前节点,再处理左右子树
void preorder(TreeNode*root){if(root==nullptr){return;}//前序位置preorder(root->left);preorder(root->right);
}

快排

  • 先给一个元素排好位置,再给其它元素排位置

怎么确定一个元素的位置?

  • 维护该元素 左侧所有元素都小于等于它,右侧元素都大于它 那么它的位置就找到了

初步实现,后文有优化

void quicksort(vector<int>&nums,int left,int right){if(left>right){return;}int piv=nums[left];int i=left+1,j=right;while(1){while(j>=i&&nums[i]<=piv){i++;}while(j>=i&&nums[j]>piv){j--;}if(i>j)break;swap(nums[i],nums[j]);}int p=j;swap(nums[p],nums[left]);quicksort(nums,left,p-1);quicksort(nums,p+1,right);
}

时间复杂度

类比归并排序,如果我们每次选择的 piv = nums[left] 都能将区间二分,那将是理想的n logn

但如果你用上面的代码提交 leetcode 912 会发现 TLE

为什么呢?

引入快速排序的不适用场景

一、原数组有序性强 -> 容易选到最值元素

如果我们每次选择的 piv = nums[left] 都能将区间二分,那将是理想的n logn

但如果我们选到了最值元素,我们只能将区间一分,相当于挑出了当前区间的最值

如果我们每次都选到最值元素,将会退化为 n^2 的选择排序

如果原数组有序性强,我们每次选piv = nums[ left / right ]都不太合适

一种对策是选中点 piv = nums[ (left+right)/2 ]

但更好的姿态是 :

  1. 随机选取

srand((unsigned)time(NULL));

piv = nums[left + rand() % (right - left + 1)]

实际实现时我们是将 piv 移到最左侧

也就是在选 piv 前加入swap(nums[left],nums[left+rand()%(right-left+1)])

code
class Solution {
public:vector<int> sortArray(vector<int>& nums) {srand((unsigned)time(NULL));quicksort(nums,0,nums.size()-1);return nums;}void quicksort(vector<int>&nums,int left,int right){if(left>right){return;}swap(nums[left],nums[left+rand()%(right-left+1)]);int piv=nums[left];int i=left+1,j=right;while(1){while(j>=i&&nums[i]<=piv){i++;}while(j>=i&&nums[j]>piv){j--;}if(i>j)break;swap(nums[i],nums[j]);}int p=j;swap(nums[p],nums[left]);quicksort(nums,left,p-1);quicksort(nums,p+1,right);
}
}; 
  1. 打乱原数组
srand((unsigned)time(NULL));for(int i=0;i<(int)nums.size();++i){swap(nums[i],nums[i+rand()%(nums.size()-i)]);
}
code
class Solution {void shuffle(vector<int>&a){int n=a.size();srand((unsigned)time(NULL));for(int i=0;i<n;++i){swap(a[i],a[i+rand()%(n-i)]);}}
public:vector<int> sortArray(vector<int>& nums) {shuffle(nums);quicksort(nums,0,nums.size()-1);return nums;}void quicksort(vector<int>&nums,int left,int right){if(left>right){return;}int piv=nums[left];int i=left+1,j=right;while(1){while(j>=i&&nums[i]<=piv){i++;}while(j>=i&&nums[j]>piv){j--;}if(i>j)break;swap(nums[i],nums[j]);}int p=j;swap(nums[p],nums[left]);quicksort(nums,left,p-1);quicksort(nums,p+1,right);
}
}; 

现在,我们已经可以通过leetcode 912

但效率极其低下,这是不能容忍的

我们接着优化

二、大量重复元素

上面我们为一个元素排序的策略是 :

维护该元素 左侧所有元素都小于等于它,右侧元素都大于它

注意我们将相等的元素都排到了左侧

如果有大量重复元素,这无疑会浪费很多性能

优化二路快排

while(1){while(j>=i&&nums[i]<piv){i++;}while(j>=i&&nums[j]>piv){j--;}if(i>j)break;swap(nums[i],nums[j]);i++,j--;
}
code
class Solution {
public:vector<int> sortArray(vector<int>& nums) {srand((unsigned)time(NULL));quicksort(nums,0,nums.size()-1);return nums;}void quicksort(vector<int>&nums,int left,int right){if(left>right){return;}swap(nums[left],nums[left+rand()%(right-left+1)]);int piv=nums[left];int i=left+1,j=right;while(1){while(j>=i&&nums[i]<piv){i++;}while(j>=i&&nums[j]>piv){j--;}if(i>j)break;swap(nums[i],nums[j]);i++,j--;}int p=j;swap(nums[p],nums[left]);quicksort(nums,left,p-1);quicksort(nums,p+1,right);
}
}; 

我们的quicksort更健壮了

三、要求稳定排序

快速排序是不稳定的,稳定排序请看归并排序

Quick Select

主要场景是寻找第 k 大的元素或者求中位数

不难理解、求第 k 大可以转化为求第 n - k 小 ( n为元素总数 )

在快排中

当我们确定了 piv 的位置后可以知道 :piv是第p小元素

这时我们比较 pk

这里是找第 k

  • p==k 返回 nums[p]
  • p > k 去左侧找 [ left, p-1]
  • p < k 去右侧找 [ p-1, right]

时间复杂度理想情况可以达到惊人的O(n)

leetcode 215

 class Solution {
public:int findKthLargest(vector<int>& nums, int k) {srand((unsigned)time(NULL));int n=nums.size();return findk(nums,n-k,0,n-1);}int findk(vector<int>&nums,int k,int left,int right){if(left>=right)return nums[left];swap(nums[left],nums[left+rand()%(right-left+1)]);int piv=nums[left];int i=left+1,j=right;while(1){while(i<=j&&nums[i]<piv)i++;while(i<=j&&nums[j]>piv)j--;if(i>j)break;swap(nums[i],nums[j]);i++,j--;}int p=j;swap(nums[left],nums[p]);if(p==k)return nums[p];else if(p<k)return findk(nums,k,p+1,right);else return findk(nums,k,left,p-1);}
};
http://www.jsqmd.com/news/159109/

相关文章:

  • 当海量位置数据查询超过10秒,3个技巧让响应时间降至毫秒级
  • 第07章-几何访问函数
  • 好写作AI:对比实验!使用前后,论文质量与效率的客观数据大公开
  • Qwen1.5-4B边缘AI推理革命:突破显存瓶颈的技术创新
  • 如何在5分钟内搭建分布式实时通信系统:Centrifuge终极指南
  • RStudio API实战指南:高效自动化你的数据分析工作流
  • 好写作AI用户故事:一位延毕风险研究生,如何借助AI按时完成优质论文
  • Obsidian插件测试终极指南:快速掌握BRAT自动更新工具
  • GPU性能分析完全指南:三大利器深度解析与实战优化技巧
  • 终极指南:如何在浏览器中运行完整的Linux系统
  • 好写作AI:导师视角:为什么越来越多导师认可学生使用这类工具
  • 2025 年 12 月轴承厂家权威推荐榜:深沟球/圆锥滚子/调心滚子等全品类轴承,精密传动与高负载性能深度解析 - 品牌企业推荐师(官方)
  • 为什么GNU Emacs窗口管理能提升编程效率:新手必学的完整指南
  • 好写作AI:导师视角——查重报告说话:看AI如何从40%降到5%以下
  • 第05章-空间索引与性能优化
  • 马斯克押注“应用智能”:AI×机器人或在5年内把人类推向后稀缺经济
  • 揭秘虚拟机压测性能损耗:oha VSOCK直连方案深度解析
  • Vue Trend:为你的Vue.js应用注入优雅的数据可视化力量
  • 2025面包机多士炉炉胆生产厂家TOP5权威推荐:甄选源头企业筑牢家电品质根基 - mypinpai
  • 如何快速掌握bxSlider:创建响应式轮播图完整指南
  • 第08章-几何输出函数
  • 3步掌握ClickHouse地理空间分析:告别传统GIS系统臃肿配置实战指南
  • 华美食品客户评价、信任度及原料保障解析:烘焙年货品牌年度排名 - 工业设备
  • Python图像处理终极指南:从原理到实践深度解析
  • 2025年深圳靠谱移民中介排行榜,新测评精选移民公司推荐 - mypinpai
  • Serial-Studio数据可视化方案抉择:从成本控制到技术实施的最佳实践
  • BindCraft:让蛋白质分子设计变得简单高效的AI工具
  • AI编程的残酷真相:为什么说Spec Coding是2026年最大的趋势?
  • 哇塞!2026年挖到了宝藏,这几款给视频去水印工具推荐太绝啦! - 资讯焦点
  • 第01章-NPOI概述与入门