歸併和快速排序思想的延伸

前面學習了歸併和快速排序算法,如今來了解歸併和快速排序算法背後的算法思想:分治思想,並對歸併和快速排序進行擴展,解決經典算法問題:逆序對和第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
複製代碼

快速排序算法的延伸:數列中第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)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

看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。排序

我的博客番茄技術小棧掘金主頁

想了解更多,歡迎關注個人微信公衆號:番茄技術小棧

番茄技術小棧
相關文章
相關標籤/搜索