算法基础4—快速排序
来源:互联网 发布:最新网络红歌排行榜 编辑:程序博客网 时间:2024/04/29 16:25
快速排序也是在面试中常被问的排序算法之一,它与归并算法一样,也使用了分治的思想。快速排序的三步分治过程:
- 分解:将一个待排序数组A[p,…,r]划分为两个字数组(可能为空)A[p…q-1], A[q+1…r],其中A[p…q-1]中的每一个元素都小于A[q], A[q+1…r]中的每一个元素都大于A[q]。计算下标q也是划分过程中的一部分。
- 解决: 通过递归调用快速排序,对子数组A[p…q-1]和A[q+1…r]进行排序。
- 合并:因为子数组都是原址排序的,所以不需要合并操作,数组A[p…r]已经有序。
下面先给出快速排序的伪代码:
quickSort(A, p, r) 1.if p<r 4. q = partition(A,p,r) 5. quickSort(A, p, q-1) 6. quickSort(A, q+1, r)
为了排序一个数组的所有元素,初始调用是quickSort(A, 1,A.length)。
首先介绍一个快速排序的最关键的一步就是数组的划分:
即partition部分,它实现了对数组A[p…r]的原址重排。
番外:<原址在百度百科中给出的定义是在排序算法中,如果输入数组中仅有常数个元素需要在排序过程中存储在数组之外,则称排序算法是原址的。为什么归并排序不是原址排序,而堆排序是原址的呢?要怎么理解这句话呢?
因为归并排序还要另外一个数组做merge。那个是O(n)空间。in-place归并是可以写出来的,就是超级麻烦。快排是in-place的,因为它就是在自己数组里折腾。堆排也是,它建立了一个堆的结构,每次deletemax少一个元素,但它还是在自己数组里折腾的,只是这个数组前面若干个元素维护了堆结构,后面若干个元素是由小到大排好顺序的,堆永远不会访问后面排好顺序的元素。>
继续给出partition的伪代码:
partition(A, p, r) 7. x = A[r] //选定的基准值 8. i = p-1 //类似一个指针,指定要交换的元素 9. for j = p to r //也类似一个指针,指定后面满足交换条件的元素 10. if A[j] <= x 11. i ++ 12. exchange A[i] with A[j] 13. exchange A[i+1] with A[r] 14. return i+1
在子数组A[p…r]上,partition维护了4个区域,如下图所示,A[p…i]区间内的所有值都是小于等于x,A[i..j]区间内的所有值都是大于x,A[r]=x。字数组A[j…r-1]中的值可能属于任何一种情况。
当A[j..r]中的元素小于等于x时,执行如算法中的操作,i++,并且交换A[i]和A[j]。元素大于x时,没有明显操作,即加入到A[i..j]中。
js代码如下:
var partition = function(a, p, r){ var x = a[r-1]; var i = p-1; for(var j = p; j < r-1; j++) { if(a[j] <= x) { i = i+1; var temp = a[i+1]; a[i+1] = a[j]; a[j] = temp; } } var ans = a[i+1]; a[i+1] = a[r-1]; a[r-1] = ans; return i+1;}var qSort = function(a, p, r){ if(p < r) { var q = partition(a, p, r); qSort(a, p, q-1); qSort(a, q+1, r); }}
交换两个元素时,有个swap()函数,可是我还没有具体看它是怎么实现的,所以还是先这样利用一个变量进行交换。
还有一个过程,就是算法的时间复杂度分析,虽然我们常说快速排序的时间复杂度是O(nlgn),却很少具体的分析一下什么情况下是O(nlgn),这个是快排最好的时间复杂度吗?
我们都知道快排的运行时间依赖于划分是否平衡,而平衡与否又依赖于用于划分的元素。如果划分是平衡的,那么快排算法性能与归并排序一样。如果划分不平衡,那么快排的性能就接近于插入排序了。
下面从三个不同的划分来分析一下快排的时间复杂性。
最坏情况划分
当划分的两个字问题分别包含了n-1个元素和0个元素。我们假设每次递归调用都出现了这种不平衡的划分。划分操作的时间复杂度是O(n)。由于对大小为0的数组进行递归会直接返回,因此T(0) = O(1),于是算法运行的时间递归式可以表示成
T(n) = T(n-1)+O(n)
利用带入法可以得到T(n) = O(n)+O(n-1)+O(n-2)+…+O(1)+T(0)
即: T(n) = O(n^2)。
可以看到,最坏情况下快速排序运行时间并不比插入排序更好。此外,当数组已经排好序时,时间复杂度依然为O(n^2),而插入排序的时间复杂度为O(n)。最好情况划分
在可能的最平衡的划分中,partition得到的两个子问题的规模都不大于n/2。这是因为其中一个子问题的规模为[n/2],而另一个子问题的规模为[n/2]-1。在这种情况下快速排序的性能非常好。此时,算法运行的时间递归式为:
T(n) = 2T(n/2)+O(n)
上述递归式的解为T(n) = O(nlgn)。
事实上,任何一种常数比例的划分都会产生深度为O(lgn) 的递归树,其中每一层的时间代价都是O(n),因此,只要是常数比例的划分,算法的运行时间都是O(nlgn)。平均情况
在平均情况下,partition所产生的划分同时混合有“好”和“差”的划分。此时,在递归树中,好和差的划分是随机分布的。
如下图a所示,显示了连续两层上的划分,根节点处划分的代价为n,划分产生两个子数组,大小分别为n-1和0。在下一层上,大小为n-1的子数组按最好情况划分。这一组合的划分代价为O(n)+O(n-1)= O(n)。该代价并不比(b)中所示的划分情况差。因此,当好的划分和差的划分交替出现时,快速排序的时间复杂度与全是好的划分时一样,依然是O(nlgn)。区别只是O符号中隐含的常数因子要略大一些。
- 算法基础4—快速排序
- 算法基础之排序—快速排序
- 基础算法 快速排序
- 基础算法----快速排序
- 基础算法-快速排序
- 基础算法-快速排序
- 算法基础 快速排序
- [算法基础]快速排序
- 基础排序算法-快速排序
- 基础算法-快速排序算法
- 算法基础4:快速排序(随机化版本)
- 基础算法——快速排序
- 算法基础——快速排序
- 基础算法——快速排序
- 基础算法学习笔记—快速排序
- 基础算法--C快速排序
- 算法基础--快速排序详解
- 排序算法(4)——快速排序
- Ajax局部刷新后,重新加载百度分享
- 算法之数字反转问题
- Hive最新数据操作详解(超级详细)
- 买房购车注意事项
- 学习 Node.js 的 6 个步骤
- 算法基础4—快速排序
- 华夏群英传隐私政策
- 项目知识复习3----CAN总线
- VC++6.0 制作标准dll(动态库)
- Linux常用的20条命令
- 0x5f3759df的推导
- Java web ajax分页
- 取数游戏-动态规划
- nginx+tomcat+redis的集群+session共享