1. 程式人生 > 其它 >《劍指offer》Partition函式及其Partition函數週邊

《劍指offer》Partition函式及其Partition函數週邊

技術標籤:資料結構劍指offerleetcodepartition中位數

  Partion函式最早出現於快速排序演算法,但其代表的分治思想,在《劍指offer》這本書中求陣列中位數(面試題39,陣列中出現次數超過一半的數字)中也有所涉及,將其彙總在一起,稱之為Partition函式及其周邊。
  快速排序完整程式碼Github連線
  陣列中位數完整Github連線
  若開啟Github網址有困難,可以參考我的這篇文章程式設計師常用網站加速方法

1. 快速排序

 1.1 快排之Partion函式

  Partition函式是快速排序的核心。下面程式碼是我基於c++語言對快速排序演算法的實現。

#include <iostream>
#include <ctime>

// exch array[i] and array[j]
void Exch(int *array, int i, int j);
void RandShuffle(int *array, int lo, int hi);

//find j, and make array[i] <= array[j], when 0 <=i <= j-1
//            make array[i] >= array[j], when j < j+1 < len -1
int
Partition(int * array, int len, int lo, int hi) { int i = lo; int j = hi+1 ;//後撤一步 while (true) { while (array[++i] <= array[lo] && i < hi);//停在“大”數字,或者遞減陣列,一直到hi while (array[--j] >= array[lo] && j > lo);//停在“小”數字,或者遞增陣列,一直到lo if (i >= j) break;//此處i和j的最終停止範圍,j<i,除非陣列本身遞減此時i=j
Exch(array, i, j); } Exch(array, j, lo);//此處i和j不等價,因為99%的情形下,j=i-1;1%的情形始,i=j=hi(遞減) return j; } int QuickSort(int *array, int len, int lo, int hi) { if (lo >= hi) return 1;//遞迴終止條件,遞迴子問題的陣列已經變成一個元素,此時就可以return了。 RandShuffle(array, lo, hi);//每一次打亂遞迴子陣列的順序,使得達到一種均衡效率。 //遞迴呼叫必須有一個父問題---》(簡單)子問題的過程 int target = Partition(array, len, lo, hi); QuickSort(array, len, lo, target - 1); QuickSort(array, len, target + 1, hi); return 1; } int main(int argv, char * argc) { int array[5] = { 0, 4, 3, 2, 1}; int len = sizeof(array) / sizeof(int); //陣列輸入檢查 if (array == nullptr || len < 1) { return -1; } //主體 int lo = 0; int hi = len - 1; QuickSort(array, len, lo, hi); ... return 0; }

 1.2 快排演算法中,最終狀態下i和j的位置關係分析

  首先,要強調一點最終是lo和j指向的元素進行,因為J最終會停留在小於等於array[lo]的位置,而i則大部分情形下會停留在大於等於array[lo]的位置,就像這樣。
  array的lo和hi段普通情形:滿足 j < i j<i j<i,且 j = i − 1 j=i-1 j=i1
在這裡插入圖片描述

  array的lo和hi段遞減情形:此時 i = j = h i i=j=hi i=j=hi
在這裡插入圖片描述

array的lo和hi段遞增情形:此時 j < i j<i j<i,且 j = i − 1 j=i-1 j=i1
在這裡插入圖片描述
  綜上所述,i和j並非等價的關係,大部分情形下,i=j+1,且j代表的總是小於等於a[lo]的元素,因此將其與a[lo]交換,這樣符合交換後,小—target—大的順序。

 1.3 快排演算法的計算複雜度

  網上已經有不少的證明計算複雜度的部落格 ,我認為只需大概理解理想情況下的每次等分的證明情形即可。
  然後需要有一個直觀反射弧,如果 T ( l ) = 2 T ( l − 1 ) + n T(l)=2T(l-1)+n T(l)=2T(l1)+n這樣的推導通式,其計算複雜度為 o ( n l o g n ) o(nlogn) o(nlogn)。再有就是,記住 T ( 1 ) = 0 T(1)=0 T(1)=0,演算法的迭代次數為二叉樹的高度 l o g n logn logn,可以有助於自己必要時刻隨時推出該計算複雜度。

2. Partition在求陣列中位數中的應用

  面試題39:陣列中出現次數超過一半的數字。
  分析,出現次數超過一半的數字,等同於該陣列的中位數。分析見下圖

  • 若陣列元素個數n為奇數:對於排完序陣列
    在這裡插入圖片描述

  • 若陣列元素個數n為偶數:對於排完序陣列

在這裡插入圖片描述
  但如果仔細分析,上述排序陣列,並非需要嚴格有序,如果我們去先排序的話,最低的時間複雜度也需要 o ( n l o g n ) o(nlogn) o(nlogn),但事實上,只要保證[0+len-1]/2處元素,大於其左邊元素,小於其右邊元素,此時其即為中位數,這個要求顯然比嚴格有序要低,後面會證明,達到這種效果,最快的演算法為 o ( n ) o(n) o(n)
  看到“保證[0+len-1]/2處元素,大於其左邊元素,小於其右邊元素”這種要求,應該可以聯絡到我們已有的一些"武器庫" : Partition函式。
  只不過這次我們不再呼叫遞迴演算法,而是採用while迴圈,這樣當找到中位數時理解可以中止程式(遞迴比較適用於把所有的元素都理順)。
  核心程式碼如下:

	int target = hi / 2;
	int return_value = Partition(array, lo, hi);

	while (return_value != target)
	{
		if (return_value > target)
		{
			lo = lo;
			hi = return_value-1;
			return_value = Partition(array, lo, hi);
		}
		else
		{
			lo = return_value+1;
			hi = hi;
			return_value = Partition(array, lo, hi);

		}

	}

2.2 Partition函式求陣列的中位數補充知識

  

時間複雜度時o(n)。基於Partition函式的演算法的時間複雜度的分析不是很直觀,本書限於篇幅不作詳細討論,感興趣的讀者可以參考《演算法導論》等書籍的相關章節。

  該Partition函式會修改陣列的內容。事實上對於“陣列中超過一半的數字”除了從中位數的角度出發,還可以從原始超過一半來出發,我把這種解法稱之為“動物世界”,具體見《劍指offer》雜篇