堆排序與1億數據中找出100大(小)的數據


堆排序的步驟:

例如:從小到大排序

1.由給定元素建立一棵完全二叉樹

2.調整該完全二叉樹使其成為最大堆

  因為堆的存儲結構是數組形式,堆排序的實質就是對數組中的元素進行排序,如果按照從小到大排序的話,就說明數組最后一個元素最大,而最頂錐第一個元素和最后一個元素交換位置剛好滿足;

  同理,如果要從大到小排序的話,應該建立一個最小堆

3.不斷交換堆頂元素與堆未元素,進行從小到大的排序,直至輸出所有堆頂元素。

一個很好的參考例子:https://www.cnblogs.com/jingmoxukong/p/4303826.html

 

內部排序與外部排序

排序大的分類可以分為兩種:內排序和外排序。

在排序過程中,全部記錄存放在內存,則稱為內排序,如果排序過程中需要使用外存,則稱為外排序。

一般來說外排序分為兩個步驟:預處理和合並排序。首先,根據可用內存的大小,將外存上含有n個紀錄的文件分成若干長度為t的子文件(或段);其次,利用內部排序的方法,對每個子文件的t個紀錄進行內部排序。這些經過排序的子文件(段)通常稱為順串(run),順串生成后即將其寫入外存。這樣在外存上就得到了m個順串(m=[n/t])。最后,對這些順串進行歸並,使順串的長度逐漸增大,直到所有的待排序的幾率成為一個順串為止。

內排序可以分為以下幾類:

(1)、插入排序:直接插入排序、折半插入排序、希爾排序。

(2)、選擇排序:簡單選擇排序、堆排序。

(3)、交換排序:冒泡排序、快速排序。

外排序可以分為一下幾類(既使用內部存儲也使用外部存儲,內存不夠時建議使用):

(4)、歸並排序

(5)、基數排序

 

穩定性:就是能保證排序前兩個相等的數據其在序列中的先后位置順序與排序后它們兩個先后位置順序相同。再簡單具體一點,如果A i == A j,Ai 原來在 Aj 位置前,排序后 Ai  仍然是在 Aj 位置前。

不穩定:簡單選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法

穩定:冒泡排序、直接插入排序、折半插入排序,歸並排序和基數排序都是穩定的排序算法。

平均時間復雜度

O(n^2):直接插入排序,簡單選擇排序,冒泡排序。

在數據規模較小時(9W內),直接插入排序,簡單選擇排序差不多。當數據較大時,冒泡排序算法的時間代價最高。性能為O(n^2)的算法基本上是相鄰元素進行比較,基           本上都是穩定的。

O(nlogn):快速排序,歸並排序,希爾排序,堆排序。

其中,快排是最好的, 其次是歸並和希爾,堆排序在數據量很大時效果明顯。

 

1億數據中找出前k大的數據(內存不夠的情境,磁盤足夠大)

  當內存不夠大,一般不選用將所有元素全部加載,不然超過內存限制。這里排序又分為內部排序和外部排序。

1.在內存中新建一個k的小頂錐(直接和),如果插入的元素比錐頂大,則把錐頂的元素扔掉,然后重新調整使其變成小頂錐,重復該過程,最后剩下的k個元素就是最大的。

  為什么不采用大頂錐呢,因為插入的元素如果比錐頂大的話,它就會替換錐頂,最終結果只是錐頂中的元素是最大的,無法挑選出最大的k個;

注意:堆排序實際上僅僅是利用最開始的那個元素,因為堆頂的要素要么是最大,要么是最小,其余的元素並不是有序排列的,因此,當堆得大小為k時,意味着着k個數組元素就是最大的,要從1億個數字中挑選出前k個大數,就說明比k小的數字都比丟掉了,如何實現,就采用小頂錐的方式,每次都扔掉錐頂最小的元素,扔到最后,還剩下k個大數;

同理,當找出k個最小數字的話,意味着剩下的這k個數組元素為最小的,這就要確保每次排序要把大的都扔掉,因此要新建大頂堆。

2.采用分治法,划分為若干個小文件(通常利用hash(x)%M,m是划分的大小,來進行划分),每個文件依次找出前k大個,然后放在一起再找出k大個。每次文件找出k大個,可以利用快速排序,每次快速排序分為兩部分(一邊是小的,一邊是大的),如果大的部分長度大於k,接着利用快速排序,直到大的部分小於k(假設為n)(說明這些肯定是這么多數中最大的),然后對剩下的部分進行快排,找出前(k-n)個最大的,然后在對剩下的進行快排,最后會發現遞歸到最后,只需要找到最大的那個數就行了。這種思想是分治思想,一直分下去。

如果不考慮分治思想的話,可以利用快速排序,一下子排好序,然后找出前k個最大的就行了

3.hash,采用hash主要是先去重,然后再利用分治或者是堆排序進行查找

 

補充:處理環境

實際運行:

        實際上,最優的解決方案應該是最符合實際設計需求的方案,在時間應用中,可能有足夠大的內存,那么直接將數據扔到內存中一次性處理即可,也可能機器有多個核,這樣可以采用多線程處理整個數據集。

       下面針對不容的應用場景,分析了適合相應應用場景的解決方案。

(1)單機+單核+足夠大內存

        如果需要查找10億個查詢次(每個占8B)中出現頻率最高的10個,考慮到每個查詢詞占8B,則10億個查詢次所需的內存大約是10^9 * 8B=8GB內存。如果有這么大內存,直接在內存中對查詢次進行排序,順序遍歷找出10個出現頻率最大的即可。這種方法簡單快速,使用。然后,也可以先用HashMap求出每個詞出現的頻率,然后求出頻率最大的10個詞。

(2)單機+多核+足夠大內存

        這時可以直接在內存總使用Hash方法將數據划分成n個partition,每個partition交給一個線程處理,線程的處理邏輯同(1)類似,最后一個線程將結果歸並。

        該方法存在一個瓶頸會明顯影響效率,即數據傾斜。每個線程的處理速度可能不同,快的線程需要等待慢的線程,最終的處理速度取決於慢的線程。而針對此問題,解決的方法是,將數據划分成c×n個partition(c>1),每個線程處理完當前partition后主動取下一個partition繼續處理,知道所有數據處理完畢,最后由一個線程進行歸並。

(3)單機+單核+受限內存

        這種情況下,需要將原數據文件切割成一個一個小文件,如次啊用hash(x)%M,將原文件中的數據切割成M小文件,如果小文件仍大於內存大小,繼續采用Hash的方法對數據文件進行分割,知道每個小文件小於內存大小,這樣每個文件可放到內存中處理。采用(1)的方法依次處理每個小文件。

(4)多機+受限內存

        這種情況,為了合理利用多台機器的資源,可將數據分發到多台機器上,每台機器采用(3)中的策略解決本地的數據。可采用hash+socket方法進行數據分發。

 

        從實際應用的角度考慮,(1)(2)(3)(4)方案並不可行,因為在大規模數據處理環境下,作業效率並不是首要考慮的問題,算法的擴展性和容錯性才是首要考慮的。算法應該具有良好的擴展性,以便數據量進一步加大(隨着業務的發展,數據量加大是必然的)時,在不修改算法框架的前提下,可達到近似的線性比;算法應該具有容錯性,即當前某個文件處理失敗后,能自動將其交給另外一個線程繼續處理,而不是從頭開始處理。

        top K問題很適合采用MapReduce框架解決,用戶只需編寫一個Map函數和兩個Reduce 函數,然后提交到Hadoop(采用Mapchain和Reducechain)上即可解決該問題。具體而言,就是首先根據數據值或者把數據hash(MD5)后的值按照范圍划分到不同的機器上,最好可以讓數據划分后一次讀入內存,這樣不同的機器負責處理不同的數值范圍,實際上就是Map。得到結果后,各個機器只需拿出各自出現次數最多的前N個數據,然后匯總,選出所有的數據中出現次數最多的前N個數據,這實際上就是Reduce過程。對於Map函數,采用Hash算法,將Hash值相同的數據交給同一個Reduce task;對於第一個Reduce函數,采用HashMap統計出每個詞出現的頻率,對於第二個Reduce 函數,統計所有Reduce task,輸出數據中的top K即可。

        直接將數據均分到不同的機器上進行處理是無法得到正確的結果的。因為一個數據可能被均分到不同的機器上,而另一個則可能完全聚集到一個機器上,同時還可能存在具有相同數目的數據。

(這句話的意思是,MapReduce處理任務時,mapreduce在map階段會對文件進行划分和排序,然后在reduce階段進行歸並排序。

partition是分割map每個節點的結果,按照key分別映射給不同的reduce,也是可以自定義的。這里其實可以理解歸類。
我們對於錯綜復雜的數據歸類。比如在動物園里有牛羊雞鴨鵝,他們都是混在一起的,但是到了晚上他們就各自牛回牛棚,羊回羊圈,雞回雞窩。partition的作用就是把這些數據歸類。只不過在寫程序的時候,mapreduce使用哈希HashPartitioner幫我們歸類了。)所以,數據並不是均分的,而是利用mapreduce自身的函數進行分類。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM