快速排序
Hello!这
七大排序
想想我现在的感觉
是什么呢?感觉会,但细想又是模糊的,再细想,我擦!!会个锤子!更别提撸代码了…手动狗头~来一篇巩固总结!(还在更新__没写完)
本文总纲:
1.快排缘起!
快速排序:是
东尼·霍尔
(老爷子还挺儒雅地!!哈哈~)所发展的一种排序算法。YYDS!
- 在平均状况下,排序 n 个项目要
Ο(nlogn)
次比较。- 在最坏状况下则需要
Ο(n^2)
次比较,但这种状况并不常见。- 事实上,快速排序通常明显比其他
Ο(nlogn)
算法更快,因为它的内部循环(inner loop)
可以在大部分的架构上很有效率地被实现出来。
可能有人会问?凭啥子叫快排
? 猪排
,牛排
不香吗??
快排!!快排!!!因为一听到这个名字你就
知道了
!就是一个字~快!!!而且效率高哦!(好吧!不止一个字了…)虽然
最糟糕
的时间复杂度达到了O(n²)
,但是人家就是优秀,牛逼!
(好像虎落平阳,但还是虎!)敲黑板:但是在
大多数情况下
都比平均时间复杂度为O(n logn)
的排序算法表现要更好**,你可能会问!~这是啥子原因噻!!
我引用一段大佬的论述:(下面这段不是我写的哈!!!中国的文学用语啥来着~叫做
借鉴
)嘻嘻!厚脸皮无敌!!这位大佬查了 N 多资料终于在
《算法艺术与信息学竞赛》
上找到了满意的答案:感兴趣看看这论证就行哈!!(别较真)
快速排序
的最坏运行情况是O(n²)
,比如说顺序数列的快排。- 但它的平摊期望(就是平均
E(X)
)时间是O(nlogn)
,且O(nlogn)
记号中隐含的常数因子很小,比复杂度稳定等于O(nlogn)
的归并排序要小很多。- 所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
最后一点
(我自己的哈):快排对越有序的数列反而排的越慢
2.快排原理
- 快速排序使用分治法(
Divide and conquer
)策略来把一个串行
(list
)分为两个子串行
(sub-lists
)。- ***分治法:***我的理解就是
切西瓜
,一个西瓜切两半
- 快速排序:又是一种
分而治之
思想在排序算法上的典型应用。说白了
:快速排序就是在冒泡排序基础上的递归分治法
。
3.算法步骤
- 从数列中挑出一个元素,称为
基准
(pivot)或者哨兵
;重新排序数列
,所有元素比基准值小
的摆放在基准前面
,所有元素比基准值大
的摆在基准的后面
(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间
位置。这个称为分区
(partition
)操作;递归
(recursive
)进行把小于基准值元素的子数列和大于基准值元素的子数列排序;
4.代码实现
//严蔚敏《数据结构》标准分割函数
Paritition1(int A[], int low, int high) {
int pivot = A[low];
while (low < high) {
while (low < high && A[high] >= pivot) {
--high;
}
A[low] = A[high];
while (low < high && A[low] <= pivot) {
++low;
}
A[high] = A[low];
}
A[low] = pivot;
return low;
}
void QuickSort(int A[], int low, int high) //快排母函数
{
if (low < high) {
int pivot = Paritition1(A, low, high);
QuickSort(A, low, pivot - 1);
QuickSort(A, pivot + 1, high);
}
}
另一个版本(我感觉更好,通俗易懂!)
搬运大佬博客代码(哈哈):你们可以直接去看他的博客,我这方便日后查看(防止它没了)
参考博客:快速排序(详细讲解)
问题描述:
假设我们现在对
[6 1 2 7 9 3 4 5 10 8]
这个10个数进行排序。
首先:在这个序列中随便找一个数作为基准数。
- 选取第一个数6作为基准数。
- 在这个序列中,将所有比基准数大的数放在6的右边,比基准数小的数放在6的左边,类似下面这种排列:
3 1 2 5 4 6 9 7 10 8
**在初始状态下,**数字6在序列的第1位。
我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。
现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。
快速排序
public void quickSortReviewSecond(int[] nums,int start,int end){
if(start>end) return;
//init left&right node ,pivot
int left,right,pivot;
left=start;
right=end;
//设定的基准值(想作为比较的标尺)
pivot=nums[start];
while(left<right){
//left=================哨兵==================right
//首先哨兵与右边的值比较,如果哨兵值小于哨兵右边位置的值,右边位置向前进一位(就是递减),直到右边哨兵找到小于基准值停下
while(left<right&&nums[right]>=pivot) right--;
//注意:都是有等于,由此可确定快排不是稳定排序
//找到左边大于基准的值停下(自己揣摩揣摩)
while(left<right&&nums[left]<=pivot) left++;
if(left<right){
//交换左右哨兵的值
swap(nums,left,right);
}
//将基准值换到最准确的位置
swap(nums,start,left);
//对此时更新后基准左边的序列快排
quickSortReviewSecond(nums,start,right-1);
//对此时更新后基准右边的序列快排
quickSortReviewSecond(nums,right,end);
}
}
private void swap(int[] nums, int left, int right) {
nums[right]^=nums[left];
nums[left]^=nums[right];
nums[right]^=nums[left];
}
方法其实很简单:(认真看!我当初不懂,草草了事,折腾半天又回头看了三四遍!..)
初始化(热身运动)
- 分别从初始序列
“6 1 2 7 9 3 4 5 10 8”
两端开始“探测”
。- 先
从右往左
找一个小于6
的数,再从左往右
找一个大于6
的数,然后交换
他们。- 这里可以用两个变量
i
和j
,分别指向序列最左
边和最右
边。- 我们为这两个变量起个好听的名字
“哨兵i”
和“哨兵j”
。刚开始的时候让哨兵i
指向序列的最左
边(即i=1
),指向
数字6。让哨兵j
指向序列的最右边
(即j=10
),指向数字8
首先
哨兵j
开始出动。为什么??因为此处设置的
基准数
是最左边
的数,所以需要让哨兵j
先出动,这一点非常重要(请自己想一想为什么)。为什么?大家怎么理解的呢!手动狗头
哨兵j
一步一步地向左挪动(即j–-
),直到找到一个小于6
的数停
下来。接下来
哨兵i
再一步一步向右挪动(即i++
),直到找到一个数大于6
的数停下来。最后
哨兵j
停在了数字5
面前,哨兵i
停在了数字7
面前。
交换
现在交换
哨兵i
和哨兵j
所指向的元素的值。交换之后的序列如下:[6 1 2 5 9 3 4 7 10 8]
一轮遍历渐入尾声
到此,
第一次
交换结束。
- 接下来开始
哨兵j
继续向左挪动(再友情提醒
,每次必须是哨兵j
先出发)。- 他发现了
4
(比基准数6
要小,满足要求)之后停了下来。哨兵i
也继续向右挪动的,他发现了9
(比基准数6
要大,满足要求)之后停了下来。- 此时再次进行交换,交换之后的序列如下:
[6 1 2 5 4 3 9 7 10 8]
第二次交换结束,
“探测”继续。
哨兵j
继续向左挪动,他发现了3
(比基准数6要小
,满足要求)之后又停了下来。
哨兵i
继续向右移动,糟啦
!此时哨兵i
和哨兵j
相遇了(单身狗笑嘻嘻!);
哨兵i
和哨兵j
都走到3
面前(看下图),说明此时***“探测”***结束
。我们将
基准数6
和3
进行交换。交换之后的序列如下:[3 1 2 5 4 6 9 7 10 8]
到此第一轮“探测”真正结束。
此时以
基准数6
为分界点,6
左边的数都小于等于6
,6
右边的数都大于等于6
。回顾一下刚才的
过程
- 其实
哨兵j
的使命就是要找小于
基准数的数,- 而
哨兵i
的使命(乌拉!!)就是要找大于基准数的数,直到i和j碰头为止。OK!! 解释完毕。
现在
基准数6
已经归位,它正好处在序列的第6位
。此时我们已经将
原来的序列
,以6
为分界点拆分成了两个序列,
- 左边的序列是
[3 1 2 5 4]
,- 右边的序列是
[9 7 10 8]
。接下来还需要分别处理这两个序列。
因为
6
左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理
6
左边和右边的序列即可。
现在先来处理
6
左边的序列现吧。左边的序列是
[3 1 2 5 4]
。请将这个序列以3为基准数进行调整,使得3左边的数都小于等于3,3右边的数都大于等于3。好了开始动笔吧如果你模拟的没有错,调整完毕之后的序列的顺序应该是:
[2 1 3 5 4]
OK,现在3已经归位。
接下来需要处理
3
左边的序列[2 1]
和右边的序列[5 4]
。对序列
[2 1]
以2
为基准数进行调整
,处理完毕之后的序列为[1 2]
,到此2
已经归位。序列
1
只有一个数,也不需要进行任何处理。至此我们对序列
[2 1]
已全部处理完毕,得到序列是[1 2]
。序列
[5 4]
的处理也仿照此方法,最后得到的序列:[1 2 3 4 5 6 9 7 10 8]
对于序列
[9 7 10 8]
也模拟刚才的过程,直到不可拆分出
新的子序列为止。最终将会得到这样的序列:
[1 2 3 4 5 6 7 8 9 10]
到此,排序完全结束。(撒花)
细心的同学可能已经
发现
,快速排序的每一轮处理其实
就是将这一轮的基准数归位
,直到所有的数都归位为止
,排序就结束
了。