作者
NeroJings
來源
https://blog.csdn.net/zhang6622056/article/details/82698859
本文思維導圖
簡述
先不說平衡二叉樹,我們單開來說,這樣比較方便理解。
先說二叉樹,再說平衡條件,沒那麼多花裏胡哨的理論,我只是想讓大家看完能明白,能寫出來。
二叉樹
什麼是二叉樹?二叉樹數據結構,顧名思義,只有兩個叉,在數據結構中,操作性能要遠高於線性結構,有O(height)的索引性能。與線性結構有相同的空間複雜度,特性如下:
- 每個節點最多隻有兩個兒子節點
- 左兒子小,右兒子大 (大小按照我們默認的比較規則,本例用int來比較)
線行找7與二叉數找7
線性找7
二叉樹找7
okay,我想大家聰明人已經看出來了,二叉樹搜索用了2次,而線性結構卻用了5次。
說白了,二叉樹結構,我每次問一個節點,都會離着我的目標越來越近,但是線性的則不然,我必須一個個問。
說到這兒,我想會有博友提出質疑了,如果線性查找,7恰好就在第一個呢?那不是一下就找到了嗎?
哈哈,你怎麼不上天呢 - -。還第一個。開個小玩笑。
這就是二叉樹索引的好處。相比看圖比碼字要清楚的多。
平衡條件
那麼,什麼叫平衡呢?其實很簡單,任何一個節點的子節點高度差必須小於2
第一個二叉平衡樹
- 從下往上數,第一個高度爲1(比較符合日常生活數數),那我們數數吧
- 5:———1高度 | 4,7,23,71 ———2高度| 6,50 ———3高度 | 15 ———4高度
- 比如節點6,那麼4和7的高度都是2,那就2-2 < 2 。平衡!!
難點一 遞歸
遞歸查找
我又加入了一些節點,方便大家理解遞歸深度
- 每一次正向橙色線條的滾動,就是一次遞歸查找
- 每一次正向橙色線條的滾動,方法的入棧!
- 遞歸的深度,取決於線條走了幾次,那就有多大的棧深度
- 本次查找,刨除root,共4次進棧
難點二 回溯
插入回溯
先不要關心這個旋轉操作,如圖所示,我們在遞歸的基礎上,沿着線條理解一下回溯
- 每一次逆向橙色線條的滾動,就是一次回溯
- 操作遞歸的每一個節點,都會在回溯的軌跡上
- 正因爲每一次遞歸,都有每一次回溯,那麼,我們就可以先完成相關操作(增加或刪除)之後,判定平衡
4種旋轉
左左類型旋轉
博主儘量放慢了速度,讓大家看清楚究竟旋轉是如何進行的,這是一個插入操作,我們看到在不平衡的時候,進行了左旋轉,這裏我們看到
- 正向插入,遞歸3-2-1
- 逆向回溯,1-2 判斷平衡條件 ,是平衡的
- 再次回溯,2-3,3的左邊高度爲2,右邊沒有節點爲0,那麼2-0 > 1,不平衡!
到這裏我們基本上理解了平衡的判斷,下面正式說一下旋轉:
- 判斷不平衡邊 在3節點判定,不平衡,那麼左邊高,我們需要調整左邊,獲取左邊節點2
- 判斷旋轉類型 這時候我們拿到節點2,判斷節點2哪邊高。左邊高,爲左左類型。右邊高爲左右旋轉類型,我們先不管
- 旋轉操作 3.left = 2.right; 2.right = 3; 重新計算,2和3節點的高度
右右類型旋轉[同上,不再敘述]
左右類型旋轉
右左類型旋轉
到此旋轉就說完了,希望大家好好的理解第一個左左類型!(理解了一個也就都理解了 )
後續部分沒有講是因爲說太多反而更亂。
後續的理解不了沒關係,我們代碼在看。
代碼基礎部分
node類
public static class AvlNodeInteger{ private Integer value; private Integer height; private AvlNodeInteger left; private AvlNodeInteger right; public AvlNodeInteger(int t){ initNode(t,null,null,1); } public AvlNodeInteger(int t,AvlNodeInteger left,AvlNodeInteger right){ initNode(t,left,right,null); } private void initNode(int t,AvlNodeInteger left,AvlNodeInteger right,Integer height){ this.setValue(t); this.left = left; this.right = right; this.height = height; } public Integer getValue() { return value; } public void setValue(Integer value) { this.value = value; } public Integer getHeight() { return height; } public void setHeight(Integer height) { this.height = height; } public AvlNodeInteger getLeft() { return left; } public void setLeft(AvlNodeInteger left) { this.left = left; } public AvlNodeInteger getRight() { return right; } public void setRight(AvlNodeInteger right) { this.right = right; } }
高度計算
/*** * 求一個節點的高度 * @param t * @return */ private int height(AvlNodeInteger t){ return null == t ? 0 : t.getHeight(); }
/*** * 求左右子節點最大高度 * @param left * @param right * @return */ private int maxHeight(AvlNodeInteger left,AvlNodeInteger right){ return height(left) > height(right) ? height(left) : height(right); }
插入操作
旋轉
/*** * 左左旋轉模型 * @param node 旋轉之前的parent node 節點 * @return 旋轉之後的parent node節點 */ private AvlNodeInteger leftLeftRotate(AvlNodeInteger node){ AvlNodeInteger newRoot = node.getLeft(); node.setLeft(newRoot.getRight()); newRoot.setRight(node); //由此node的高度降低了,newRoot的高度提高了。 //newRoot的高度由node的高度而來 node.setHeight(maxHeight(node.getLeft(),node.getRight())+1); newRoot.setHeight(maxHeight(newRoot.getLeft(),newRoot.getRight())+1); return newRoot; } /*** * 右右旋轉模型 * @param node * @return */ private AvlNodeInteger rightRightRotate(AvlNodeInteger node){ AvlNodeInteger newRoot = node.getRight(); node.setRight(newRoot.getLeft()); newRoot.setLeft(node); //由此node的高度降低了,newRoot的高度提高了。 //newRoot的高度由node的高度而來 node.setHeight(maxHeight(node.getLeft(),node.getRight())); newRoot.setHeight(maxHeight(newRoot.getLeft(),newRoot.getRight())); return newRoot; } /** * 左右模型,先右右,再左左 * @param node * @return */ private AvlNodeInteger leftRightRotate(AvlNodeInteger node){ //注意傳遞的參數 node.setLeft(rightRightRotate(node.getLeft())); return leftLeftRotate(node); } /*** * 右左模型,先左左,在右右 * @param node * @return */ private AvlNodeInteger rightLeftRotate(AvlNodeInteger node){ node.setRight(leftLeftRotate(node.getRight())); return rightRightRotate(node); }
insert
/**** * 對外開放,插入操作 * @param val * @throws Exception */ public void insert(Integer val) throws Exception { if(null == root){ initRoot(val); size++; return; } if(contains(val)) throw new Exception("The value is already exist!"); insertNode(this.root,val); size++; } /** * 遞歸插入 * parent == null 到最底部插入前節點判斷情況 * @param parent * @param val * @return */ private AvlNodeInteger insertNode(AvlNodeInteger parent,Integer val){ if(parent == null){ return createSingleNode(val); } if(val < parent.getValue()){ //插入判斷,小於父節點,插入到右邊 //注意理解回溯,這裏最終返回的是插入完成節點 //每一層回溯,都會返回相應當時遞歸的節點!!! parent.setLeft(insertNode(parent.getLeft(),val)); //判斷平衡,不要在意這裏的parent是誰, //這個parent肯定是遞歸層級上,回溯的一個節點!每一個節點都需要判斷平衡 if(height(parent.getLeft()) - height(parent.getRight()) > 1){ Integer compareVal = (Integer) parent.getLeft().getValue(); //左左旋轉類型 if(val < Integer.valueOf(compareVal)){ parent = leftLeftRotate(parent); }else{ //左右旋轉類型 parent = leftRightRotate(parent); } } } if(val > parent.getValue()){ //插入判斷,小於父節點,插入到右邊 //注意理解回溯,這裏最終返回的是插入完成節點 //每一層回溯,都會返回相應當時遞歸的節點!!! parent.setRight(insertNode(parent.getRight(),val)); //判斷平衡,不要在意這裏的parent是誰, //這個parent肯定是遞歸層級上,回溯的一個節點!每一個節點都需要判斷平衡 if(height(parent.getRight()) - height(parent.getLeft()) > 2){ Integer compareVal = (Integer) parent.getLeft().getValue(); if(val > compareVal){ parent = rightRightRotate(parent); }else{ parent = rightLeftRotate(parent); } } } parent.setHeight((maxHeight(parent.getLeft(),parent.getRight()))+1); return parent; }
刪除操作
public void remove(Integer val) { if(null == val || null == root){ return; } if(!contains(val)){ return; } remove(root,val); } /**** * AVL刪除,平衡樹實現 * @param parent * @param val * @return */ private AvlNodeInteger remove(AvlNodeInteger parent,Integer val){ if(val < parent.getValue()){ //左子樹遞歸查詢 //刪除以後返回替換的新節點 AvlNodeInteger newLeft = remove(parent.getLeft(),val); parent.setLeft(newLeft); //檢查是否平衡,刪除的左邊,那麼用右邊-左邊 if(height(parent.getRight()) - height(parent.getLeft()) > 1){ AvlNodeInteger tempNode = parent.getRight(); if(height(tempNode.getLeft()) > height(tempNode.getRight())){ //RL類型 rightLeftRotate(parent); }else{ //RR類型 rightRightRotate(parent); } } }else if(val > parent.getValue()){ //右子樹遞歸查找 //刪除以後返回替換的新節點 AvlNodeInteger newRight = remove(parent.getRight(),val); parent.setRight(newRight); //檢查是否平衡 if(height(parent.getLeft()) - height(parent.getRight()) > 1){ AvlNodeInteger tempNode = parent.getLeft(); if(height(tempNode.getLeft()) > height(tempNode.getRight())){ //LL類型 leftLeftRotate(parent); }else{ //LR類型 leftRightRotate(parent); } } }else{ //相等,匹配成功 if(null != parent.getLeft() && null != parent.getRight()){ //左右子節點都不爲空 //判斷高度,高的一方,拿到最大(左),最小(右)的節點,作爲替換節點。 //刪除原來匹配節點 //左邊更高,獲取到左邊最大的節點 if(parent.getLeft().getHeight() > parent.getRight().getHeight()){ AvlNodeInteger leftMax = getMax(parent.getLeft()); parent.setLeft(remove(parent.getLeft(),leftMax.getValue())); leftMax.setLeft(parent.getLeft()); leftMax.setRight(parent.getRight()); leftMax.setHeight(maxHeight(leftMax.getLeft(),leftMax.getRight())); parent = leftMax; }else{ //右邊更高,獲取到右邊最小的節點 AvlNodeInteger rightMin = getMin(parent.getRight()); parent.setRight(remove(parent.getRight(),rightMin.getValue())); rightMin.setLeft(parent.getLeft()); rightMin.setRight(parent.getRight()); rightMin.setHeight(maxHeight(parent.getLeft(),parent.getRight())+1); parent = rightMin; } }else{ //有任意一方節點爲空,則不爲空的那一方作爲替換節點,刪除原來的節點 parent = null; } } return parent; } /*** * 刪除時用到,獲取當前節點子節點最大值 * @param currentRoot * @return */ private AvlNodeInteger getMax(AvlNodeInteger currentRoot){ if(currentRoot.getRight() != null){ currentRoot = getMax(currentRoot.getRight()); } return currentRoot; } /*** * 刪除時用到,獲取當前節點子節點最小值 * @param currentRoot * @return */ private AvlNodeInteger getMin(AvlNodeInteger currentRoot){ if(currentRoot.getLeft() != null){ currentRoot = getMin(currentRoot.getLeft()); } return currentRoot; }
以上就是難點插入和刪除的實現了, 沒有過多闡述,是因爲大家如果真的理解了上面說明的理論, 那麼應該沒有問題來理解這些code。
當然有任何問題大家可以在留言區回覆我 ,歡迎大家指正!
4種遍歷
- 前序遍歷 根左右
- 中序遍歷 左跟右
- 後序遍歷 左右根
- 層級遍歷 從root開始,一層層
/***java * 前序遍歷 * 1-根節點 * 2-左節點 * 3-右節點 * 根左右 * @param parent */ private void xianxu(AvlNodeInteger parent){ System.out.println(parent.getValue()); if(null != parent.getLeft()){ xianxu(parent.getLeft()); } if(null != parent.getRight()){ xianxu(parent.getRight()); } } /*** * 中序遍歷 * 左節點 * 根節點 * 右節點 * * 左根右 * @param parent */ private void zhongxu(AvlNodeInteger parent){ if(null != parent.getLeft()){ zhongxu(parent.getLeft()); } System.out.println(parent.getValue()); if(null != parent.getRight()){ zhongxu(parent.getRight()); } } /*** * 後續遍歷 * 左右根 * 左節點 * 右節點 * 根節點 */ private void houxu(AvlNodeInteger parent){ if(null != parent.getLeft()){ houxu(parent.getLeft()); } if(null != parent.getRight()){ houxu(parent.getRight()); } System.out.println(parent); } /*** * 層級遍歷 * @param parent */ private void cengji(List<AvlNodeInteger> parent){ if(null == parent || parent.size() == 0) return; //打印當前層 List<AvlNodeInteger> AvlNodeIntegers = new ArrayList<AvlNodeInteger>(); int k = 0; for(int i = 0 ; i < parent.size() ; i++){ AvlNodeInteger currentNode = parent.get(i); System.out.println(currentNode.getValue()+","); if(null != currentNode.getLeft()){ AvlNodeIntegers.add(currentNode.getLeft()); k++; } if(null != currentNode.getRight()){ AvlNodeIntegers.add(currentNode.getRight()); k++; } } System.out.println("--------------------------"); cengji(AvlNodeIntegers); }
完整源碼見:平衡二叉樹 AVL樹結構詳解 [Java實現]--源碼部分
END