1. 程式人生 > 實用技巧 >Java資料結構(十四)—— 平衡二叉樹(AVL樹)

Java資料結構(十四)—— 平衡二叉樹(AVL樹)

平衡二叉樹(AVL樹)

二叉排序樹問題分析

  1. 左子樹全部為空,從形式上看更像一個單鏈表

  2. 插入速度沒有影響

  3. 查詢速度明顯降低

  4. 解決方案:平衡二叉樹

基本介紹

  1. 平衡二叉樹也叫二叉搜尋樹,保證查詢效率較高

  2. 它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩棵子樹都是一棵平衡二叉樹

  3. 常用的實現方法有紅黑樹、AVL、替罪羊樹、Treap、伸展樹等

平衡二叉樹左旋轉

使用條件

右子樹高度與左子樹高度插值大於1的時候,使用左旋轉

要求

給定數列{4,3,6,5,7,8},建立對應的平衡二叉樹

建立二叉排序樹

此時若轉換為平衡二叉樹,降低右子樹的高度

思路分析

  1. 建立一個新節點newNode ,值等於當前根節點的值

  2. 把新節點的左子樹設定為當前節點的左子樹,newnode.left = left

  3. 把新結點的右子樹設定為當前節點右子樹的左子樹newNode.right = right.left

  4. 把當前節點的值換位右子節點的值 value = right.value

  5. 把當前節點的右子樹設定成右子樹的右子樹right = right.right

  6. 把當前節點的左子樹設定為新節點 left = newLeft

轉換後

平衡二叉樹右旋轉

要求

使用數列{10,12,8,9,7,6},建立平衡二叉樹

建立二叉排序樹

基本思路

  1. 建立新的節點newNode,使得newNode.value = this.value

  2. 將newNode的右子樹設定為this的右子樹,newNode.right = this.right

  3. 將newNode的左子樹設定為this左子樹的右子樹,newNode.left = this.left.right

  4. 把this節點的值換為左子節點的值,this.value = this.left.value

  5. 將this節點的左子樹設定為左子樹的左子樹,this.left = this.left.left

  6. 將this節點的右子樹 設定為newNode,this.right = newNode

轉換後

平衡二叉樹雙旋轉

要求

使用數列{10,11,7,6,8,9},建立平衡二叉樹

建立二叉排序樹

思路分析

  • 當孩子節點滿足左旋轉或右旋轉條件時,先平衡孩子 節點,後平衡父節點

建立平衡二叉樹程式碼實現

package com.why.tree;

/**
* @Description TODO 平衡二叉樹
* @Author why
* @Date 2020/12/6 15:56
* Version 1.0
**/
public class AVLTreeDemo {
public static void main(String[] args) {
int[] arr = {10,11,7,6,8,9};
//建立AVLTree物件
AVLTree avlTree = new AVLTree();

//新增節點
for (int i = 0; i < arr.length; i++) {
avlTree.add(new AVLNode(arr[i]));
}

//遍歷
System.out.println("中序遍歷:");
avlTree.midOrder();

//根節點樹的高度
System.out.println("根節點樹的高度: " + avlTree.height());
System.out.println("左子樹高度:" + avlTree.leftHeight());
System.out.println("右子樹高度:" + avlTree.rightHeight());
System.out.println("根節點:" + avlTree.getRoot());

}
}

/**
* AVL,平衡二叉樹
*/
class AVLTree{
private AVLNode root;

public AVLNode getRoot() {
return root;
}

public void setRoot(AVLNode root) {
this.root = root;
}

/**
* 新增節點
* @param node
*/
public void add(AVLNode node){
if (root == null){//直接放上
root = node;
}else {
root.add(node);
}
}

/**
* 中序遍歷
*/
public void midOrder(){
if (root != null){
root.midOrder();
}else {
System.out.println("二叉排序樹為空");
}
}

/**
* 查詢需刪除的節點
* @param value
* @return
*/
public AVLNode search(int value){
if (root == null){
return null;
}else {
return root.search(value);
}
}

/**
* 查詢父節點
* @param value
* @return
*/
public AVLNode searchParent(int value){
if (root == null){
return null;
}else {
return root.searchParent(value);
}
}

public void deleteNode(int value){
if (root == null){
return;
}else {
//找到需刪除的節點
AVLNode targetNode = search(value);
if (targetNode == null){//未找到
return;
}
//如果二叉排序樹只有一個節點
if (root.left == null && root.right == null){
return;
}

//查詢需刪除的節點的父節點
AVLNode parent = searchParent(value);
if (targetNode.left == null && targetNode.right == null){//刪除的節點是葉子節點
//判斷targetNode是父節點的左子節點還是右子節點
if (parent.left != null && parent.left.value == value){//是左子節點
parent.left = null;
}else if (parent.right != null && parent.right.value == value){//是右子節點
parent.right = null;
}
}else if ((targetNode.left != null && targetNode.right == null) ||
(targetNode.right != null && targetNode.left == null)) {//只有一棵子樹
//確定targetNode的節點是左節點還是右節點
if (targetNode.left != null) {//左子節點
if (parent != null){//非根節點
//確定targetNode是parent的左子節點還是右子節點
if (parent.left.value == value) {//左子節點
parent.left = targetNode.left;
} else {//右子節點
parent.right = targetNode.left;
}
}else {
root = targetNode.left;
}
} else {//右子節點
if (parent != null){
//確定targetNode是parent的左子節點還是右子節點
if (parent.left.value == value) {//左子節點
parent.left = targetNode.right;
} else {//右子節點
parent.right = targetNode.right;
}
}else {
root = targetNode.right;
}
}
}else {//刪除的節點有兩顆子樹
//找到最小值並刪除
int minValue = deleteRightMin(targetNode.right);
//將最小值賦值給targetNode.value
targetNode.value = minValue;
}
}
}

/**
* 尋找最小值
* @param node
* @return
*/
public int deleteRightMin(AVLNode node){
AVLNode target = node;
while (target.left != null){
target = target.left;
}
//這時target指向最小節點
//刪除最小節點
deleteNode(target.value);
//返回最小節點的value
return target.value;
}

/**
* 返回根節點樹的高度
* @return
*/
public int height(){
return root.height();
}

/**
* 左子樹的高度
* @return
*/
public int leftHeight(){
return root.leftHeight();
}

/**
* 右子樹的高度
* @return
*/
public int rightHeight(){
return root.rightHeight();
}

}

/**
* 節點類
*/
class AVLNode{
int value;
AVLNode left;
AVLNode right;

public AVLNode(int value) {
this.value = value;
}

/**
* 新增節點,遞迴形式,需滿足二叉排序樹的要求
* @param node
*/
public void add(AVLNode 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
if (rightHeight() - leftHeight() > 1){
if (right != null && right.leftHeight() > right.rightHeight()){
//對右子樹 右旋轉
right.rightRotate();
}
//左旋轉
this.leftRotate();
return;
}
//當新增完節點後leftHeight - rightHeight > 1
if (leftHeight() - rightHeight() > 1){
if (left != null && left.rightHeight() > left.leftHeight()){
//對左子樹左旋轉
left.leftRotate();
}
//右旋轉
this.rightRotate();
return;
}
}

/**
* 中序遍歷
*/
public void midOrder(){
if (left != null){
this.left.midOrder();
}
System.out.println(this);
if (this.right != null){
this.right.midOrder();
}
}

@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}

/**
* 尋找需要刪除的節點
* @param value
* @return
*/
public AVLNode 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);
}
}

/**
* 查詢需要刪除節點的父節點
* @param value
* @return
*/
public AVLNode 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;//沒有找到父節點
}
}
}

/**
* 返回以當前節點為根節點的樹的高度
* @return
*/
public int height(){
return Math.max(this.left == null ? 0 : this.left.height(),this.right == null ? 0 : this.right.height()) + 1;
}

/**
* 返回左子樹的高度
* @return
*/
public int leftHeight(){
if (left == null){
return 0;
}else {
return left.height();
}
}

/**
* 返回右子樹的高度
* @return
*/
public int rightHeight(){
if (right == null){
return 0;
}else {
return right.height();
}
}

/**
* 左旋轉方法
*/
private void leftRotate(){
//建立新的節點,以當前根節點的值建立
AVLNode newNode = new AVLNode(this.value);
//把新的節點的左子樹設定為當前節點的左子樹
newNode.left = this.left;
//把新節點的右子樹設定為當前節點右子樹的左子樹
newNode.right = this.right.left;
//將當前節點的值修改為右子樹的值
this.value = this.right.value;
//將當前節點的右子樹設定為右子樹的右子樹
this.right = this.right.right;
//將當前節點的左子節點設定為新的節點
this.left = newNode;
}

/**
* 右旋轉
*/
private void rightRotate(){
//以當前節點的值建立新的節點
AVLNode newNode = new AVLNode(this.value);
//將新節點的右子樹設定為當前節點的右子樹
newNode.right = this.right;
//將當前節點的左子樹設定為當前節點左子節點的右子樹
newNode.left = this.left.right;
//將當前節點的值用左子節點的值替換
this.value = this.left.value;
//將當前節點的左子節點設定為當節點左子節點的左子樹
this.left = this.left.left;
//將當前節點的右子節點設定為新節點
this.right = newNode;
}
}