尋找數組中的第K大的元素,多種解法以及分析


遇到了一個很簡單而有意思的問題,可以看出不同的算法策略對這個問題求解的優化過程。
問題:尋找數組中的第K大的元素。

最簡單的想法是直接進行排序,算法復雜度是O(N*logN)。這么做很明顯比較低效率,因為不要求別的信息只要計算出第K大的元素。當然,如果在某種情況下需要頻繁訪問第K大的元素就可以先進行一次排序在直接得出結果。

第一種方式是這樣,用選擇排序,冒泡法,或者交換排序這類的排序,對前K個元素進行排序。這三種算法也許不是最快的排序算法。但是都有個性質:計算出最大(小)的元素的算法復雜度是O(N)。這個過程不能中斷,要計算第三大的元素必須建立在已經算出第二大的元素的基礎上(因為每次都是計算當前數組最大)。所以它的算法復雜度是O(N*K);

第二種方法是用快速排序的思想。快速排序每次把一個元素交換到正確的位置,同時把左邊的都方上大的,右邊都放上小的。這個算法每一次選取一個樞紐元,排序之后,查看樞紐元的位置。如果它的位置大於K,就說明,要求出前面一個子序列的第K大的元素。反之,如果小於K,就說明要求出在后面一個序列的第K - 前一個序列的長度個元素。

如此,就把這個問題改變成了一個可以用快排思想解決的問題。對於快速排序,算法復雜度是O(N*logN)。而這個算法的算法復雜度是O(N)。為什么呢?

其實這個地方的算法復雜度分析很有意思。第一次交換,算法復雜度為O(N),接下來的過程和快速排序不同,快速排序是要繼續處理兩邊的數據,再合並,合並操作的算法復雜度是O(1),於是總的算法復雜度是O(N*logN)(可以這么理解,每次交換用了N,一共logN次)。但是這里在確定樞紐元的相對位置(在K的左邊或者右邊)之后不用再對剩下的一半進行處理。也就是說第二次插入的算法復雜度不再是O(N)而是O(N/2),這不還是一樣嗎?其實不一樣,因為接下來的過程是1+1/2+1/4+........ < 2,換句話說就是一共是O(2N)的算法復雜度也就是O(N)的算法復雜度。

這個算法目前我在數據結構和算法書上和劍指Offer上都看到過。算是一種很經典很經典的算法。原因是因為他通過努力把算法復雜度在每次遞歸中下降一些,最終讓整個算法的復雜度下降極多,算是一種十分聰明的做法。

第三種方法很是簡單,但是使用它需要某個條件,也就是輸入數組的取值范圍很小,最好的情況是能形成完全分布,也就是1000大小的數組里面的數字是從1到1000這樣子。首先,生成一個能夠完全裝下原數組的數組,這個地方的裝下是指數組大小等於原數組最大元素(也許還有優化,但這么描述簡單一點),比如原數組是[1,2,3,4,5],我要生成的數組大小是5,如果原數組是[5,3,6,10],我要生成的數組大小是10。接下來遍歷原數組,把每一個元素放到第二個數組對應的下標處,5就放在下標為5的地方(實際過程中要減1,因為是數組從0開始)。放的過程中增加元素值用來統計這個元素出現的次數。這一過程算法復雜度是O(N)。接下來,再遍歷生成的數組,找出第K大的元素。
這個過程的算法復雜度是多少呢?其實這個和原數組很有關系,原數組越離散也就越糟糕。比如原數組是[1,1000],這樣就十分糟糕。第二部的算法復雜度是O(M),M是前數組的最大值。總的算法復雜度O(N)+O(M);

由此可見第三種方法在這個問題的處理非常不好。雖然第三種方法限制頗多(浮點型和負數還有對原數組大小的要求),但是第三種方法的實質是一種散列。就是把原來的映射關系變成了一種反映射。也就是說如果形成了數據與地址的直接映射。但是這種映射的問題也體現的很明顯,它這么做也只能算是撿了個漏子,如果輸入數組稍微一邊,還是一樣要用hash算法計算其hash值。再把hash值映射到地址上。

第四種方法是用二叉堆來做。對大小為N的數組構建二叉堆的算法復雜度是O(N)。然后每次下濾的算法復雜度是O(logN),一共下濾K次,算法復雜度是O(N+K*logN)。

這種做法比較適合用來處理輸入數組極大的情況,原因是如果輸入數組大到不能放入內存,那么構建二叉堆(優先隊列)的時候就可以只構造一個K個元素的優先隊列。如果下一個元素比這個最小堆的堆頂還小就直接pass。第二個原因是算法二在對付一個極大的輸入隊列的時候算法復雜度的一個常數會很大。

 


免責聲明!

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



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