主要介绍快速排序,和3个快排的优化,最后引出三路快速排序。
原文请访问我的技术博客番茄技术小站
定义(百度百科)
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
算法步骤(wiki)
- 从数列中挑出一个元素,称为"基准"(pivot),
- 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
快速排序代码实现(basic)
分析
- 首先寻找基准元素;
- 分区(partition):所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面;
- 递归的对左右两边的子数列进行递归排序
图示(初始条件很重要)
partition的某个时刻
- 当e>v时, i直接i++
- 当e<v时, arr[j+1] <=> e互换, j++, i++
- 最后的状态为:(返回j)
php代码实现
最重要的是实现partition函数,而实现partition函数最重要的是初始化状态值(l,
l+1)
//对arr[l...r]部分进行partition操作
// 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
function partition(&$arr, $l, $r){
$v = $arr[$l];
$j = $l;
for ($i=$l+1; $i <= $r ; $i++) {
if ($arr[$i] < $v) {
swap($arr, $j+1, $i);
$j++;
}
}
swap($arr, $l, $j);
return $j;
}
/**
* [__quickSort 对数组arr[l...r]进行快速排序]
* @param [type] &$arr [description]
* @param [type] $l [description]
* @param [type] $r [description]
* @return [type] [description]
*/
function __quickSort(&$arr, $l, $r){
if ($l >= $r) {
return;
}
//优化点1
// if($r - $l <= 15){
// insertSortCom($arr, $l, $r);
// return;
// }
$p = partition($arr, $l, $r);
__quickSort($arr, $l, $p-1);
__quickSort($arr, $p+1, $r);
}
function quickSort(&$arr, $n){
__quickSort($arr, 0, $n-1);
}
复制代码
执行时间
mergeSort运行的时间为:0.031744003295898s
quickSort运行的时间为:0.02904486656189s
复制代码
优化点1
还是在数列小于16的时候直接对数列进行插入排序
function __quickSort(&$arr, $l, $r){
// if ($l >= $r) {
// return;
// }
//优化点1
if($r - $l <= 15){
insertSortCom($arr, $l, $r);
return;
}
$p = partition($arr, $l, $r);
__quickSort($arr, $l, $p-1);
__quickSort($arr, $p+1, $r);
}
复制代码
优化点2
对于数列基本有序的情况
// //main
$n = 100000;
// $arr = generateRandomArray($n, 0, $n);
$arr = generateNearlyOrderedArray($n, 100);
$copy_arr = $arr;
testSort("mergeSort", "mergeSort", $copy_arr, $n);
testSort("quickSort", "quickSort", $arr, $n);
复制代码
归并排序和快速排序算法的时间对比
mergeSort运行的时间为:0.32358503341675s
quickSort运行的时间为:18.838504076004s
复制代码
分析
当数组近乎有序的情况下,快速排序每次划分的数列极其不平衡,几乎退化成O(N*N)时间复杂度的算法
有没有解决方法,有的!只要每次随机的选取“基准数“即可。
问题解决(代码实现)
只需要在partition代码中加入swap($arr, $l, rand($l, $r));
function partition(&$arr, $l, $r){
swap($arr, $l, rand($l, $r));
$v = $arr[$l];
$j = $l;
for ($i=$l+1; $i <= $r ; $i++) {
if ($arr[$i] < $v) {
swap($arr, $j+1, $i);
$j++;
}
}
swap($arr, $l, $j);
return $j;
}
复制代码
执行时间
mergeSort运行的时间为:0.21585202217102s
quickSort运行的时间为:0.34276294708252s
复制代码
优化点3
对于数列中有大量重复元素的情况
$n = 100000;
$arr = generateRandomArray($n, 0, 10);
$copy_arr = $arr;
testSort("mergeSort", "mergeSort", $copy_arr, $n);
testSort("quickSort", "quickSort", $arr, $n);
复制代码
运行时间
mergeSort运行的时间为:0.89260506629944s
quickSort运行的时间为:19.236886024475s
复制代码
分析
当数列中存在大量重复元素时,我们发现快排速度很慢,我们有理由怀疑它可能又蜕化成O(n*n)时间复杂度的算法了。我们的代码中,对于arr[i]等于v的情况都放到了右边,所以会造成partition造成极度不平衡:
问题解决(代码实现)
** 场景分析**
-
初始场景
-
arr[
i++
- arr[
j--
- 直到
i] >= e,
j] <= e, swap(arr[
j]);这时候,与$v重复的元素就均匀的分布在数列的两边
- 最终状态
代码实现
/*--------------------第二种算法开始-------------------*/
//对arr[l...r]部分进行partition操作
// 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
function partition2(&$arr, $l, $r){
swap($arr, $l, rand($l, $r));
$v = $arr[$l];
// arr[l+1...i) <= v; arr(j...r] >= v
$i = $l+1;
$j = $r;
while (true) {
while($arr[$i] < $v && $i <= $r){
$i++;
}
while($arr[$j] > $v && $j >= $l+1){
$j--;
}
if ($i > $j) {
break;
}
swap($arr, $i, $j);
$i++;
$j--;
}
swap($arr, $l, $j);
return $j;
}
function quickSort2(&$arr, $n){
__quickSort2($arr, 0, $n-1);
}
function __quickSort2(&$arr, $l, $r){
// if ($l >= $r) {
// return;
// }
//优化点1
if($r - $l <= 15){
insertSortCom($arr, $l, $r);
return;
}
$p = partition2($arr, $l, $r);
__quickSort2($arr, $l, $p-1);
__quickSort2($arr, $p+1, $r);
}
/*--------------------第二种算法结束-------------------*/
复制代码
运行时间比较
mergeSort运行的时间为:1.1809809207916s
quickSort运行的时间为:25.186847925186s
quickSort2运行的时间为:0.20458602905273s
复制代码
可以看出,优化后的快排时间复杂度又回到了O(N*logN)了。从优化2算法其实可以引出三路快排算法。
三路快速排序算法
定义
三路快速排序是快速排序的的一个优化版本, 将数组分成三段, 即小于基准元素、 等于 基准元素和大于基准元素, 这样可以比较高效的处理数组中存在相同元素的情况,其它特征与快速排序基本相同。
图示
- 初始情况
- 如果arr[
i++
- 如果arr[
arr[
arr[
i++, $lt++
- 如果arr[
arr[
arr[
gt--, $i++
- 最终状态
代码实现
/*--------------------第三种算法开始-------------------*/
//对arr[l...r]部分进行partition操作
// 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
function partition3(&$arr, $l, $r){
swap($arr, $l, rand($l, $r));
$v = $arr[$l];
$lt = $l;
$i = $l + 1;
$gt = $r + 1;
while($i < $gt){
if ($arr[$i] == $v) {
$i++;
}elseif ($arr[$i] < $v) {
swap($arr, $lt+1, $i);
$lt++;
$i++;
}else{
swap($arr, $gt-1, $i);
$gt--;
}
}
swap($arr, $l, $lt);
return array("lt" => $lt, "gt" => $gt);
}
function quickSort3(&$arr, $n){
__quickSort3($arr, 0, $n-1);
}
function __quickSort3(&$arr, $l, $r){
// if ($l >= $r) {
// return;
// }
//优化点1
if($r - $l <= 15){
insertSortCom($arr, $l, $r);
return;
}
$rt = partition3($arr, $l, $r);
$lt = $rt['lt'];
$gt = $rt['gt'];
__quickSort3($arr, $l, $lt -1);
__quickSort3($arr, $gt, $r);
}
/*--------------------第三种算法结束-------------------*/
复制代码
时间结果
quickSort2运行的时间为:0.43507099151611s
quickSort3运行的时间为:0.19537496566772s
复制代码
可以看出3路快速排序的算法还是很快的。
-------------------------华丽的分割线--------------------
看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励。
想了解更多,欢迎关注我的微信公众号:番茄技术小栈