咱們從一個數列(1,2,3,4,5,6)
,來講明構成二叉排序樹的一些問題node
1.左子樹所有爲空, 從形式圖所看,更像一個單鏈表
2.查詢速度明顯下降(由於須要依次比較),不能發揮BST
的優點,由於每次還須要比較左子樹,其查詢速度比單鏈表還慢算法
那麼怎麼辦?ide
那麼像這樣的數列咱們能夠是用解決方案--->平衡二叉樹(AVL)
性能
1.平衡二叉樹也叫平衡二叉搜索樹(Self balancing binary searchtree)又被稱爲AVL樹
, 能夠保證查詢效率較高。測試
有如下特色:
1.它是一顆空樹或它的左右兩個子樹的高度差絕對超過1
2.左右兩個子樹都是一棵平衡二叉樹。優化
平衡二叉樹的經常使用實現:紅黑樹、AVL(算法)、替罪羊樹、Treap、伸展樹
等。this
結合前面咱們介紹的AVL樹特色分析看看,如今你知道了嗎?spa
平衡二叉樹之因此將二叉排序樹,調整爲平衡狀態,是爲了在二叉排序樹近似爲鏈的狀況下,加強其查找性能,下降時間複雜度。3d
常見的二叉平衡樹調整平衡方法有:LL、LR、RR、RL
code
RR型介紹
當前這種狀況圖三,鏈式就須要平衡調整,不然則影響到查詢的效率
平衡調整:一個根節點與兩左右子節點的
二叉排序樹(以下圖)
原本Mar節點再插入May時,還保持平衡:知足右子節點大於根節點
當插入麻煩節點Nov
時平衡點被破壞,且Nov節點是在根節點的右子樹的右子樹上
根據二叉排序的特性:右子樹節點比當前節點大
咱們找到Mar、May、Nov的中間數
,它們的大小關係是Mar<May<Nov
此時將May做爲根節點
,Mar做爲左子樹、Nov做爲右子樹
進行調整
這時,這種插入即稱呼爲RR插入
,平衡調整也成爲RR旋轉(右單轉)
LL型介紹
當插入麻煩節點Apr
時平衡點被破壞,且Apr節點是在Mar節點的左子樹的左子樹上
咱們找到Mar、Aug、Apr的中間數
,它們的大小關係是Apr<Aug<Mar
此時將Aug做爲根節點
,Apr做爲左子樹、Mar做爲右子樹
進行調整
這時,這種插入即稱呼爲LL插入
,平衡調整也成爲LL旋轉(左單轉)
固然插入的節點也多是左子節點或者右子節點
咱們發現其實進行旋轉調整的時候呢
不必定是根節點才進行旋轉。在中間的節點Mar也是能夠的
LR介紹
當插入麻煩節點Jan
時平衡點被破壞,且Jan節點是在May節點的左子樹的右子樹上
咱們找到May、Aug、Mar的中間數
,它們的大小關係是Aug<Mar<May
此時將Mar做爲根節點
,Aug做爲左子樹、May做爲右子樹
且Mar>Aug
、Jan<Mar
根據特性Jan做爲Aug右子樹
進行調整
這時,這種插入即稱呼爲LR插入
,平衡調整也成爲LR旋轉
RL介紹
當插入麻煩節點Feb
時平衡點被破壞,且Feb節點是在Aug節點的右子樹的左子樹上
咱們找到Jan、Aug、Dec的中間數
,它們的大小關係是Aug<Dec<Jan
此時將Dec做爲根節點
,Aug做爲左子樹、Jan做爲右子樹
且Jan>Dec
、Feb>Dec
根據特性Feb做爲Jan左子樹
進行調整
這時,這種插入即稱呼爲RL插入
,平衡調整也成爲RL旋轉
即看插入節點把誰破壞了,跟被破壞節點是什麼關係?
是左邊的左邊?右邊的右邊?仍是左邊的右邊?右邊的左邊?
可是須要注意的是:平衡調整後仍爲二叉排序樹
給你一個數列{4,3,6,5,7,8}
,讓你可以高效
的完成對數據的查詢和添加
那麼按照咱們以前的思路,先構建:一顆二叉排序樹
非葉子節點特色:左子節點的值比當前節點的值小,右子節點的值比當前節點的值大。
特別說明:若是有相同的值
,能夠將該節點放在左子節點或右子節點
那麼咱們前面分析了在二叉排序樹近似爲鏈的狀況下
1.從形式圖所看,更像一個單鏈表
2.查詢速度明顯下降(由於須要依次比較),不能發揮BST的優點
因此咱們須要進行調整:平衡狀態加強其查找性能,下降時間複雜度
咱們發現這顆二叉排序樹左子樹高度爲1,右邊子樹高度爲3
此時不符合平衡二叉樹的特色:它是一顆空樹或它的左右兩個子樹的高度差絕對超過1
根據上面介紹的四種平衡調整模式介紹,目前比較符合的是RR旋轉
咱們結合上面的圖與RR旋轉的思路一塊兒來分析當前的案例
未插入節點8時,還保持平衡:知足右子節點大於根節點
當插入麻煩節點8
時平衡點被破壞,且節點8是在根節點的右子樹的右子樹上
根據二叉排序的特性:右子樹節點比當前節點大
咱們找到四、六、7的中間數
,它們的大小關係是 4 < 6 < 7
此時將 6 做爲根節點
, 4 做爲左子樹、 7 做爲右子樹
進行調整
右子樹
設置爲當前根節點root的右子樹的左子樹
右子樹的右子樹
你們有沒有發現,節點與子樹之間的高度是關鍵的
並非說加入一個節點得時候就進行旋轉,而是左右兩個子樹的高度差絕對超過1纔去進行平衡調整
因此須要先完成事情是:統計當前樹的高度、統計與左子樹、或右子樹的高度
那麼咱們用代碼實踐來統計:樹的高度、左子樹高度、右子樹高度
(節點代碼、AVL代碼可參考二叉排序樹相關代碼)
class Node{ int value; Node left; Node right; public Node(int value) { this.value = value; } /** * @param value 但願刪除的結點的值 * @return若是找到返回該結點,不然返回null */ public Node search(int value) { if(value == this.value) { //找到就是該結點 return this; } else if(value < this.value) {//若是查找的值小於當前結點,向左子樹遞歸查找 //若是左子結點爲空 if(this.left == null) { return null; } return this.left.search(value); } else { //若是查找的值不小於當前結點,向右子樹遞歸查找 if(this.right == null) { return null; } return this.right. search(value); } } public Node searchParent(int value){ //若是當前節點是須要刪除節點的父節點則返回 if((this.left!=null && this.left.value == value) || (this.right!=null && this.right.value == value)){ return this; }else{ //若是查找的值小於當前節點的值,而且當前節點的左子節點不爲空 if(value <this.value && this.left!=null){ return this.left.searchParent(value); }else if(value >= this.value && this.right!=null){ //若是查找的值大於等於於當前節點的值,而且當前節點的右子節點不爲空 return this.right.searchParent(value); }else { return null;//沒有找到父節點,好比說節點7 } } } //添加節點方法 //遞歸方式添加節點,要知足二叉排序樹的要求 //要求是:`左子節點的值比當前節點的值小,右子節點的值比當前節點的值大。` public void add(Node node) { if (node == null) { return; } //判斷傳入的節點的值,和當前節點值的關係 //添加的節點小於當前節點 if (node.value < this.value) { if (this.left == null) { this.left = node; } else { this.left.add(node); } } else {//添加的節點大於當前節點 if (this.right == null) { this.right = node; } else { this.right.add(node); } } } @Override public String toString() { return "Node{" +"value=" + value +'}'; } //中序遍歷 public void infixOrder(){ if(this.left != null){ this.left.infixOrder(); } System.out.println(this); if(this.right != null){ this.right.infixOrder(); } } }
class AVLTree { private Node root; public Node getRoot() { return root; } public void setRoot(Node root) { this.root = root; } //添加節點的方法 public void add(Node node){ if(root == null){ root = node; }else{ root.add(node); } } /** * @param node 傳入的節點(當作新二叉排序樹的根節點) * return 返回新跟節點的最小節點的值 */ public int delRigthTreeMin(Node node){ Node target = node; //循環的查找左子節點,找到最小值 while(target.left!=null) { target = target.left; } //刪除最小值 delNode(target.value); //返回最小值 return target.value; } public void delNode(int value){ if(root == null){ System.out.println("當前根節點爲空!沒法刪除節點!"); return; }else{ //1.須要先找到刪除的值的對應節點 Node targetNode = search(value); //若是沒有找到須要刪除的節點 if(targetNode == null){ System.out.println("對不起!沒有找到刪除節點信息!"); return; } //若是咱們發現根節點沒有左子節點與右子節點 if(root.left == null && root.right == null){ root = null; return; } //找到targetNode 的父節點 Node parent = searchParent(value); //若是刪除節點是葉子節點 if(targetNode.left == null && targetNode.right == null){ //判斷刪除節點是父節點的左子節點仍是右子節點 if(parent.left!= null && parent.left.value == targetNode.value){ parent.left = null; }else if (parent.right!= null && parent.right.value == targetNode.value){ parent.right = null; } }else if (targetNode.left != null && targetNode.right != null){ int minValue = delRigthTreeMin(targetNode.right); targetNode.value = minValue;//重置值 }else{//刪除只有一顆子樹的節點 //若是刪除節點的子節點是左子節點 if(targetNode.left !=null){ if(parent!=null){ //判斷刪除節點是父節點的左子節點仍是右子節點 if(parent.left.value == targetNode.value){ //將原刪除節點的位置給到子節點 parent.left = targetNode.left; }else if (parent.right.value == targetNode.value){ //將原刪除節點的位置給到子節點 parent.right = targetNode.left; } }else{ root = targetNode.left; } }else if (targetNode.right != null){ //若是刪除節點的子節點是右子節點 if(parent!=null){ //判斷刪除節點是父節點的左子節點仍是右子節點 if( parent.left.value == targetNode.value){ //將原刪除節點的位置給到子節點 parent.left = targetNode.right; }else if (parent.right.value == targetNode.value){ //將原刪除節點的位置給到子節點 parent.right = targetNode.right; } }else{ root = targetNode.right; } } } } } //查找須要刪除節點的方法 public Node search(int value){ if(root == null){ return null; }else{ return root.search(value); } } //查找須要刪除節點的父節點信息 public Node searchParent(int value){ if(root == null){ return null; }else{ return root.searchParent(value); } } //調用中序遍歷的方法 public void infixOrder(){ if(root == null){ System.out.println("當前二叉排序根節點爲空,沒法遍歷"); return; }else{ root.infixOrder(); } } }
如上圖所示,咱們取的該這顆樹的高度是爲:3 ,算上根節點則是 4
因此咱們求該樹的高度,須要知道左節點與右節點的高度分別是多少
class Node{ //......省略其餘關鍵代碼 //返回當前節點的左子樹的高度 public int leftHigth(){ if(left == null){ return 0; } return left.hight(); } //返回當前節點的右子樹的高度 public int rightHight(){ if(right == null){ return 0; } return right.hight(); } //返回當前節點的高度,若算上根節點則須要 + 1 public int hight(){ return Math.max(left == null? 0 : left.hight(),right == null?0:right.hight()) + 1; } }
有沒有發現,咱們須要知道左子樹的高度是多少
同時也須要知道左子樹的左子樹與右邊子樹是多少.....
這是一個一直遞歸的過程。
public static void main(String[] args) { int[] arr ={4,3,6,5,7,8}; AVLTree avlTree = new AVLTree(); for(int i = 0; i<arr.length; i++){ avlTree.add(new Node(arr[i])); } //遍歷 System.out.println("中序遍歷"); avlTree.infixOrder(); //節點高度 System.out.println("算上跟節點高度爲:"+avlTree.getRoot().hight()); } 運行結果以下: 中序遍歷 Node{value=3} Node{value=4} Node{value=5} Node{value=6} Node{value=7} Node{value=8} 算上跟節點高度爲:4
咱們剛剛也說了,算上跟根節點高度就是4,那麼咱們看看左子樹和右子樹
//節點高度 System.out.println("節點高度爲:"+avlTree.getRoot().hight()); //節點高度 System.out.println("左節點高度爲:"+avlTree.getRoot().leftHigth()); //節點高度 System.out.println("右節點高度爲:"+avlTree.getRoot().rightHight()); 運行結果以下: 節點高度爲:4 左節點高度爲:1 右節點高度爲:3
咱們剛剛說到,左右兩個子樹的高度差絕對超過1纔去進行平衡調整,那麼當前的狀況則須要進行調整:右旋轉
class Node{ //......省略其餘關鍵代碼 //RR旋轉方法 private void RightRotate(){ //建立一個新節點newNode等於當前根節點root,值相等 Node newNode = new Node(value); //新節點newNode的左子樹設置爲當前根節點root的左子樹 newNode.left = left; //新節點newNode的右子樹設置爲當前根節點root的右子樹的左子樹 newNode.right = right.left; //當前根節點root的值換爲右子節點的值 value = right.value; //當前根節點root的右子樹設置成根節點root的右子樹的右子樹 right = right.right; //當前根節點root的左子樹設置爲新節點 left = newNode; } //優化添加節點操做 public void add(Node node) { if (node == null) { return; } //判斷傳入的節點的值,和當前節點值的關係 //添加的節點小於當前節點 if (node.value < this.value) { if (this.left == null) { this.left = node; } else { this.left.add(node); } } else {//添加的節點大於當前節點 if (this.right == null) { this.right = node; } else { this.right.add(node); } } //當添加完一個節點後:若是(右子樹的高度 - 左子樹的高度)> 1 則執行RR旋轉 if(rightHight() - leftHigth() > 1 ){ RightRotate();//執行RR旋轉 } } }
接下來咱們實踐看看,當添加節點知足條件是否會進行平衡調整
public static void main(String[] args) { int[] arr ={4,3,6,5,7,8}; AVLTree avlTree = new AVLTree(); for(int i = 0; i<arr.length; i++){ avlTree.add(new Node(arr[i])); } //遍歷 System.out.println("中序遍歷"); avlTree.infixOrder(); //節點高度 System.out.println("節點高度爲:"+avlTree.getRoot().hight()); //節點高度 System.out.println("左節點高度爲:"+avlTree.getRoot().leftHigth()); //節點高度 System.out.println("右節點高度爲:"+avlTree.getRoot().rightHight()); } 運行結果以下: 中序遍歷 Node{value=3} Node{value=4} Node{value=5} Node{value=6} Node{value=7} Node{value=8} 節點高度爲:3 左節點高度爲:2 右節點高度爲:2
這時咱們進行平衡調整,從左右兩個子樹的高度差沒有超過1了。
給你一個數列{10,12,8,9,7,6}
,讓你可以高效
的完成對數據的查詢和添加
那麼按照咱們以前的思路,先構建:一顆二叉排序樹
非葉子節點特色:左子節點的值比當前節點的值小,右子節點的值比當前節點的值大。
特別說明:若是有相同的值
,能夠將該節點放在左子節點或右子節點
咱們發現這顆二叉排序樹左子樹高度爲3,右邊子樹高度爲1
此時不符合平衡二叉樹的特色:它是一顆空樹或它的左右兩個子樹的高度差絕對超過1
根據上面介紹的四種平衡調整模式介紹,目前比較符合的是LL旋轉
根據咱們前面的示例經驗,咱們能夠直接進行實現思路分析
左子樹
設置爲當前根節點root的左子樹的右子樹
左子樹的左子樹
示例代碼實現
class Node{ //......省略其餘關鍵代碼 //LL旋轉方法 private void leftRotate(){ //建立一個新節點newNode等於當前根節點root,值相等 Node newNode = new Node(value); //新節點newNode的右子樹設置爲當前根節點root的右子樹 newNode.right = right; //新節點newNode的`左子樹`設置爲當前根節點root的`左子樹的右子樹` newNode.left = left.right; //當前根節點root的值換爲左子節點的值 value = left.value; //當前根節點root的左子樹設置成根節點root的`左子樹的左子樹` left = left.left; //當前根節點root的右子樹設置爲新節點 right = newNode; } //優化添加節點操做 public void add(Node node) { if (node == null) { return; } //判斷傳入的節點的值,和當前節點值的關係 //添加的節點小於當前節點 if (node.value < this.value) { if (this.left == null) { this.left = node; } else { this.left.add(node); } } else {//添加的節點大於當前節點 if (this.right == null) { this.right = node; } else { this.right.add(node); } } //當添加完一個節點後:若是(右子樹的高度 - 左子樹的高度)> 1 則執行RR旋轉 if(rightHight() - leftHigth() > 1 ){ RightRotate();//執行RR旋轉 } //當添加完一個節點後:若是(左子樹的高度 - 右子樹的高度)> 1 則執行LL旋轉 if(leftHigth() - rightHight() > 1 ){ leftRotate();//執行LL旋轉 } } }
接下來咱們Demo測試一下爲調整以前的高度分別是多少
public static void main(String[] args) { //int[] arr ={4,3,6,5,7,8}; int[] arr ={10,12,8,9,7,6}; AVLTree avlTree = new AVLTree(); for(int i = 0; i<arr.length; i++){ avlTree.add(new Node(arr[i])); } //遍歷 System.out.println("中序遍歷"); avlTree.infixOrder(); //節點高度 System.out.println("節點高度爲:"+avlTree.getRoot().hight()); //節點高度 System.out.println("左節點高度爲:"+avlTree.getRoot().leftHigth()); //節點高度 System.out.println("右節點高度爲:"+avlTree.getRoot().rightHight()); } 運行結果以下: 中序遍歷 Node{value=6} Node{value=7} Node{value=10} Node{value=9} Node{value=8} Node{value=12} 節點高度爲:3 左節點高度爲:2 右節點高度爲:2
這時咱們進行平衡調整,從左右兩個子樹的高度差沒有超過1了。
給你一個數列{10,11,7,6,8,9}
,讓你可以高效
的完成對數據的查詢和添加
那麼按照咱們以前的思路,先構建:一顆二叉排序樹
咱們發現這顆二叉排序樹左子樹高度爲3,右邊子樹高度爲1
此時不符合平衡二叉樹的特色:它是一顆空樹或它的左右兩個子樹的高度差絕對超過1
按照咱們以前思路適合的是LL旋轉,那麼咱們執行左旋後的樣子發現是
那麼出現這種問題的緣由是什麼呢?
分析1:加入節點九時,左子樹(節點7)的樹高度 > 右子樹(節點11) 的高度
分析2:左子樹高度 - 右子樹高度 >1 觸發LL旋轉,就變成上面那樣了
那麼咱們能夠根據上面的LR介紹能夠猜到一些思路,解決這個問題.
咱們在符合:執行LL旋轉時條件時
思路1.獲取左子樹的右子樹(節點8)高度,取名爲:k
思路2.獲取左子樹(節點7)的高度,取名爲:J
思路3.若是 k > J,對左子樹進行RR旋轉
思路4.若是 k < J,直接進行LL旋轉
簡單的一句話:若是K>J,則先旋轉子樹再旋轉本身
同時咱們在符合:執行RR旋轉時條件時
思路1.獲取右子樹的左子樹高度 ,取名爲:U
思路2.獲取右子樹(節點11)的高度,取名爲:L
思路3.若是U > L,對右子樹進行LL旋轉
思路4.若是U < L,直接進行RR旋轉
class Node{ //......省略其餘關鍵代碼 //優化添加節點操做 public void add(Node node) { if (node == null) { return; } //判斷傳入的節點的值,和當前節點值的關係 //添加的節點小於當前節點 if (node.value < this.value) { if (this.left == null) { this.left = node; } else { this.left.add(node); } } else {//添加的節點大於當前節點 if (this.right == null) { this.right = node; } else { this.right.add(node); } } //當添加完一個節點後:若是(右子樹的高度 - 左子樹的高度)> 1 則執行RR旋轉 if(rightHight() - leftHigth() > 1 ){ //獲取它的右子樹的左子樹的高度 取名U,獲取它的右子樹高度L //若是u > l 執行LL旋轉 if(right != null && right.leftHigth()> right.rightHight()){ right.leftRotate();//執行LL旋轉 RightRotate();//執行RR旋轉 }else{ RightRotate(); } return;//防止接着往下走 } //當添加完一個節點後:若是(左子樹的高度 - 右子樹的高度)> 1 則執行LL旋轉 if(leftHigth() - rightHight() > 1 ){ //獲取左子樹的右子樹高度,取名爲:k 獲取左子樹的高度,取名爲:J //若是 k > J,對左子樹進行RR旋轉 if(left != null && left.rightHight() > left.leftHigth()){ left.RightRotate();//執行RR旋轉 leftRotate();//執行LL旋轉 }else{ leftRotate();//執行LL旋轉 } } } }
接下來讓咱們使用Demo驗證一下咱們的思路
public static void main(String[] args) { //int[] arr ={4,3,6,5,7,8}; int[] arr ={10,12,8,9,7,6}; AVLTree avlTree = new AVLTree(); for(int i = 0; i<arr.length; i++){ avlTree.add(new Node(arr[i])); } //遍歷 System.out.println("中序遍歷"); avlTree.infixOrder(); //節點高度 System.out.println("節點高度爲:"+avlTree.getRoot().hight()); //節點高度 System.out.println("左節點高度爲:"+avlTree.getRoot().leftHigth()); //節點高度 System.out.println("右節點高度爲:"+avlTree.getRoot().rightHight()); System.out.println("右節點高度爲:"+avlTree.getRoot().rightHight()); System.out.println("當前根節點爲:"+avlTree.getRoot()); System.out.println("當前根節點的左節點爲:"+avlTree.getRoot().left); System.out.println("當前根節點的右節點爲:"+avlTree.getRoot().right); } 運行結果以下: 中序遍歷 Node{value=6} Node{value=7} Node{value=8} Node{value=9} Node{value=10} Node{value=11} 節點高度爲:3 左節點高度爲:2 右節點高度爲:2 當前根節點爲:Node{value=8} 當前根節點的左節點爲:Node{value=7} 當前根節點的右節點爲:Node{value=10}