漫畫:尋找無序數(shù)組的第k大元素

————— 第二天 —————

題目是什么意思呢?比如給定的無序數(shù)組如下:

如果 k=6,也就是要尋找第6大的元素,這個元素是哪一個呢?

顯然,數(shù)組中第一大的元素是24,第二大的元素是20,第三大的元素是17 ...... 第6大的元素是9

方法一:排序法

這是最容易想到的方法,先把無序數(shù)組從大到小進行排序,排序后的第k個元素,自然就是數(shù)組中的第k大元素。

方法二:插入法

維護一個長度為k的數(shù)組A的有序數(shù)組,用于存儲已知的k個較大的元素。

接下來遍歷原數(shù)組,每遍歷到一個元素,和數(shù)組A中最小的元素相比較,如果小于等于數(shù)組A的最小元素,繼續(xù)遍歷;如果大于數(shù)組A的最小元素,則插入到數(shù)組A中,并把曾經(jīng)的最小元素“擠出去”。

比如k=3,先把最左側的7,5,15三個數(shù)有序放入數(shù)組A當中,代表當前最大的三個數(shù)。

這時候,遍歷到3, 由于3<5,繼續(xù)遍歷。

接下來遍歷到17,由于17>5,插入到數(shù)組A的合適位置,類似于插入排序,并把原先最小的元素5“擠出去”。

繼續(xù)遍歷原數(shù)組,一直遍歷到數(shù)組的最后一個元素......

最終,數(shù)組A中存儲的元素是24,20,17,代表著整個數(shù)組中最大的3個元素。此時數(shù)組A中的最小的元素17就是我們要尋找的第k大元素。

————————————

什么是二叉堆?不太了解的小伙伴可以先看看這一篇:

漫畫:什么是二叉堆?(修正版)

簡而言之,二叉堆是一種特殊的完全二叉樹,它包含大頂堆和小頂堆兩種形式。

其中小頂堆的特點,是每一個父節(jié)點都小于等于自己的子節(jié)點。要解決這個算法題,我們可以利用小頂堆的特性。

方法三:小頂堆法

維護一個容量為k的小頂堆,堆中的k個節(jié)點代表著當前最大的k個元素,而堆頂顯然是這k個元素中的最小值

遍歷原數(shù)組,每遍歷一個元素,就和堆頂比較,如果當前元素小于等于堆頂,則繼續(xù)遍歷;如果元素大于堆頂,則把當前元素放在堆頂位置,并調(diào)整二叉堆(下沉操作)。

遍歷結束后,堆頂就是數(shù)組的最大k個元素中的最小值,也就是第k大元素。

假設k=5,具體的執(zhí)行步驟如下:

1.把數(shù)組的前k個元素構建成堆。

2.繼續(xù)遍歷數(shù)組,和堆頂比較,如果小于等于堆頂,則繼續(xù)遍歷;如果大于堆頂,則取代堆頂元素并調(diào)整堆。

遍歷到元素2,由于 2<3,所以繼續(xù)遍歷。

遍歷到元素20,由于 20>3,20取代堆頂位置,并調(diào)整堆。

遍歷到元素24,由于 24>5,24取代堆頂位置,并調(diào)整堆。

以此類推,我們一個一個遍歷元素,當遍歷到最后一個元素8的時候,小頂堆的情況如下:

3.此時的堆頂,就是堆中的最小值,也就是數(shù)組中的第k大元素。

這個方法的時間復雜度是多少呢?

1.構建堆的時間復雜度是 O(k)

2.遍歷剩余數(shù)組的時間復雜度是O(n-k)

3.每次調(diào)整堆的時間復雜度是 O(logk)

其中2和3是嵌套關系,1和2,3是并列關系,所以總的最壞時間復雜度是O((n-k)logk + k)。當k遠小于n的情況下,也可以近似地認為是O(nlogk)。

這個方法的空間復雜度是多少呢?

剛才我們在詳細步驟中把二叉堆單獨拿出來演示,是為了便于理解。但如果允許改變原數(shù)組的話,我們可以把數(shù)組的前k個元素“原地交換”來構建成二叉堆,這樣就免去了開辟額外的存儲空間。

因此,方法的空間復雜度是O(1)。

/**
 * 尋找第k大的元素
 * @param array 待調(diào)整的堆
 * @param k 第幾大
 */
public static int findNumberK(int[] array, int k){
 //1.用前k個元素構建小頂堆
 buildHeap(array, k);
 //2.繼續(xù)遍歷數(shù)組,和堆頂比較
 for(int i=k; i<array.length;i++){
 if(array[i] > array[0]){
 array[0] = array[i];
 downAdjust(array, 0, k);
 }
 }
 //3.返回堆頂元素
 return array[0];
}
/**
 * 構建堆
 * @param array 待調(diào)整的堆
 * @param length 堆的有效大小
 */
private static void buildHeap(int[] array, int length) {
 // 從最后一個非葉子節(jié)點開始,依次下沉調(diào)整
 for (int i = (length-2)/2; i >= 0; i--) {
 downAdjust(array, i, length);
 }
}
/**
 * 下沉調(diào)整
 * @param array 待調(diào)整的堆
 * @param index 要下沉的節(jié)點
 * @param length 堆的有效大小
 */
private static void downAdjust(int[] array, int index, int length) {
 // temp保存父節(jié)點值,用于最后的賦值
 int temp = array[index];
 int childIndex = 2 * index + 1;
 while (childIndex < length) {
 // 如果有右孩子,且右孩子小于左孩子的值,則定位到右孩子
 if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
 childIndex++;
 }
 // 如果父節(jié)點小于任何一個孩子的值,直接跳出
 if (temp <= array[childIndex])
 break;
 //無需真正交換,單向賦值即可
 array[index] = array[childIndex];
 index = childIndex;
 childIndex = 2 * childIndex + 1;
 }
 array[index] = temp;
}
public static void main(String[] args) {
 int[] array = new int[] {7,5,15,3,17,2,20,24,1,9,12,8};
 System.out.println(findNumberK(array, 5));
}

方法四:分治法

大家都了解快速排序,快速排序利用分治法,每一次把數(shù)組分成較大和較小的兩部分。

我們在尋找第k大元素的時候,也可以利用這個思路,以某個元素A為基準,把大于于A的元素都交換到數(shù)組左邊,小于A的元素都交換到數(shù)組右邊。

比如我們選擇以元素7作為基準,把數(shù)組分成了左側較大,右側較小的兩個區(qū)域,交換結果如下:

包括元素7在內(nèi)的較大元素有8個,但我們的k=5,顯然較大元素的數(shù)目過多了。于是我們在較大元素的區(qū)域繼續(xù)分治,這次以元素12位基準:

這樣一來,包括元素12在內(nèi)的較大元素有5個,正好和k相等。所以,基準元素12就是我們所求的。

這就是分治法的大體思想,這種方法的時間復雜度甚至優(yōu)于小頂堆法,可以達到O(n)。有興趣的小伙伴可以嘗試用代碼實現(xiàn)一下。

歡迎工作一到五年的Java工程師朋友們加入JavaQQ群:219571750,群內(nèi)提供免費的Java架構學習資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 如何尋找無序數(shù)組中的第K大元素? 有這樣一個算法題:有一個無序數(shù)組,要求找出數(shù)組中的第K大元素。比如給定的無序數(shù)組...
    Brandon_Murphy閱讀 608評論 0 0
  • 一些概念 數(shù)據(jù)結構就是研究數(shù)據(jù)的邏輯結構和物理結構以及它們之間相互關系,并對這種結構定義相應的運算,而且確保經(jīng)過這...
    Winterfell_Z閱讀 6,612評論 0 13
  • 前言 2. 實現(xiàn) Singleton 3. 數(shù)組中重復的數(shù)字 4. 二維數(shù)組中的查找 5. 替換空格 6. 從尾到...
    Observer_____閱讀 3,176評論 0 1
  • 1. 找出數(shù)組中重復的數(shù)字 題目:在一個長度為n的數(shù)組里的所有數(shù)字都在0到n-1的范圍內(nèi)。數(shù)組中某些數(shù)字是重復的,...
    BookThief閱讀 2,025評論 0 2
  • 十年風風雨雨,我們一起走過,共同攜手,筑起我們的幸福的家園。 她們就像天使,來到你我身邊,給我?guī)頍o限的快樂,享受...
    2f7a69550239閱讀 367評論 0 0

友情鏈接更多精彩內(nèi)容