快速排序的三种实现方式以及非递归版本
来源:互联网 发布:three.js 全景图切换 编辑:程序博客网 时间:2024/06/05 18:47
一、快速排序的基本思想
快速排序利用了分治的思想,分而治之。通过一趟排序将序列分为两部分,其中一部分比较关键字小,一部分比关键字大。之后继续对这两个子序列重复此过程,直到整个序列都有序。
二、快速排序的三个步骤
- 选基准值:在要排序的序列中选一个几个基准值,作为划分区间的 关键字。通常选取关键字的方式对效率有很大的影响。
- 分割序列:按照基准值把序列分为两部分,左边的小于基准值,右边的大于基准值。
- 重复分割:对子序列重复上面的步骤,知道序列为空或者只有一个元素。当递归处理的所有子序列都返回时,序列有序。
三、三种实现方式
注:下面代码中所有区间都是左闭右开,[ left, right),且排序都是从大到小。且基准值默认选择序列的最后一个元素。
一、版本一填充法
思路:两个指针 begng作为序列的开始 和 end 序列的结束。
将序列的最后一个元素保存起来,作为基准值。
开始划分序列:直到begin < end 不成立
begin 从左往右找比 基准值 大的元素,将该值填充到 end 的位置(end 位置上的值已经保存到 key),end– ,因为已经划分好了一个元素。
end 从右往左找比 基准值小的元素,如将该值填充到 begin 的位置(begin 位置上的值已经保存到 end位置),begin++,已经划分好一个元素。
循环结束时,将 key 填充到 end 的位置,然后将 end 返回,作为下一次划分序列的边界。
// 版本一 填充法int partition(vector<int> &v, int left, int right){ int begin = left; int end = right - 1; int key = v[end]; // 选基准 while (begin < end) { while (begin < end && v[begin] <= key) begin++; if (begin < end) v[end--] = v[begin]; while (begin < end && v[end] >= key) end--; if (begin < end) v[begin++] = v[end]; } v[end] = key; return end;}void QuickSort(vector<int>& v, int left, int right){ if (left < right) { int boundary = partition(v, left, right); QuickSort(v, left, boundary); QuickSort(v, boundary + 1, right); }}
二、版本二 交换法
和上面的版本一类似,不同之处是,这里不是填充,而是将找到的元素交换。
// horn版本int partition(vector<int> &v, int left, int right){ int begin = left; int end = right - 1; int key = v[end]; while (begin < end) { while (begin < end && v[begin] <= key) begin++; while (begin < end && v[end] >= key) end--; if (begin < end) std::swap(v[begin], v[end]); } if(end != right -1) std::swap(v[end], v[right-1];) return end;}void QuickSort(vector<int>& v, int left, int right){ if (left < right) { int boundary = partition(v, left, right); QuickSort(v, left, boundary); QuickSort(v, boundary + 1, right); }}
我认为一,二的主要不同在于一个是填充一个是交换,当比较对象较大时,填充的效率是高于交换,不需要在构造一个临时变量。
三、版本三 双指针前移法
该方法短小精悍。
思路:
两个指针从同一个方向走。
cur 指向当前元素,每次循环都要往前走,直到遍历完序列,所以循环结束的条件就是 cur < right ,right 为 序列的右边界。
prev 指向cur的前一个位置。
如果cur 指向的值小于 基准值, 那么就让 prev前进一个位置,并判断 cur 是否等于 prev,如果不等,说明它两不在同一位置,那么交换位置上的值。
cur 要么相邻,要么中间间隔的都是别基准值大的元素。cur 的目的就是替 prev 清理前进路上的障碍—“比基准值小的元素”。剩下的都是比基准值大的元素, 只要当 cur 找到比基准值小的,就放心的交换。
循环结束时,需要将基准值,即序列的最后一个元素,与 prev下一个位置上的元素 交换。因为这个prev 所在的位置是比基准值小的元素,那么prev 的下一个位置一定比 prev 大,将这个比基准值大的元素与基准值交换就完成了一次划分。
int partition1(vector<int> &v, int left, int right){ int prev = left - 1; int cur = left; int key = v[right - 1]; while (cur < right) { if (v[cur] < key && ++prev != cur) std::swap(v[cur], v[prev]); cur++; } if (v[++prev] != key) std::swap(v[prev], v[right - 1]); return prev;}void QuickSort(vector<int>& v, int left, int right){ if (left < right) { int boundary = partition1(v, left, right); QuickSort(v, left, boundary); QuickSort(v, boundary + 1, right); }}
非递归版本
每次用到递归,大家都会担心递归太深是否会栈溢出?我测试了900000000个int 整型的排序(3.35GB左右内存),栈也木有溢出啊。。
尽管如此还是给出非递归的实现版本吧,比较栈资源宝贵。
利用栈将序列起始位置与结束位置保存起来。
void QiockSortNor(vector<int> &v, int left, int right){ if (left >= right) return; stack<int> s; s.push(left); s.push(right); while (!s.empty()) { int right = s.top(); s.pop(); int left = s.top(); s.pop(); if (left < right) { int boundary = partition1(v, left, right); // 左区间 s.push(left); s.push(boundary); // 右区间 s.push(boundary + 1); s.push(right); } }}
简单总结一下:
快速排序,是一种不稳定的排序,时间复杂度平均为
- 快速排序的三种实现方式以及非递归版本
- 三种快速排序算法的实现(递归算法、非递归算法、三路划分快速排序)
- 三种快速排序算法的实现(递归算法、非递归算法、三路划分快速排序)
- 三种快速排序算法的实现(递归算法、非递归算法、三路划分快速排序)
- 快速排序(三种算法实现和非递归实现)
- 非递归版本的快速排序
- 快速排序的非递归版本
- [置顶]快速排序的递归方式和非递归方式
- 快速排序的非递归实现
- php的非递归快速排序实现
- 非递归的快速排序实现(转)
- 快速排序的非递归实现
- 快速排序的非递归实现
- 快速排序的非递归实现
- 快速排序的非递归实现代码
- 快速排序的非递归实现
- 快速排序算法的非递归实现
- 快速排序的非递归实现
- 二叉搜索树转换成一个排序的双向链表
- swoole源码学习——协程的概念,yield协程和原生协程的实现(上)
- Vmware vRealize Autostation 蓝图简介
- 使用jQuery实现Ajax
- 5.依赖倒转原则
- 快速排序的三种实现方式以及非递归版本
- 个人学习总结的部分java小知识与面试题(三)
- Develop Universal Robot (UR) in ROS: From Install to Demo
- python virtualenv activate文件分析
- 6.装饰模式
- Java中递归实现复制文件夹和文件
- PHP解决网页乱码问题
- Java注解自定义注解
- 马农???(穷举+哈希)