归并和快速排序思想的延伸
Posted 番茄技术小栈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了归并和快速排序思想的延伸相关的知识,希望对你有一定的参考价值。
了解归并和快速排序算法背后的算法思想:分治思想,并对归并和快速排序进行扩展,解决经典算法问题:逆序对和第K大的算法问题
分治算法
顾名思义,分而治之,就是将原问题,分割成同等结构的子问题,之后将子问题逐一解决后,原问题也就得到了解决。
归并排序算法的延伸:逆序对
什么是逆序对?
对于一个长度为N的整数序列A,满足i < j 且 Ai > Aj.的数对(i,j)称为整数序列A的一个逆序。 通常逆序对可以表示一个数列的顺序程度,从小到大的数列逆序对为0,从大到小的逆序对为:(n*(n-1))/2;
分析
采用分而治之的思想,要求整个数列的逆序对,可以先求出前一半数列的逆序对,和后一半数列的逆序对,然后加上前一个数列和后一个数列所形成的逆序对,因为前后两个数列都是有序,直接在归并排序merge的时候求是非常简单的。
代码实现
function merge(&$arr, $l, $mid, $r){
$tmp = array();
$tmp = array_slice($arr, $l, $r-$l+1, true);
$res = 0;
//tmp现在为$arr的副本,以tmp为轴,重新赋值$arr
$i = $l;
$j = $mid+1;
for ($k=$l; $k <= $r; $k++) {
if ($i > $mid) {
$arr[$k] = $tmp[$j];
$j++;
}elseif ($j > $r) {
$arr[$k] = $tmp[$i];
$i++;
}elseif($tmp[$i] <= $tmp[$j]){
$arr[$k] = $tmp[$i];
$i++;
}else{
// 此时, 因为右半部分k所指的元素小
// 这个元素和左半部分的所有未处理的元素都构成了逆序数对
// 左半部分此时未处理的元素个数为 $mid - $i + 1;
$res += $mid - $i + 1;
$arr[$k] = $tmp[$j];
$j++;
}
}
return $res;
}
/**
* [__mergeSort 对区间为[l,r]的元素进行归并排序]
* @param [type] $arr [description]
* @param [type] $l [description]
* @param [type] $r [description]
* @return [type] [description]
*/
function __inversionCount(&$arr, $l, $r){
//此时为一个元素,不需要进行归并
if ($l >= $r) {
return 0;
}
$mid = (int)(($l + $r) / 2);
// 求出 arr[l...mid] 范围的逆序数
$res1 = __inversionCount($arr, $l, $mid);
// 求出 arr[mid+1...r] 范围的逆序数
$res2 = __inversionCount($arr, $mid+1, $r);
return $res1 + $res2 + merge($arr, $l, $mid, $r);
}
function inversionCount(&$arr, $n){
$res = __inversionCount($arr, 0, $n-1);
return $res;
}
结果
Array
(
[0] => 3
[1] => 0
[2] => 5
[3] => 5
[4] => 8
[5] => 0
[6] => 8
[7] => 5
)
逆序对的个数: 7
快速排序算法的延伸:数列中第k大的数
分析
解法1: 我们可以对这个乱序数组按照从大到小先行排序,然后取出前k大,总的时间复杂度为O(n*logn + k)。
解法2: 利用选择排序或交互排序,K次选择后即可得到第k大的数。总的时间复杂度为O(n*k)
解法3: 利用快速排序的思想,从数组S中随机找出一个元素X,把数组分为两部分Sa和Sb。Sa中的元素大于等于X,Sb中元素小于X。这时有两种情况:
Sa中元素的个数小于k,则Sb中的第k-|Sa|个元素即为第k大数;
Sa中元素的个数大于等于k,则返回Sa中的第k大数。时间复杂度近似为O(n)
这里我们采用第三种解法,时间复杂度为:O(n)+O(1/2)+O(1/4)+...+O(1/n), 当n为无穷大时候,时间复杂度约为O(n)
代码实现
//对arr[l...r]部分进行partition操作
// 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
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;
}
/**
* [__quickSort 对数组arr[l...r]进行快速排序]
* @param [type] &$arr [description]
* @param [type] $l [description]
* @param [type] $r [description]
* @return [type] [description]
*/
function __selectK(&$arr, $l, $r, $k){
if ($l == $r) {
return $arr[$l];
}
// 如果 k == p, 直接返回arr[p]
$p = partition($arr, $l, $r, $k);
if ($p == $k) {
return $arr[$p];
}elseif($p > $k){// 如果 k < p, 只需要在arr[l...p-1]中找第k小元素即可
return __selectK($arr, $l, $p-1, $k);
}else{// 如果 k > p, 则需要在arr[p+1...r]中找第k小元素
return __selectK($arr, $p+1, $r, $k);
}
}
// 寻找arr数组中第k小的元素
function selectK(&$arr, $n, $k){
assert($k >= 0 && $k <= $n);
return __selectK($arr, 0, $n-1, $k);
}
结果
Array
(
[0] => 9
[1] => 4
[2] => 10
[3] => 4
[4] => 7
[5] => 6
[6] => 3
[7] => 10
[8] => 7
[9] => 9
)
第3小的数为: 6
以上是关于归并和快速排序思想的延伸的主要内容,如果未能解决你的问题,请参考以下文章
C++ 随机化快速排序 最简单易懂的代码! 基于归并分区思想实现