內容介紹
樹結構簡介
樹結構是計算機中經常使用的一種數據結構。咱們先來看一下生活中的樹: java
計算機中的樹和生活中的樹是相似的,只不過是倒着的,樹根在上,樹葉在下。樹上的每一個組成元素都是一個節點,樹根稱爲根節點
,樹枝稱爲分支節點
,樹葉稱爲葉子節點
,以下圖所示: 編程
二叉樹結構簡介
二叉樹是:每一個節點最多隻能有兩個子節點樹。二叉樹的子節點分爲左節點和右節點,以下圖:api
滿二叉樹:若是該二叉樹的全部葉子節點都在最後一層,而且節點總數= 2^n -1 , n 爲層數,則咱們稱爲滿二叉樹,以下圖: 數組
徹底二叉樹:若是該二叉樹的全部葉子節點都在最後一層或者倒數第二層,並且最後一層的葉子節點在左邊連續,倒數第二 層的葉子節點在右邊連續,咱們稱爲徹底二叉樹,以下圖: 微信
咱們這裏注意理解徹底二叉樹,由於堆結構是一種特殊的徹底二叉樹。關於樹的結構,咱們先簡單介紹,之後會專門講解樹結構,咱們這裏主要是講堆結構,因此先簡單說起一下樹結構。數據結構
堆結構簡介
堆(Heap)是一種特殊的樹形數據結構,每一個結點都有一個值。常見的堆有二叉堆、斐波那契堆等,一般咱們所說的堆的數據結構,是指二叉堆。以下圖: 動畫
堆知足下列兩個性質:code
- 堆中某個節點的值老是不大於或不小於其父節點的值。
- 堆老是一棵徹底二叉樹。 根節點最大的堆叫作
最大堆
或大根堆
,根節點最小的堆叫作最小堆
或小根堆
,以下圖所示:
堆的存儲
堆是非線性數據結構,能夠使用一維數組來存儲,將堆中序號對應的數據放到數組對應的索引中,以下圖: blog
堆的一些概念和規律
概念:排序
- 某節點左邊的子節點成爲:左孩子。
- 某節點右邊的子節點成爲:右孩子。
- 某節點的上一個節點成爲:父節點。
規律:假設當前節點的索引爲i
- 父節點索引 = (i - 1) / 2 (Java中除以2取整數,好比7/2 = 3)
- 左孩子索引 = 2 * i + 1
- 右孩子索引 = 2 * i + 2
堆的定義性質:
- 最大堆節點的值大於左右孩子的值,也就是知足:
arr[i] > arr[2*i+1] && arr[i] > arr[2*i+2]
,以下圖:
堆獲取最大值
獲取最大堆的最大值,其實就是獲取堆中最前面一個元素。對於堆這種數據結構一般是將最前面的元素和最後面的元素換位置,最大值就到了最後一個位置,而後從堆中排除這個元素,當最後一個元素交換到最前面時,此時就不知足堆的性質了,咱們須要將最前面這個元素經過ShiftDown
(下沉)的手段讓堆繼續知足堆的規則。
堆獲取最大值能夠分紅兩個步驟:
- 將堆中最前面的最大值和最後一個元素交換位置。
- 使用
ShiftDown
讓最前面的元素下沉到合適的位置,依然知足堆的性質。 動畫演示效果以下:
這裏面重點注意,ShiftDown
可讓堆中的一個元素下沉到合適的位置,而且知足堆的規則。後面咱們構建堆就須要使用到ShiftDown
操做。
ShiftDown
詳細圖解:
最大堆的最後一個非葉子節點
- 咱們構建堆時須要從最後一個非葉子節點開始按照規則構建堆,因此咱們須要知道最後一個非葉子節點計算公式:(堆的最大索引-1) / 2。
構建一個堆結構
構建堆實際上是將無序的徹底二叉樹調整爲二叉堆。非葉子節點沒有子節點不須要從新構建,而後自底向上對每個子樹執行SiftDown
操做,直到完成二叉堆化。 假設咱們如今有一個數組,內容爲:{6, 3, 7, 5, 8, 2, 1, 4, 9},它是不知足堆的規則,咱們如今將這個數組構建成一個二叉堆,步驟爲:
- 找到最後一個非葉子節點,使用
ShiftDown
下沉,使這個顆樹知足堆的規則。 - 找到倒數第二個非葉子節點,使用
ShiftDown
下沉,使這個顆樹知足堆的規則。 - 以此類推,直到找到最前面的一個元素使用
ShiftDown
下沉,使這顆樹知足堆的規則。 將無序的徹底二叉樹調整爲二叉堆的過程稱爲heapify
,動畫以下:
堆添加數據
往堆中插入一個元素,是在數組的最末尾插入新的數據,此時可能不知足堆的特性,咱們須要進行自下而上調整子節點和父節點,不知足堆性質則交換父子元素,直到當前子樹知足堆的性質。動畫效果以下:
代碼以下:
public class Heap { public static void main(String[] args) { int[] arr = {6, 3, 7, 5, 8, 2, 1, 4, 9}; heapify(arr); System.out.println("構建堆後:" + Arrays.toString(arr)); arr = insert(arr, 11); System.out.println("堆中插入數據後:" + Arrays.toString(arr)); } // 往數組中添加一個數據 public static int[] insert(int[] arr, int element) { arr = Arrays.copyOf(arr, arr.length + 1); // 複製以前的數組數據到新數組中 arr[arr.length-1] = element; shiftUp(arr, arr.length-1); return arr; } // 上浮操做,將i索引元素上浮到合適位置,保證知足堆的兩個特性 private static void shiftUp(int[] arr, int i) { // (i-1) / 2: 是i的父節點 while ((i-1) / 2 >= 0 && arr[(i-1) / 2] < arr[i]) { swap(arr, (i-1) / 2, i); i = (i-1) / 2; } } // heapify將無序的徹底二叉樹調整爲二叉堆 private static void heapify(int[] arr) { // 從非葉子節點開始,Shift Down將每一個子樹構建成最大堆 for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) { shiftDown(arr, i); } } // 下沉操做,將指定元素下沉到子樹的合適位置,使這個顆樹知足堆的規則。 private static void shiftDown(int[] arr, int i) { // 循環找子孩子交換位置。左孩子不能越界 while (2*i + 1 < arr.length) { // 假設要交換的是左孩子 int j = 2*i + 1; // 判斷是否有有孩子,而且右孩子是否大於左孩子 if (j+1 < arr.length && arr[j+1] > arr[j]) { j++; // 若是是,和右孩子交換 } // 若是當前節點大於兩個孩子,就不須要交換 if (arr[i] > arr[j]) break; // 當前節點小於子孩子,將當前節點和較大的子孩子交換 swap(arr, i, j); // 在判斷下一層 i = j; } } public static void swap(int[] arr, int start, int end) { if (start == end) return; int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; } }
添加元素放在堆的最後面,使用ShiftUp
讓元素上浮
,讓添加的元素找到一個合適的位置,讓堆中的數據依然知足堆的特性。
堆的做用
- 用做優先隊列,咱們知道堆的最前一個元素就是堆的最大值。咱們能夠依據優先級來構建堆,每次都取出堆中優先級最高的那個數據。
- 用做堆排序。
- 查找第N大(小)元素。
- 查找前N大(小)元素。
後續咱們會選擇合適的時間來完成上面這些功能。
總結
- 堆是一種特殊的徹底二叉樹,堆中某個節點的值老是不大於或不小於其父節點的值。堆是非線性數據結構,使用數組來存儲,操做堆其實就是操做數組中的數據。
- 假設當前節點的索引爲i,父節點索引 =
(i - 1) / 2
(Java中除以2取整數,好比7/2 = 3),左孩子索引 =2 * i + 1
,右孩子索引 =2 * i + 2
。 - 使用
ShiftDown
讓堆中最前面的元素下沉到合適的位置,讓堆依然知足堆的性質。 - 添加元素放在堆的最後面,使用
ShiftUp
讓元素上浮
,讓添加的元素找到一個合適的位置,讓堆中的數據依然知足堆的特性。
原創文章和動畫製做真心不易,您的點贊就是最大的支持! 想了解更多文章請關注微信公衆號:表哥動畫學編程