二叉搜索樹自動調整平衡(左右子樹高度差小於等於1)——平衡二叉樹
目錄
1.編寫測試程序,測試二叉樹的四種平衡操作
public class AVLTree {
public static void main(String[] args) {
//驗證插入操作
BalancedBinaryTree test0 = new BalancedBinaryTree(100);
test0.insert(50);
test0.insert(200);
System.out.print("草稿紙上運算爲{100,50,200}: ");
test0.preOrder();
System.out.println();
//驗證LL旋轉
//發生在root
BalancedBinaryTree test1 = new BalancedBinaryTree(100);
test1.insert(70);
test1.insert(30);
test1.insert(10);
test1.insert(5);
System.out.print("草稿紙上運算爲{70,10,5,30,100}: ");
test1.preOrder();
System.out.println();
//發生在非root
BalancedBinaryTree test4 = new BalancedBinaryTree(100);
test4.insert(50);
test4.insert(200);
test4.insert(150);
test4.insert(130);
System.out.print("草稿紙上運算爲{100,50,150,130,200 }: ");
test4.preOrder();
System.out.println();
//驗證RR旋轉
//發生在root
BalancedBinaryTree test2 = new BalancedBinaryTree(100);
test2.insert(200);
test2.insert(300);
test2.insert(400);
test2.insert(500);
System.out.print("草稿紙上運算爲{200,100,400,300,500}: ");
test2.preOrder();
System.out.println();
//發生在非root
BalancedBinaryTree test3 = new BalancedBinaryTree(100);
test3.insert(50);
test3.insert(200);
test3.insert(75);
test3.insert(80);
System.out.print("草稿紙上運算爲{100,75,50,80,200}: ");
test3.preOrder();
System.out.println();
//驗證LR旋轉
BalancedBinaryTree test5 = new BalancedBinaryTree(100);
test5.insert(50);
test5.insert(200);
test5.insert(75);
test5.insert(30);
test5.insert(80);
System.out.print("LR草稿紙上運算爲{75,50,30,100,80,200}: ");
test5.preOrder();
System.out.println();
//驗證RL旋轉
BalancedBinaryTree test6 = new BalancedBinaryTree(100);
test6.insert(50);
test6.insert(200);
test6.insert(150);
test6.insert(300);
test6.insert(125);
System.out.print("RL草稿紙上運算爲{150,100,50,125,200,300}: ");
test6.preOrder();
System.out.println();
//驗證刪除操作
BalancedBinaryTree test7 = new BalancedBinaryTree(100);
test7.insert(50);
test7.insert(200);
test7.insert(150);
test7.insert(300);
test7.insert(25);
test7.insert(75);
System.out.print("delete草稿紙上運算爲{100,50,25,75,200,150,300}: ");
test7.preOrder();
System.out.println();
//刪除沒有兒子的節點
test7.delete(150);
System.out.print("刪除沒有兒子的節點150,delete草稿紙上運算爲{100 50 25 75 200 300 }: ");
test7.preOrder();
System.out.println();
//刪除有一個兒子的節點
test7.delete(200);
System.out.print("刪除有一個兒子的節點200,delete草稿紙上運算爲{100 50 25 75 200 300 }: ");
test7.preOrder();
System.out.println();
//刪除有兩個兒子的節點
test7.delete(100);
System.out.print("刪除有兩個兒子的節點100,delete草稿紙上運算爲{75,50,25,300}: ");
test7.preOrder();
System.out.println();
test7.delete(75);
System.out.print("刪除節點75,delete草稿紙上運算爲{50,25,300}: ");
test7.preOrder();
System.out.println();
test7.delete(50);
System.out.print("刪除節點50,delete草稿紙上運算爲{25,300}: ");
test7.preOrder();
System.out.println();
}
}
2.編寫內部類:樹結點
class AVLNode {
public int data;
public int depth;//當前子樹深度的 depth
public int balance;
public AVLNode parent = null;//指向父節點的指針
public AVLNode left = null;
public AVLNode right = null;
public AVLNode() {
depth = 1;
balance = 0;
left = null;
right = null;
}
public AVLNode(int data) {
this.data = data;
depth = 1;
balance = 0;
left = null;
right = null;
}
}
3.根節點,類構造器
class BalancedBinaryTree {
/**
* 根節點
*/
private AVLNode root = null;
/**
* 構造器
*/
public BalancedBinaryTree() {
}
public BalancedBinaryTree(int data) {
root = new AVLNode(data);
}
//以及別的操作
}
4.插入
插入過程分爲:二叉查找樹的插入過程+再平衡過程
再平衡過程分爲:計算深度和平衡值+進行平衡操作
public void insert(int data) {
if (root == null) {
root = new AVLNode(data);
} else {
insertSon(root, data);
}
}
private void insertSon(AVLNode node, int data) {
if (data < node.data) {
if (node.left != null) {
insertSon(node.left, data);
} else {
node.left = new AVLNode(data);
node.left.parent = node;//子節點鎖住parent
}
} else {
if (node.right != null) {
insertSon(node.right, data);
} else {
node.right = new AVLNode(data);
node.right.parent = node;
}
}
// 計算平衡和深度
node.balance = calculateBalance(node);
node.depth = calculateDepth(node);
// 平衡節點
balanceNode(node);
}
5.平衡
5.1計算平衡值
private int calculateBalance(AVLNode node) {
int leftDepth;
int rightDepth;
if (node.left != null) {
leftDepth = node.left.depth;
} else {
leftDepth = 0;
}
if (node.right != null) {
rightDepth = node.right.depth;
} else {
rightDepth = 0;
}
return leftDepth - rightDepth;
}
5.2.計算深度
private int calculateDepth(AVLNode node) {
int depth = 0;
if (node.left != null) {
depth = node.left.depth;
}
if (node.right != null && depth < node.right.depth) {
depth = node.right.depth;
}
depth++;
return depth;
}
5.3.平衡節點(調用四個旋轉的條件)
根據balance的數值,找出不平衡的地方進行調整操作(RR,RL,LR,LL)
private void balanceNode(AVLNode node) {
if (node.balance <= -2) {
if (node.right.balance == -1) {
// RR插入,RR旋轉
rightRightRotate(node);
} else {
// RL插入,RL旋轉
rightLeftRotate(node);
}
} else if (node.balance >= 2) {
if (node.left.balance == 1) {
// LL插入,LL旋轉
leftLeftRotate(node);
} else {
// LR插入,LR旋轉
leftRightRotate(node);
}
}
}
5.3.1. RR旋轉
如圖A,B節點進行逆時針旋轉
圖片來源:中國MOOC浙大數據結構第四章
public void rightRightRotate(AVLNode node) {
counterClockwiseRotate(node);
}
5.3.2. LL旋轉
如圖A,B兩個節點進行順時針旋轉
圖片來源:中國MOOC浙大數據結構第四章
public void leftLeftRotate(AVLNode node) {
clockwiseRotate(node);
}
5.3.3. RL旋轉
先對C,B兩個節點先進行逆時針旋轉,再對A,C兩個節點順時針旋轉
圖片來源:中國MOOC浙大數據結構第四章
public void rightLeftRotate(AVLNode node) {
clockwiseRotate(node.right);
counterClockwiseRotate(node);
}
5.3.4. LR旋轉
先B,C逆時針旋轉,再A,B順時針旋轉
圖片來源:中國MOOC浙大數據結構第四章
public void leftRightRotate(AVLNode node) {
counterClockwiseRotate(node.left);
clockwiseRotate(node);
}
5.3.5. 逆時針旋轉
重新用回RR旋轉的圖,逆時針旋轉就是一個RR旋轉,本質就是交換A,B節點對別的節點的引用,再交換別的節點對A,B節點的引用而已。
public void counterClockwiseRotate(AVLNode node) {
AVLNode nodeOriginRight = node.right;
//1.右子樹佔node位
if(node.parent == null) { // 先確定是不是根節點,根節點不用處理parent
nodeOriginRight.parent = null;
root = nodeOriginRight;
} else {
if(node.parent.left.equals(node)) {
node.parent.left = nodeOriginRight;
} else {
node.parent.right = nodeOriginRight;
}
nodeOriginRight.parent = node.parent;
}
//2.node變成原右子樹的左子樹
AVLNode bl = nodeOriginRight.left;
nodeOriginRight.left = node;
node.parent = nodeOriginRight;
//3.node新右子樹變爲原右子樹的的左子樹
node.right = bl;
if (bl != null) { // 有可能bl是null
bl.parent = node;
}
node.balance = calculateBalance(node);
node.depth = calculateDepth(node);
nodeOriginRight.balance = calculateBalance(nodeOriginRight);
nodeOriginRight.depth = calculateDepth(nodeOriginRight);
}
5.3.6 順時針旋轉
就是逆時針操作反過來而已
public void clockwiseRotate(AVLNode node) {
AVLNode nodeOriginLeft = node.left;
// 1.左子樹佔node位
if (node.parent == null) {
nodeOriginLeft.parent = null;
root = nodeOriginLeft;
} else { // 更改node父節點的指向子結點指針
if(node.parent.left.equals(node)) {
node.parent.left = nodeOriginLeft;
} else {
node.parent.right = nodeOriginLeft;
}
nodeOriginLeft.parent = node.parent;
}
//2.node變成原左子樹的右子樹
AVLNode br = nodeOriginLeft.right;
nodeOriginLeft.right = node;
node.parent = nodeOriginLeft;
//3.node新左子樹變爲原左子樹的的右子樹
node.left = br;
if (br != null) {
br.parent = node;
}
node.balance = calculateBalance(node);
node.depth = calculateDepth(node);
nodeOriginLeft.balance = calculateBalance(nodeOriginLeft);
nodeOriginLeft.depth = calculateDepth(nodeOriginLeft);
}
6.刪除
刪除有三種情況,刪除節點沒有兒子,刪除節點有一個兒子,刪除節點有兩個兒子。
步驟爲:先比較大小確定是否是要刪除節點,再根據有幾個兒子調用不同的刪除方法,刪除完後進行平衡
public void delete(int data) {
if (root != null) {
deleteDetail(root, data);
}
}
private void deleteDetail(AVLNode node, int data) {
if (data < node.data) {
if (node.left != null) {
deleteDetail(node.left, data);
}
} else if (data > node.data) {
if(node.right != null) {
deleteDetail(node.right, data);
}
} else { // 不大不小則是節點是要刪除節點
if (node.left == null && node.right == null) { // 沒有兒子
deleteSonWithNoChild(node);
} else if (node.left == null || node.right == null) { // 有一個兒子
deleteSonWithOneChild(node);
} else { // 有兩個兒子
deleteSonWithTwoChild(node);
}
}
// 計算平衡和深度
// 由於node已被替換,且替換node的節點已經平衡過或不需要平衡,爲了整個遞歸的平衡過程,這裏平衡node的父節點
if (node.parent != null) {
node.parent.balance = calculateBalance(node.parent);
node.parent.depth = calculateDepth(node.parent);
// 平衡節點
balanceNode(node.parent);
}
}
6.1刪除無兒子節點
父節點的引用改爲null即可
private void deleteSonWithNoChild(AVLNode node) {
if (node.parent == null) {
root = null;
} else if (node.parent.left == node) {
node.parent.left = null;
} else if (node.parent.right == node) {
node.parent.right = null;
}
}
6.2 刪除有一個兒子的節點
父節點的兒子引用指向孫子節點,孫子節點的父引用指向父節點
private void deleteSonWithOneChild(AVLNode node) {
AVLNode temporary = ((node.right == null) ? node.left : node.right);
if (node.parent == null) {
root = temporary;
} else if (node.parent.left == node) {
node.parent.left = temporary;
} else if (node.parent.right == node) {
node.parent.right = temporary;
}
temporary.parent = node.parent;
}
6.3 刪除有兩個兒子的節點
用左子樹的最大節點(也可以用右子樹最小節點)來替換被刪除節點,步驟爲:
1.保存左子最大樹節點數據
2.刪除左子最大樹節點(這個節點要麼沒兒子,要麼只有一個兒子)
3.將要被刪除節點數據替換爲左子最大樹節點數據(由於步驟2刪除過程已調過平衡,這個節點不用再調平衡)
private void deleteSonWithTwoChild(AVLNode node) {
AVLNode temporary = searchMax(node.left);// 左子樹最大的的節點可以替換刪除的node
delete(temporary.data);
node.data = temporary.data;// 直接換數據
}
private AVLNode searchMax(AVLNode node) {
if (node.right == null) {
return node;
} else {
return searchMax(node.right);
}
}
7.前序遍歷,用於驗證二叉樹
public void preOrder() {
if (root != null) {
preOrderDetail(root);
}
}
public void preOrderDetail(AVLNode node) {
if (node != null) {
System.out.print(node.data);
System.out.print(" ");
preOrderDetail(node.left);
preOrderDetail(node.right);
}
}
8.全部代碼如下
package BinaryTree;
public class AVLTree {
public static void main(String[] args) {
// 驗證插入操作
BalancedBinaryTree test0 = new BalancedBinaryTree(100);
test0.insert(50);
test0.insert(200);
System.out.print("草稿紙上運算爲{100,50,200}: ");
test0.preOrder();
System.out.println();
// 驗證LL旋轉
// 發生在root
BalancedBinaryTree test1 = new BalancedBinaryTree(100);
test1.insert(70);
test1.insert(30);
test1.insert(10);
test1.insert(5);
System.out.print("草稿紙上運算爲{70,10,5,30,100}: ");
test1.preOrder();
System.out.println();
// 發生在非root
BalancedBinaryTree test4 = new BalancedBinaryTree(100);
test4.insert(50);
test4.insert(200);
test4.insert(150);
test4.insert(130);
System.out.print("草稿紙上運算爲{100,50,150,130,200 }: ");
test4.preOrder();
System.out.println();
// 驗證RR旋轉
// 發生在root
BalancedBinaryTree test2 = new BalancedBinaryTree(100);
test2.insert(200);
test2.insert(300);
test2.insert(400);
test2.insert(500);
System.out.print("草稿紙上運算爲{200,100,400,300,500}: ");
test2.preOrder();
System.out.println();
// 發生在非root
BalancedBinaryTree test3 = new BalancedBinaryTree(100);
test3.insert(50);
test3.insert(200);
test3.insert(75);
test3.insert(80);
System.out.print("草稿紙上運算爲{100,75,50,80,200}: ");
test3.preOrder();
System.out.println();
// 驗證LR旋轉
BalancedBinaryTree test5 = new BalancedBinaryTree(100);
test5.insert(50);
test5.insert(200);
test5.insert(75);
test5.insert(30);
test5.insert(80);
System.out.print("LR草稿紙上運算爲{75,50,30,100,80,200}: ");
test5.preOrder();
System.out.println();
// 驗證RL旋轉
BalancedBinaryTree test6 = new BalancedBinaryTree(100);
test6.insert(50);
test6.insert(200);
test6.insert(150);
test6.insert(300);
test6.insert(125);
System.out.print("RL草稿紙上運算爲{150,100,50,125,200,300}: ");
test6.preOrder();
System.out.println();
// 驗證刪除操作
BalancedBinaryTree test7 = new BalancedBinaryTree(100);
test7.insert(50);
test7.insert(200);
test7.insert(150);
test7.insert(300);
test7.insert(25);
test7.insert(75);
System.out.print("delete草稿紙上運算爲{100,50,25,75,200,150,300}: ");
test7.preOrder();
System.out.println();
// 刪除沒有兒子的節點
test7.delete(150);
System.out.print("刪除沒有兒子的節點150,delete草稿紙上運算爲{100 50 25 75 200 300 }: ");
test7.preOrder();
System.out.println();
// 刪除有一個兒子的節點
test7.delete(200);
System.out.print("刪除有一個兒子的節點200,delete草稿紙上運算爲{100 50 25 75 200 300 }: ");
test7.preOrder();
System.out.println();
// 刪除有兩個兒子的節點
test7.delete(100);
System.out.print("刪除有兩個兒子的節點100,delete草稿紙上運算爲{75,50,25,300}: ");
test7.preOrder();
System.out.println();
test7.delete(75);
System.out.print("刪除有兩個兒子的節點75,delete草稿紙上運算爲{50,25,300}: ");
test7.preOrder();
System.out.println();
test7.delete(50);
System.out.print("刪除有兩個兒子的節點50,delete草稿紙上運算爲{25,300}: ");
test7.preOrder();
System.out.println();
}
}
class BalancedBinaryTree {// 本類對插入相同數值,刪除不存在數值的行爲不會有反應
/**
* 節點
*/
class AVLNode {
public int data;
public int depth;
public int balance;
public AVLNode parent = null;
public AVLNode left = null;
public AVLNode right = null;
public AVLNode() {
depth = 1;
balance = 0;
left = null;
right = null;
}
public AVLNode(int data) {
this.data = data;
depth = 1;
balance = 0;
left = null;
right = null;
}
}
/**
* 根節點
*/
private AVLNode root = null;
/**
* 構造器
*/
public BalancedBinaryTree() {
}
public BalancedBinaryTree(int data) {
root = new AVLNode(data);
}
/**
* 插入
*/
public void insert(int data) {
if (root == null) {
root = new AVLNode(data);
} else {
insertSon(root, data);
}
}
private void insertSon(AVLNode node, int data) {
if (data < node.data) {
if (node.left != null) {
insertSon(node.left, data);
} else {
node.left = new AVLNode(data);
node.left.parent = node;//子節點鎖住parent
}
} else {
if (node.right != null) {
insertSon(node.right, data);
} else {
node.right = new AVLNode(data);
node.right.parent = node;
}
}
// 計算平衡和深度
node.balance = calculateBalance(node);
node.depth = calculateDepth(node);
// 平衡節點
balanceNode(node);
}
/**
* 刪除
*/
public void delete(int data) {
if (root != null) {
deleteDetail(root, data);
}
}
private void deleteDetail(AVLNode node, int data) {
if (data < node.data) {
if (node.left != null) {
deleteDetail(node.left, data);
}
} else if (data > node.data) {
if(node.right != null) {
deleteDetail(node.right, data);
}
} else { // 不大不小則是節點是要刪除節點
if (node.left == null && node.right == null) { // 沒有兒子
deleteSonWithNoChild(node);
} else if (node.left == null || node.right == null) { // 有一個兒子
deleteSonWithOneChild(node);
} else { // 有兩個兒子
deleteSonWithTwoChild(node);
}
}
// 計算平衡和深度
// 由於node已被替換,且替換node的節點已經平衡過或不需要平衡,爲了整個遞歸的平衡過程,這裏平衡node的父節點
if (node.parent != null) {
node.parent.balance = calculateBalance(node.parent);
node.parent.depth = calculateDepth(node.parent);
// 平衡節點
balanceNode(node.parent);
}
}
private void deleteSonWithNoChild(AVLNode node) {
if (node.parent == null) {
root = null;
} else if (node.parent.left == node) {
node.parent.left = null;
} else if (node.parent.right == node) {
node.parent.right = null;
}
}
private void deleteSonWithOneChild(AVLNode node) {
AVLNode temporary = ((node.right == null) ? node.left : node.right);
if (node.parent == null) {
root = temporary;
} else if (node.parent.left == node) {
node.parent.left = temporary;
} else if (node.parent.right == node) {
node.parent.right = temporary;
}
temporary.parent = node.parent;
}
private void deleteSonWithTwoChild(AVLNode node) {
AVLNode temporary = searchMax(node.left);// 左子樹最大的的節點可以替換刪除的node
delete(temporary.data);
node.data = temporary.data;// 直接換數據
}
private AVLNode searchMax(AVLNode node) {
if (node.right == null) {
return node;
} else {
return searchMax(node.right);
}
}
/**
* 計算平衡值
*/
private int calculateBalance(AVLNode node) {
int leftDepth;
int rightDepth;
if (node.left != null) {
leftDepth = node.left.depth;
} else {
leftDepth = 0;
}
if (node.right != null) {
rightDepth = node.right.depth;
} else {
rightDepth = 0;
}
return leftDepth - rightDepth;
}
/**
* 計算深度
*/
private int calculateDepth(AVLNode node) {
int depth = 0;
if (node.left != null) {
depth = node.left.depth;
}
if (node.right != null && depth < node.right.depth) {
depth = node.right.depth;
}
depth++;
return depth;
}
/**
* 平衡
*/
private void balanceNode(AVLNode node) {
if (node.balance <= -2) {
if (node.right.balance == -1) {
// RR插入,RR旋轉
rightRightRotate(node);
} else {
// RL插入,RL旋轉
rightLeftRotate(node);
}
} else if (node.balance >= 2) {
if (node.left.balance == 1) {
// LL插入,LL旋轉
leftLeftRotate(node);
} else {
// LR插入,LR旋轉
leftRightRotate(node);
}
}
}
/**
* 旋轉
*/
// 四種旋轉
public void rightRightRotate(AVLNode node) {
counterClockwiseRotate(node);
}
public void rightLeftRotate(AVLNode node) {
clockwiseRotate(node.right);
counterClockwiseRotate(node);
}
public void leftRightRotate(AVLNode node) {
counterClockwiseRotate(node.left);
clockwiseRotate(node);
}
public void leftLeftRotate(AVLNode node) {
clockwiseRotate(node);
}
// 兩個基本旋轉
// 逆時針旋轉
public void counterClockwiseRotate(AVLNode node) {
AVLNode nodeOriginRight = node.right;
//1.右子樹佔node位
if(node.parent == null) { // 先確定是不是根節點,根節點不用處理parent
nodeOriginRight.parent = null;
root = nodeOriginRight;
} else {
if(node.parent.left.equals(node)) {
node.parent.left = nodeOriginRight;
} else {
node.parent.right = nodeOriginRight;
}
nodeOriginRight.parent = node.parent;
}
//2.node變成原右子樹的左子樹
AVLNode bl = nodeOriginRight.left;
nodeOriginRight.left = node;
node.parent = nodeOriginRight;
//3.node新右子樹變爲原右子樹的的左子樹
node.right = bl;
if (bl != null) { // 有可能bl是null
bl.parent = node;
}
node.balance = calculateBalance(node);
node.depth = calculateDepth(node);
nodeOriginRight.balance = calculateBalance(nodeOriginRight);
nodeOriginRight.depth = calculateDepth(nodeOriginRight);
}
// 順時針旋轉:counterClockwiseRotate代碼反過來
public void clockwiseRotate(AVLNode node) {
AVLNode nodeOriginLeft = node.left;
// 1.左子樹佔node位
if (node.parent == null) {
nodeOriginLeft.parent = null;
root = nodeOriginLeft;
} else { // 更改node父節點的指向子結點指針
if(node.parent.left.equals(node)) {
node.parent.left = nodeOriginLeft;
} else {
node.parent.right = nodeOriginLeft;
}
nodeOriginLeft.parent = node.parent;
}
//2.node變成原左子樹的右子樹
AVLNode br = nodeOriginLeft.right;
nodeOriginLeft.right = node;
node.parent = nodeOriginLeft;
//3.node新左子樹變爲原左子樹的的右子樹
node.left = br;
if (br != null) {
br.parent = node;
}
node.balance = calculateBalance(node);
node.depth = calculateDepth(node);
nodeOriginLeft.balance = calculateBalance(nodeOriginLeft);
nodeOriginLeft.depth = calculateDepth(nodeOriginLeft);
}
/**
* 前序遍歷
*/
public void preOrder() {
if (root != null) {
preOrderDetail(root);
}
}
public void preOrderDetail(AVLNode node) {
if (node != null) {
System.out.print(node.data);
System.out.print(" ");
preOrderDetail(node.left);
preOrderDetail(node.right);
}
}
}