归并和快速排序思想的延伸

Posted 番茄技术小栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了归并和快速排序思想的延伸相关的知识,希望对你有一定的参考价值。

了解归并和快速排序算法背后的算法思想:分治思想,并对归并和快速排序进行扩展,解决经典算法问题:逆序对和第K大的算法问题

分治算法

顾名思义,分而治之,就是将原问题,分割成同等结构的子问题,之后将子问题逐一解决后,原问题也就得到了解决。

归并排序算法的延伸:逆序对

什么是逆序对?

对于一个长度为N的整数序列A,满足i < j 且 Ai > Aj.的数对(i,j)称为整数序列A的一个逆序。 通常逆序对可以表示一个数列的顺序程度,从小到大的数列逆序对为0,从大到小的逆序对为:(n*(n-1))/2;

分析

采用分而治之的思想,要求整个数列的逆序对,可以先求出前一半数列的逆序对,和后一半数列的逆序对,然后加上前一个数列和后一个数列所形成的逆序对,因为前后两个数列都是有序,直接在归并排序merge的时候求是非常简单的。

代码实现

 
   
   
 
  1. function merge(&$arr, $l, $mid, $r){

  2.    $tmp = array();

  3.    $tmp = array_slice($arr, $l, $r-$l+1, true);

  4.    $res = 0;

  5.    //tmp现在为$arr的副本,以tmp为轴,重新赋值$arr

  6.    $i = $l;

  7.    $j = $mid+1;

  8.    for ($k=$l; $k <= $r; $k++) {

  9.        if ($i > $mid) {

  10.            $arr[$k] = $tmp[$j];

  11.            $j++;

  12.        }elseif ($j > $r) {

  13.            $arr[$k] = $tmp[$i];

  14.            $i++;

  15.        }elseif($tmp[$i] <= $tmp[$j]){

  16.            $arr[$k] = $tmp[$i];

  17.            $i++;

  18.        }else{

  19.            // 此时, 因为右半部分k所指的元素小

  20.            // 这个元素和左半部分的所有未处理的元素都构成了逆序数对

  21.            // 左半部分此时未处理的元素个数为 $mid - $i + 1;

  22.            $res += $mid - $i + 1;

  23.            $arr[$k] = $tmp[$j];

  24.            $j++;

  25.        }

  26.    }

  27.    return $res;

  28. }

  29. /**

  30. * [__mergeSort 对区间为[l,r]的元素进行归并排序]

  31. * @param  [type] $arr [description]

  32. * @param  [type] $l   [description]

  33. * @param  [type] $r   [description]

  34. * @return [type]      [description]

  35. */

  36. function __inversionCount(&$arr, $l, $r){

  37.    //此时为一个元素,不需要进行归并

  38.    if ($l >= $r) {

  39.        return 0;

  40.    }

  41.    $mid = (int)(($l + $r) / 2);

  42.    // 求出 arr[l...mid] 范围的逆序数

  43.    $res1 = __inversionCount($arr, $l, $mid);

  44.    // 求出 arr[mid+1...r] 范围的逆序数

  45.    $res2 = __inversionCount($arr, $mid+1, $r);

  46.    return $res1 + $res2 + merge($arr, $l, $mid, $r);

  47. }

  48. function inversionCount(&$arr, $n){

  49.    $res = __inversionCount($arr, 0, $n-1);

  50.    return $res;

  51. }

结果

 
   
   
 
  1. Array

  2. (

  3.    [0] => 3

  4.    [1] => 0

  5.    [2] => 5

  6.    [3] => 5

  7.    [4] => 8

  8.    [5] => 0

  9.    [6] => 8

  10.    [7] => 5

  11. )

  12. 逆序对的个数: 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)

代码实现

 
   
   
 
  1. //对arr[l...r]部分进行partition操作

  2. // 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]

  3. function partition(&$arr, $l, $r){

  4.    swap($arr, $l, rand($l, $r));

  5.    $v = $arr[$l];

  6.    $j = $l;

  7.    for ($i=$l+1; $i <= $r ; $i++) {

  8.        if ($arr[$i] < $v) {

  9.            swap($arr, $j+1, $i);

  10.            $j++;

  11.        }

  12.    }

  13.    swap($arr, $l, $j);

  14.    return $j;

  15. }

  16. /**

  17. * [__quickSort 对数组arr[l...r]进行快速排序]

  18. * @param  [type] &$arr [description]

  19. * @param  [type] $l    [description]

  20. * @param  [type] $r    [description]

  21. * @return [type]       [description]

  22. */

  23. function __selectK(&$arr, $l, $r, $k){

  24.    if ($l == $r) {

  25.        return $arr[$l];

  26.    }

  27.    // 如果 k == p, 直接返回arr[p]

  28.    $p = partition($arr, $l, $r, $k);

  29.    if ($p == $k) {

  30.        return $arr[$p];

  31.    }elseif($p > $k){// 如果 k < p, 只需要在arr[l...p-1]中找第k小元素即可

  32.        return __selectK($arr, $l, $p-1, $k);

  33.    }else{// 如果 k > p, 则需要在arr[p+1...r]中找第k小元素

  34.        return __selectK($arr, $p+1, $r, $k);

  35.    }

  36. }

  37. // 寻找arr数组中第k小的元素

  38. function selectK(&$arr, $n, $k){

  39.    assert($k >= 0 && $k <= $n);

  40.    return __selectK($arr, 0, $n-1, $k);

  41. }

结果

 
   
   
 
  1. Array

  2. (

  3.    [0] => 9

  4.    [1] => 4

  5.    [2] => 10

  6.    [3] => 4

  7.    [4] => 7

  8.    [5] => 6

  9.    [6] => 3

  10.    [7] => 10

  11.    [8] => 7

  12.    [9] => 9

  13. )

  14. 3小的数为: 6


以上是关于归并和快速排序思想的延伸的主要内容,如果未能解决你的问题,请参考以下文章

C++ 随机化快速排序 最简单易懂的代码! 基于归并分区思想实现

算法笔记 7 归并排序和快速排序

算法排序02——归并排序介绍及其在分治算法思想上与快排的区别(含归并代码)

排序算法中——归并排序和快速排序

算法链表的快速排序和归并排序

算法链表的快速排序和归并排序