這道題最簡單的思路是排序,時間複雜度是o(nlog(n))。但是這樣做在那n-k 個數的排序上浪費了資源。
改進一下,將陣列的前k個數作為最小的k數的快取。從第k+1個數開始遍歷,如果有比前k個數小的,就將其和前k個數那個較大交換。
照這個思路,可以引入乙個結構,使得前k個數總是最大的數在第乙個,這樣每次遇到乙個數值需要和前k個數中排在第一位的那個最大數比較就可以了。
這個結構就是最大堆。
思路一:維護乙個maxsize為k的最大堆,用來存放這k個數,遍歷陣列,如果堆未滿,插入堆中。如果堆已滿,如果數字比堆的根節點小,則刪除堆的根節點,向堆中插入這個數字。
時間複雜度為 o(nlog(k))。如果求最大的k個數,就是使用最小堆了。
思路二:其實我們也可以在n個整數的範圍內構建最小堆,然後每次將堆頂的最小值取走,然後調整,再取走堆頂值,取k次,自然就找到了最小的k個數。
這樣做的時間複雜度是多少呢?基於n個無序數構建最小堆,時間是o(n),而取走堆頂元素,將堆末元素放到堆頂重新調整的時間複雜度是 o(h),h為堆高度,這裡堆高度h總是logn保持不變。
因此時間複雜度是o(n+k*logn),這個思路可以再改進:每次重新調整,只需要基於堆頂部k層進行調整,堆末元素被放到堆頂後,最多隻需要下調至k層即可,因此調整的時間複雜度成了o(k)。這樣做的時間複雜度成了 o(n+k*k)。
可以證明o(n+k*k)
我們別忘了思路二的初始化需要基於整個n個數構建堆,而思路一則不需要。實際情況下,我們往往需要基於大量分布式儲存的n個數找出k個數,例如:在分布式存在10臺伺服器上的總大小大約2t的訪問頁面記錄中找出訪問量最高的100個頁面。想要跨10臺伺服器整體構建最大堆,顯然不現實,而思路一則只需要維護乙個100的最小堆,然後順序遍歷10臺伺服器上的記錄即可。
因此思路一更加具有實際意義。
這裡給出思路一的實現。
如果真正寫**,要知道堆是沒有stl的,也就是說我們要自己實現。
書中使用了multiset,multiset和set一樣,都是基於紅黑樹實現,區別是set不允許重複而multiset允許重複。
那麼multiset和vector區別在**?區別在於multiset支援排序。multiset的插入和刪除的時間複雜度也都為 o(logk)
樹上**:
typedef multiset >intset;還有第三種思路,快速選擇 法。這個方法參考了july的文章,以及csdn上huagong_adu所寫的另一篇對它的解讀。typedef multiset
>::iterator setiterator;
void getleastnumbers_solution2(const vector& data, intset& leastnumbers, int
k) }}}
試想一下,如果存在這麼乙個數,它在陣列的第k位,而且正好前k-1個數比它小,後n-k個數比它大。那麼這個時候,我們只需要這個數前面的那k個數即可。
這是不有點像快排中的 pivot?
先選取乙個數作為基準比較數(作者稱為「樞紐元」,即pivot),用快排方法把資料分為兩部分sa和sb。
如果k< |sa|( |sa|表示sa的大小),則對sa部分用同樣的方法繼續操作;
如果k= |sa|,則sa是所求的數;
如果k= |sa| + 1,則sa和這個pivot一起構成所求解;
如果k> |sa| + 1,則對sb部分用同樣的方法查詢最小的(k- |sa|-1)個數(其中sa和pivot已經是解的一部分了)。
當pivot選擇的足夠好的時候,可以做到時間複雜度是o(n)
那麼如何選擇乙個好的pivot?
這裡必須提bfprt演算法,這個演算法就是為了尋找陣列中第k小的數而設。
bfprt是一種獲得較優秀pivot的方法。其過程有乙個flash動畫作為演示。其過程是將n個數5個一組劃分,求出沒乙個5元組的中位數,然後再基於這些中位數繼續求中位數,這個重複的次數應該是由k的大小來定,隨後將選出的中位數作為pivot,將小於pivot的數交換到陣列的左側。接著基於這些小於pivot的值,繼續通過「尋找中位數,定pivot,交換法」 來縮小範圍,直到最後在乙個較小範圍內找到k個最小值。
附加,最大堆的插入刪除操作:
template class maxheap()//建立堆,記住堆的根節點是element[1]
//這樣做的目的,是為了可以使用i/2來訪問其父結點,可以使用2i表示其左結點。
template maxheap::maxheap(
intmaxsize)
//插入節點,其實就是從末節點開始,往上找,直到找乙個地方,讓新元素放進去後,比它的父節點小。
//找的過程中,找過的節點下移到它的子女所在的平台,為了給新結點騰地方
//找到這個地方後,就把新結點安進去
template bool
maxheap::insert(t ele)
size ++;
int i =size;
while(i > 1
) element[i] =ele;
return
true;}
//刪除節點,就是將根節點刪除,但是我們知道,接下來必須調整,因為陣列中間不能有空缺。
//調整的過程其實就是將陣列末尾的那個元素找個地方放。根節點原來的空位不行,就把空位往下挪,
//一直挪到這個空位放末尾節點合適了,所謂合適,就是末尾節點放在這裡比其子女都大,
//(就算中間沒有合適的位置,移到葉節點,必然合適) ,把末尾節點放進去。
template bool
maxheap::deletemax()
t temp = elements[size]; //
末尾節點記下來。
size--;
int i = 1
;
while(2*i <= size)
elementp[i] = temp; //
就算一直找到最後也沒找到,這個時候i已經是某乙個葉節點,而且這個葉節點的值已經轉移到父節點上,我們把temp付給它就可以
return
true
;}
最小的K個數
問題描述 給定的n個整數,計算其中最小的k個數。最直觀的解法莫過於將n個數按公升序排列後輸出前k個。但是就效率來看,這種方法並不是最理想的。一種改進方法是借助快速排序中對陣列的劃分,以第k個元素對陣列進行劃分,使得比第k個數字小的數字都在其左邊,比其大的數字都在它的右邊。void swap int ...
最小的K個數
從 陣列中出現次數超過一半的數字 得到啟發,同樣可以基於partition函式來解決。一 o n 演算法 void getleastnumbers int input,int n,int output,int k else for int i 0 i k i output i input i 二 o...
最小的K個數
輸入n個整數,找出其中最小的k個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,如果不讓使用sort的話,自己實現乙個,或者依次選取最小的 class solution public vectorgetleastnumbers solution vectori...