【数据结构与算法】快速排序
目录
一、快速排序定义
二、排序思想
1.Hoare法
2.挖坑法
3.前后指针法
三、不足及改进
1.不足
2.改进
四、由递归改为非递归
一、快速排序定义
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好,可是这是为什么呢,我也不知道。好在我的强迫症又犯了,查了 N 多资料终于在《算法艺术与信息学竞赛》上找到了满意的答案:
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
二、排序思想
1.Hoare法
Hoare的这个版本是最早的版本,首先要选择一个key作为基准,一般选最左边或者最右边的值,也可以选择中间值,单趟排完以后:要求左边比key小,右边比key大
以最左边为key举例,要求右边先走,寻找比key小的值,然后左边走,去寻找比key大的值,然后交换这两个值。左右两边的两个下标依次交替向中间靠拢直到它们二者相交,
然后将交点的值与key所指向的值交换。
这就是单趟排序之后的结果。
单趟排序后,会发现key左边的值都比key小,右边的值都比key大,但是[left,key-1]
[key+1,right]这两个区间不是有序的,采用分治的思想,key的左区间有序,key的右区间也有序,则整体有序。
void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}int left = begin;int right = end;int keyi = left;while (left < right){//右边先走找比key小的值while (left < right && a[right] >= a[keyi]){right--;}//左边走,找比key大的值while (left < right && a[left] <= a[keyi]){left++;}//交换Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}
2.挖坑法
挖坑法与第一个版本思想上是比较类似的,先将第一个数据存放在临时变量key中,形成一个坑位
然后右边走寻找比key小的值,复制到坑里,这个值的位置形成新的坑,左边寻找比key大的值,存放到坑里,这个值也形成新的坑,直到两者相交,然后将key里的值放到坑里,单趟就结束了。
然后递归左区间,右区间,让左右区间分别有序。
这是单趟排序,剩下的大框架与前面一致,快速排序递归版本的框架都是一样的,唯一不同的只是单趟排序。
//挖坑法
int PartSort2(int* a, int begin, int end)
{int key = a[begin];int piti = begin;while (begin < end){while (begin < end && a[end] >= key){end--;}a[piti] = a[end];piti = end;while (begin < end && a[begin] <= key){begin++;}a[piti] = a[begin];piti = begin;}a[piti] = key;return piti;
}
3.前后指针法
前后指针的方法与前两种方法有略微不同
有两个指针一个是prev另一个是cur
如果cur指向的值小于key,那么就交换prev和cur的值,cur和prev都想后挪一位,
反之不然,cur向后挪一位
最后交换prev和key的值,让prev的值作为下一个key
//前后指针法
int PartSort3(int* a, int begin, int end)
{int prev = begin;int cur = begin + 1;int keyi = begin;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);cur++;}Swap(&a[keyi], &a[prev]);keyi = prev;return keyi;
}
三、不足及改进
1.不足
快速排序在有序或者接近有序时时间复杂度为O(N^2)
上述三种方法,都要寻找key,然后让区间中的值与key进行比较,如果每次寻找key都是区间的最小值或者是最大值时,第一行要交换N次。第二行要交换N-1次,依次往后推算到最后一行要交换1次,交换次数是一个等差数列,大致推算出它的时间复杂度为O(N^2)
同时数据量较大时会因为递归的深度过高出现栈溢出
快速排序递归类似于二叉树,一个具有N个节点的二叉树的深度是LogN,也就是说快速排序的深度是LogN,极有可能出现栈溢出现象。
2.改进
1.随机选取key
2.运用三数取中,选取既不是最大的也不是最小的数来作为key
3.在数据量较小时采用插入排序
上述三种方法是用来应对第一个缺点的处理方法
先说明三数取中逻辑比较简单取出三个数中间大小的那个数
这样可以避免每次取到最大或者最小的值
int GetMidIndex(int* a, int begin, int end)
{int mid = (begin + end) / 2;if (begin < mid){if (a[mid] < a[end]){return mid;}else if (a[begin] < a[end]){return end;}else{return begin;}}else //begin >= mid{if (a[mid] > a[end]){return mid;}else if (a[begin] < a[end]){return begin;}else{return end;}}
}
接下来是区间优化
当递归划分小区间时,区间比较小的时候,就不再递归划分去排序这个小区间,考虑直接用其它排序对小区间处理
//hoare版本 加入三数取中,和小区间优化
int PartSort1(int* a, int begin, int end)
{int left = begin;int right = end;int keyi = left;int mini = GetMidIndex(a, begin, end);Swap(&a[mini], &a[keyi]);while (left < right){//右边先走找小while (left < right && a[right] >= a[keyi]){right--;}//左边走找大while (left < right && a[left] <= a[keyi]){left++;}//交换Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;return keyi;
}
//挖坑法
int PartSort2(int* a, int begin, int end)
{int key = a[begin];int piti = begin;int mini = GetMidIndex(a, begin, end);Swap(&a[mini], &a[piti]);while (begin < end){while (begin < end && a[end] >= key){end--;}a[piti] = a[end];piti = end;while (begin < end && a[begin] <= key){begin++;}a[piti] = a[begin];piti = begin;}a[piti] = key;return piti;
}
//前后指针法
int PartSort3(int* a, int begin, int end)
{int prev = begin;int cur = begin + 1;int keyi = begin;int mini = GetMidIndex(a, begin, end);Swap(&a[mini], &a[keyi]);while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);cur++;}Swap(&a[keyi], &a[prev]);keyi = prev;return keyi;
}
四、由递归改为非递归
可以借助栈这个数据结构,来模拟递归过程
快速排序递归过程是先处理左区间,然后处理右区间
因为栈的特点,后进先出,要先将右区间入栈,然后将左区间入栈
同时区间的两个端点的顺序也是同理,先入右端点,再入左端点
如果区间至少有一个值,那么就继续入栈,直到栈为空
与二叉树的层序遍历类似。
void QuickSortNonR(int* a, int begin, int end)
{ST st;StackInit(&st);StackPush(&st, end);StackPush(&st, begin);while (!StackEmpty(&st)){int left = StackTop(&st);StackPop(&st);int right = StackTop(&st);StackPop(&st);int keyi = PartSort3(a, left, right);if (keyi + 1 < right){StackPush(&st, right);StackPush(&st, keyi + 1);}if (left < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, left);}}StackDestroy(&st);
}
【数据结构与算法】快速排序相关推荐
- java快排原理_Java数据结构与算法——快速排序
声明:码字不易,转载请注明出处,欢迎文章下方讨论交流. 前言:Java数据结构与算法专题会不定时更新,欢迎各位读者监督.本篇文章介绍排序算法中最常用也是面试中最容易考到的排序算法--快排,包括快排的思 ...
- 数据结构与算法 | 快速排序:Hoare法, 挖坑法,双指针法,非递归, 优化
前两章讲解了排序中的选择排序和插入排序,这次就来讲一讲交换排序中的快速排序. 快速排序时间复杂度:平均 O(nlogn) 最坏 O(n2) 快速排序,顾名思义,它的排序的效率是非常高的,在数据十分杂乱 ...
- java 数据结构与算法 ——快速排序法
快速排序法: 顾名思议,快速排序法是实践中的一种快速的排序算法,在c++或对java基本类型的排序中特别有用.它的平均运行时间是0(N log N).该算法之所以特别快,主要是由于非常精练和高度优化的 ...
- Java数据结构与算法——插入排序
声明:码字不易,转载请注明出处,欢迎文章下方讨论交流. 前言:Java数据结构与算法专题会不定时更新,欢迎各位读者监督.本篇文章介绍排序算法中插入排序算法,包括插入排序的思路,适用场景,性能分析,ja ...
- 数据结构与算法之快速排序
数据结构与算法之快速排序 目录 快速排序介绍 代码实现 1. 快速排序介绍 快速排序(Quicksort)是对冒泡排序的一种改进.基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的 ...
- 数据结构与算法之--高级排序:shell排序和快速排序
高级排序比简单排序要快的多,简单排序的时间复杂度是O(N^2),希尔(shell)排序大约是O(N*(logN)^2),而快速排序是O(N*logN). 说明:下面以int数组的从小到大排序为例. 希 ...
- 数据结构与算法 第八天常见排序+冒泡排序+快速排序+文件IO+大数据排序+文件合并
数据结构与算法 第八天常见排序+冒泡排序+快速排序+文件IO+大数据排序+文件合并 第一章 冒泡排序 [1]Bubble_Sort.c 第二章 快速排序 [1]quick_sort.c 第三章 大数据 ...
- 快速排序-排序-数据结构和算法
文章目录 1 基本算法 1.1 原地切分 1.2 边界 1.3 随机性 1.4 终止循环 1.5 切分元素重复 1.6 终止递归 2 备注 1 基本算法 快速排序是一种分治的排序算法.它将一个数组 ...
- 《数据结构与算法》(二十五)- 排序算法:快速排序
目录 前言 1. 快速排序 1.1 快速排序算法 1.2 快速排序算法复杂度分析 1.3 快速排序优化 2. 总结 原文地址:https://program-park.github.io/2021/1 ...
- 数据结构与算法XS班-左程云第八节课笔记(归并排序和快速排序)
第8节 归并排序和快速排序 ##这是数据结构与算法新手班-左程云第八节课的笔记## 归并排序 归并排序实际上是一个很经典的排序方法,时间复杂度o(N*logN). 递归版本(图解排序算法(四)之归并排 ...
最新文章
- 【ASP.NET Core】解决“The required antiforgery cookie xxx is not present”的错误
- 谷歌自动驾驶是个大坑,还好中国在构建自己的智能驾驶大系统
- Sitecore 9有什么新功能
- Spring BeanFactory 容器
- 关于VCP(Virtual Com Port)拓展的调试经历(一)
- 遍历可执行文件所在目录下的指定类型的文件
- html怎么引入圆角插件,jQuery圆角插件demo页面 张鑫旭-鑫空间-鑫生活
- 微软将为Linux 操作系统带来TEE的支持:TEE(Trusted Execution Environment,可信执行环境)
- Redis的服务端启动和客户端连接
- Mac上Jupyter notebook代码补全
- 山东大学计算机组成课设,山东大学计算机组成原理课程设计实验报告.pdf
- android 9.0 安装xpose框架
- 安卓盒子root--包好
- Appium_3_环境配置_Appium-desktop配置
- OctetString 转String
- Android DES加密解密
- 用Servlet实现统计网站被访问次数的功能
- 四两拨千斤!深度主动学习综述2020
- C语言——冒泡排序、改进的冒泡排序
- 10qbt超导量子计算机,南京大学于扬、朱诗亮团队在超导量子比特中实现参数空间的新型磁单极...
热门文章
- 2013年6月22日全国高校计算机联合考试广西考区一级笔试试题,2021年全国高校计算机联合考试广西考区一级笔试试题卷6月26日B.doc...
- [原创] wildfly 部署应用时出错原因调查
- java csv文件乱码_java读取csv文件出现乱码怎么处理
- 分享:写作赚钱的六个途径
- USB数据线上的“疙瘩”
- 火狐的迅雷插件,会导致连接执行2次
- DL-Paper精读:Training SDNNs with IHT
- PLSQL过期:Your trial period for PL/SQL Developer is over .注册码
- 【路径规划-VRP问题】基于遗传算法求解三维装载约束下的汽车零部件循环取货路径优化模型附matlab代码
- 滚动图片 电视背景滚动图片效果 IPTV桌面滚动图片效果 图片倒影 滚动广告图