前面学习了归并和快速排序算法,如今来了解归并和快速排序算法背后的算法思想:分治思想,并对归并和快速排序进行扩展,解决经典算法问题:逆序对和第K大的算法问题php
原文请访问个人技术博客番茄技术小栈算法
顾名思义,分而治之,就是将原问题,分割成同等结构的子问题,以后将子问题逐一解决后,原问题也就获得了解决。数组
对于一个长度为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;
}
复制代码
结果ui
Array
(
[0] => 3
[1] => 0
[2] => 5
[3] => 5
[4] => 8
[5] => 0
[6] => 8
[7] => 5
)
逆序对的个数: 7
复制代码
这里咱们采用第三种解法,时间复杂度为:O(n)+O(1/2)+O(1/4)+...+O(1/n), 当n为无穷大时候,时间复杂度约为O(n)spa
//对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);
}
复制代码
结果code
Array
(
[0] => 9
[1] => 4
[2] => 10
[3] => 4
[4] => 7
[5] => 6
[6] => 3
[7] => 10
[8] => 7
[9] => 9
)
第3小的数为: 6
复制代码
-------------------------华丽的分割线--------------------cdn
看完的朋友能够点个喜欢/关注,您的支持是对我最大的鼓励。排序
想了解更多,欢迎关注个人微信公众号:番茄技术小栈