這篇咱們說說堆這種數據結構,其實到這裏就暫時把java的數據結構告一段落,感受說的也差很少了,各類常見的數據結構都說到了,其實還有一種數據結構是「圖」,然而暫時對圖沒啥興趣,等有興趣的再說;還有排序算法,emmm....有時間再看看吧!java
其實從寫數據結構開始到如今讓我最大的感觸就是:新手剛開始仍是不要看數據結構爲好,太無聊太枯燥了,很容易讓人放棄;能夠等用的各類框架用得差很少了以後,再回頭靜下心來搞搞數據結構仍是挺有趣的;廢話很少說,開始今天的內容:算法
1.二叉樹分類數組
樹分爲二叉樹和多叉樹,其實吧有個頗有趣的現象,若是樹爲一叉也就是一個鏈表(固然沒有一叉樹這個說法啊,這裏是爲了好理解);若是爲二叉就是二叉樹(包括二叉搜索樹,紅黑樹等);若是爲二叉以上就是爲多叉樹(包括2-3樹,2-3-4樹,B樹等)數據結構
咱們再來簡單看看二叉樹,經過前面的學習咱們知道了二叉樹是什麼鬼,一句話歸納就是:除了葉節點外其餘的節點最多隻有兩個子節點;這裏咱們還能夠繼續對二叉樹進行分類,能夠簡單的分爲完美二叉樹,徹底二叉樹,滿二叉樹,咱們就簡單說說這三種分類;框架
完美二叉樹:跟名字差很少,很完美;除了葉節點以外,任意節點都有兩個子節點,整棵樹能夠構成一個大三角形,下圖所示:jvm
徹底二叉樹:沒有上面的那麼完美,只須要從根節點到倒數第二層知足完美二叉樹,而最後一層不須要都填滿,填滿一部分便可,可是最後一層有個要求:從最左邊的節點到最右邊的節點中間不能有空位置,例以下圖:學習
滿二叉樹:比徹底二叉樹要求更低,只須要保證除葉節點外,其餘節點都有兩個子節點,下面兩個圖都是滿二叉樹:測試
2.堆的簡介優化
首先咱們要明白,此堆非彼堆,咱們這裏的堆是一種數據結構而不是jvm中的堆內存空間;深刻一點來講,堆是一種徹底二叉樹,一般用數組實現,並且堆中任意一個節點數據都要大於兩個子節點的數據,下圖所示:this
注意,只要子節點兩個子節點的數據都比父節點小便可,兩個子節點誰大誰小無所謂;
因爲堆是由數組實現的,那麼數組究竟是怎麼保存堆中的數據的呢?看下圖,其實就是先將第一層數據放入數組,而後將第二層數據從左到右放入數組,而後第三層數據從左到右放入數組....
這裏補充一個小知識點,後面寫java代碼實現的時候會用到:在這裏咱們能夠知道堆中的每個節點都對應於數組中的一個位置,,因此能夠根據任意一個節點的位置得出來父節點的位置和兩個子節點的位置;舉個例子,50這個節點的數組下標是5,那麼父節點下標爲(5-1)/2向下取整等於2,左子節點下標爲2*5+1 = 11;右子節點下標爲2*5+2 = 12,咱們能夠簡單概括一下:
假如一個節點對應的數組下標爲a,那麼父節點爲:(a-1)/2向下取整,左子節點:2a+1,右子節點:2a+2
3.堆的操做
從上面這個圖能夠看出來堆中的數據放在數組中是沒有強制性的順序的(這裏叫作弱序),只能知道數組第一個數最大,最小的數不知道在哪裏,並且父節點確定要比子節點大,除此以外咱們就什麼也不知道了;
對於數據結構的操做而言無非就是增刪改查,咱們能夠知道在堆中是沒有辦法進行查詢的,由於左右節點沒有規定必須哪一個大那個小,因此查找的時候不知道應該往哪一個子節點去比較,通常而言修改操做是創建在查詢的基礎上的,因此堆也不支持修改,還有還不支持遍歷操做,這樣算下來,堆就支持增長和刪除操做了,那麼下面咱們就看看這兩個操做;
3.1添加節點
添加節點分爲兩種狀況,第一種,添加的節點數據很是小,直接放在堆的最後一個位置便可(換句話說直接添加到數組有效長度的後面一個空位置便可),這種狀況很容易就很少介紹了;第二種,添加節點的數據稍微比較大,好比下面這樣的:
此時堆的結構就被破壞了,咱們須要向上調整,那麼應該怎麼調整呢?很容易,直接和父節點交換位置便可,直到知足堆的這個條件:任意一個父節點都要大於子節點數據
3.2.刪除節點
這裏的刪除指的是刪除堆中最大的節點,換句話說就是每次刪除都是刪除根節點(對應於數組的第一個元素)
可是刪除事後堆的結構就會被破壞,因而要進行調整來平衡堆的結構,看看下圖:
咱們的作法就是將堆中最後一個節點放在根節點那裏(對應於數組就是將數組有效長度的最後元素放在第一個位置那裏),而後判斷新的根節點是否是比兩個子節點中最大的那個還要大?是,不須要調整;否,則將此時新的根節點與比較大的子節點交換位置,而後無限重複這個交換步驟,直到該節點的數據大於兩個子節點便可;
3.3.換位
上面說的換位是什麼意思呢?咱們知道在java中要交換兩個數據要有一箇中間變量。以下僞代碼:
int a = 3; int b = 10; //交換a和b的數據 int temp; temp = a;//第一步 a = b;//第二步 b = temp;//第三步
能夠看到這樣的一次簡單換位要進行三步複製操做,若是數組中對象不少,都要進行這種換位,那麼效率有點低,看看下面這個圖;
上圖進行三次這樣的交換就要通過3x3 = 9次複製操做,那有沒有方法能夠優化一下呢?
下圖所示,用一個臨時節點存儲A節點,而後D、C、B依次複製到前面的位置,最後就將臨時節點放到原來D的位置,總共只須要進行5次複製,這樣減小了4次複製,在交換的次數不少的時候這種方式效率可還行。。。
4.java代碼
根據上面說的咱們用java代碼來實現一下堆的添加和刪除操做,並且效率都是logN:
package com.wyq.test; public class MyHeap { //堆中的數組要存節點,因而就是一個Node數組 private Node[] heapArray; //數組的最大容量 private int maxSize; //數組中實際節點的數量 private int currentSize; public MyHeap(int size){ this.maxSize = size; this.currentSize = 0; this.heapArray = new Node[maxSize]; } //這裏爲了方便使用,節點類就爲一個靜態內部類,其中就存了一個int類型的數據,而後get/set方法 public static class Node{ private int data; public Node(int data){ this.data = data; } public int getData() { return data; } public void setData(int data) { this.data = data; } } //向堆中插入數據,這裏有幾點須要注意一下;首先,若是數組最大的容量已經存滿了,那麼插入失敗,直接返回false; //而後,數組沒有滿的話就將新插入的節點放在數組實際存放數據的後一個位置 //最後就是向上調整,將新插入的節點和父節點交換,重複這個操做,直到放在合適的位置,調整完畢,數組實際存放節點數量加一 //下面咱們就好好看看向上調整的方法 public boolean insert(int value){ if (maxSize == currentSize) { return false; } Node newNode = new Node(value); heapArray[currentSize] = newNode; changeUp(currentSize); currentSize++; return true; } //向上調增 private void changeUp(int current) { int parent = (current-1)/2;//獲得父節點的數組下標 Node temp = heapArray[current];//將咱們新插入的節點暫時保存起來,這在前面交換那裏說過這種作法的好處 //若是父節點數據小於插入節點的數據 while(current>0 && heapArray[parent].getData()<temp.getData()){ heapArray[current] = heapArray[parent];//這裏至關於把父節點複製到當前新插入節點的這個位置 current = parent;//當前指針指向父節點位置 parent = (parent-1)/2;//繼續獲取父節點的數組下標,而後又會繼續比較新插入節點數據和父節點數據,無限重複這個步驟 } //到達這裏,說明了該交換的節點已經交換完畢,換句哈來講就是已經把不少個節點向下移動了一個位置,留下了一個空位置,那就把暫時保存的節點放進去就ok了 heapArray[current] = temp; } //刪除節點,邏輯比較容易,始終都是刪除根節點,而後將最後面一個節點放到根節點位置,而後向下調整就行了;最後就是將實際容量減一就能夠了,重點看看向下調整 public Node delete(){ Node root = heapArray[0]; heapArray[0] = heapArray[currentSize-1]; currentSize--; changeDown(0); return root; } //向下調整,大概理一下思路,咱們首先將新的根節點臨時保存起來,而後要找兩個子節點中比較大的那個節點,最後就是比較臨時節點和比較大的節點的大小, //若是小,那麼把那個比較大的節點往上移動到父節點位置,繼續重複這個步驟將比較大的子節點往上移動,最後會留下一個空位置,就把臨時節點放進去就好 private void changeDown(int current) { Node largeChild; Node temp = heapArray[0];//臨時節點爲新的根節點 //注意這個while循環的條件,current<currentSize/2能夠保證當前節點至少有一個子節點 while (current<currentSize/2) { int leftIndex = 2*current+1;//左子節點數組下標 int rightIndex = 2*current+2;//右子節點數組下標 int largeIndex;//比較大的子節點數組下標 Node leftChild = heapArray[leftIndex];//左子節點 Node rightChild = heapArray[rightIndex];//右子節點 if (rightIndex<currentSize && leftChild.getData()<rightChild.getData()) { largeChild = rightChild; largeIndex = rightIndex; }else { largeChild = leftChild; largeIndex = leftIndex; } //若是臨時節點(即新的根節點)比大的那個子節點還大,那麼就直接跳出循環 if (temp.getData() >= largeChild.getData()) { break; } heapArray[current] = largeChild; current = largeIndex;//當前節點的執着呢指向比較大的子節點 } heapArray[current] = temp;//臨時節點插入到堆數組中 } //展現堆中的數據 public void display(){ System.out.print("堆中的數據爲:"); for (int i = 0; i < currentSize; i++) { System.out.print(heapArray[i].getData()+" "); } } public static void main(String[] args) { MyHeap heap = new MyHeap(10); heap.insert(3); heap.insert(5); heap.insert(1); heap.insert(10); heap.insert(6); heap.insert(7); heap.display(); System.out.println(); System.out.print("刪除操做以後"); heap.delete(); heap.display(); } }
咱們插入的節點以下圖所示:
代碼測試結果爲以下所示,成功;
5.總結
到這裏堆就差很少說完了,其實還有個堆排序,其實最簡單的就是向堆中添加不少節點,而後不斷的刪除節點,就會以從大到小的順序返回了,比較容易吧!固然還能夠進行改進不過也很簡單,有興趣的能夠本身去看看堆排序。
java數據結構到這裏差很少了,就隨意列舉一下咱們用過的數據結構的時間複雜度: