1. 程式人生 > 其它 >陣列的活學活用(一)

陣列的活學活用(一)

概覽

陣列我們知道,是一種有序連續的資料結構,隨機訪問的效率高。之所以隨機訪問的效率高,就是因為它的每個值都有下標,可以根據下標直接找到我們想要的值。

 

既然我們已經知道了這種思想,那麼我們可以用來做什麼呢?

 

接下來我們就講講用陣列的思想,或者乾脆就直接用陣列實現的幾種巧妙的思路。

 

堆排序

首先堆是什麼,我理解就是一顆完全二叉樹(當然,也有說多叉樹也行,但是我沒有研究過。);然後每個節點的值都大於等於它的子節點的值,則為最大堆,反之為最小堆。

 

接著我們就來看看最小堆是怎麼樣的一個東西。首先假設有一組數(3、9、2、5、1、6、10、16、7)。那我們初始化成最小堆時是什麼個樣子呢?

  1. 首先是按順序取值,也按順序存值
  2. 存入之後判斷與父節點的大小
    1. 如果小於父節點,則交換兩者位置
      1. 迴圈此步驟
    1. 否則繼續處理下一個數。
  1. 如果單純只想要最大最小值的話,到這步其實已經結束了。因為初始化的堆已經保證了根節點就是最大或者最小值
  2. 如果要繼續使堆完全排序,可以將根節點與最後一個節點交換。此時最後一個節點為排序區
  3. 然後將根節點與子節點比較,若子節點比根節點小,則取最小的一個子節點與根節點交換。交換之後繼續與子節點判斷,直到無需交換。
  4. 如此重複4 5步驟,即可得到一個完全排序的堆

總結一下就是:1、初始堆。2、交換根和最後一個未排序的節點,重新調整堆,再交換。如此直到最後就能得到一個完全排序的堆了。

 

初始化堆


所以我們可以看出,最小堆(或者最大堆)都是父節點和子節點之間的大小關係判斷,兄弟節點是沒有大小順序,此時就可以取min或者max值了,如果要完全排序,則可以繼續按下面的步驟處理。

 

完全堆排序


看,經過幾輪處理之後,我們就能得到一個完全經過排序的二叉樹了。

與陣列的關係

說了這麼多,那麼與陣列到底有什麼關係呢?其實很簡單,這是因為堆的邏輯順序是一顆二叉樹,但是物理順序卻是一個數組。

 

怎麼說呢?我們在儲存的時候是將堆從上到下,從左到右平鋪到陣列中,通過下標來判斷父子節點的關係。就以初始化堆之後的二叉樹為例,在陣列中的儲存順序是:

此時儲存形式有2種:
  1. 下標0存根節點
    1. 假設當前節點的下標為n,則它的父節點為 (n-1)>>1。它的兩個子節點分別為:(n<<1)+1和 (n<<1)+2
  1. 下標0不存值
    1. 假設當前節點的下標為n,則它的父節點為 n>>1。它的兩個子節點分別為:n<<1和 (n<<1)+1

如此,我們也知道了,可以用陣列來存一顆二叉樹,而且可以很方便的找到它有關聯的父節點甚至祖父節點等。

總結

堆排序,常用於取最大最小值,或者優先順序佇列和作業排程等。像Java中的PriorityQueue就用到了堆排序,然後JUC中的ScheduledThreadPoolExecutor中也是用到了堆排序。

 

定時任務排程池的基本實現原理是:

  • 將排程任務提取到執行緒池之前,首先計算下一次需要執行的時間戳,通過時間戳來判斷優先順序,並將任務存入最小堆中,這樣就確保了最先要執行的任務一定在最小堆的頂部,也就是跟節點。
  • 然後開一個定時任務,拿堆中第一個任務和當前時間進行比較:
    • 如果下一次執行的時間小於等於當前時間,則將該任務從堆中移除,並且投入執行緒池中執行。
    • 如果執行時間大於當前時間,則不處理,因為佇列中最先應該執行的任務都還沒到時間,其他任務也一定沒有到執行時間。

 

到這裡我們的小知識就告一段落了,今天的程式碼比較少,感興趣的小夥伴可以根據司機嘗試自己實現一下。那我們下期再見了~