1. 程式人生 > 其它 >平衡二叉樹圖解以及程式碼實現

平衡二叉樹圖解以及程式碼實現

技術標籤:資料結構與演算法二叉樹演算法資料結構

平衡二叉樹

平衡二叉搜尋樹(Self-balancing binary search tree)又被稱為AVL樹(有別於AVL演算法),且具有以下性質:

  • 它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1;
  • 左右兩個子樹都是一棵平衡二叉樹。

是為了解決二叉查詢樹查詢節點在最壞的情況下,該二叉樹有可能退化成一個連結串列。

AVL樹除了滿足二叉查詢樹的性質之外,AVL樹的特性如下:

  • AVL樹的左右子樹的高度差別小於等於1
  • AVL樹的左右子樹也是AVL樹。

平衡因子:

定義:某結點的左子樹與右子樹的高度(深度)差即為該結點的平衡因子(BF,Balance Factor)。

平衡二叉樹上所有結點的平衡因子只可能是-101。如果某一結點的平衡因子絕對值大於1則說明此樹不是平衡二叉樹。為了方便計算每一結點的平衡因子我們可以為每個節點賦予height這一屬性,表示此節點的高度。

二叉樹失衡的情況有LLRRLRRL四種,調整二叉樹為平衡二叉樹的方法有2種:右旋、左旋。

1、解決失衡

  • LL(右旋):

在這裡插入圖片描述

/**
 * 右旋轉
 */
private Node rightRotate(Node y){
	Node x = y.left;
	Node t3 = x.right;
	x.right = y;
	y.left = t3;
	//更新height
	y.height =
Math.max(getHeight(y.left),getHeight(y.right))+1; x.height = Math.max(getHeight(x.left),getHeight(x.right))+1; return x; }
  • RR左旋:

在這裡插入圖片描述

/**
 * 左旋轉
 */
private Node leftRotate(Node y){
	Node x = y.right;
	Node t2 = x.left;
	x.left = y;
	y.right = t2;
	//更新height
	y.height = Math.max(getHeight(y.left),getHeight
(y.right))+1; x.height = Math.max(getHeight(x.left),getHeight(x.right))+1; return x; }
  • LR先左旋後右旋:

在這裡插入圖片描述

  • RL先右旋後左旋:

同理LR的先左旋後右旋。

1、新增節點

// 向二分搜尋樹中新增新的元素(key, value)
public void add(E e){
	root = add(root, e);
}

// 向以node為根的二分搜尋樹中插入元素(key, value),遞迴演算法
// 返回插入新節點後二分搜尋樹的根
private Node add(Node node, E e){
	if(node == null){
		size ++;
		return new Node(e);
	}
	if(e.compareTo(node.e) < 0)
		node.left = add(node.left, e);
	else if(e.compareTo(node.e) > 0)
		node.right = add(node.right, e);
	//更新height
	node.height = 1+Math.max(getHeight(node.left),getHeight(node.right));
	//計算平衡因子
	int balanceFactor = getBalanceFactor(node);
	if(balanceFactor > 1 && getBalanceFactor(node.left)>0) {
		//右旋LL
		return rightRotate(node);
	}
	if(balanceFactor < -1 && getBalanceFactor(node.right)<0) {
		//左旋RR
		return leftRotate(node);
	}
	//LR
	if(balanceFactor > 1 && getBalanceFactor(node.left) < 0){
		node.left = leftRotate(node.left);
		return rightRotate(node);
	}
	//RL
	if(balanceFactor < -1 && getBalanceFactor(node.right) > 0){
		node.right = rightRotate(node.right);
		return leftRotate(node);
	}
	return node;
}

2、刪除節點

分為以下3種情況:

(1)當刪除的節點是葉子節點,則將節點刪除,然後從父節點開始,判斷是否失衡,如果沒有失衡,則再判斷父節點的父節點是否失衡,直到根節點,此時到根節點還發現沒有失衡,則說此時樹是平衡的;如果中間過程發現失衡,則判斷屬於哪種型別的失衡(左左,左右,右左,右右),然後進行調整。

(2)刪除的節點只有左子樹或只有右子樹,這種情況其實就比刪除葉子節點的步驟多一步,就是將節點刪除,然後把僅有一支的左子樹或右子樹替代原有結點的位置,後面的步驟就一樣了,從父節點開始,判斷是否失衡,如果沒有失衡,則再判斷父節點的父節點是否失衡,直到根節點,如果中間過程發現失衡,則根據失衡的型別進行調整。

(3)刪除的節點既有左子樹又有右子樹,這種情況又比上面這種多一步,就是中序遍歷,找到待刪除節點的前驅或者後驅都行,然後與待刪除節點互換位置,然後把待刪除的節點刪掉,後面的步驟也是一樣,判斷是否失衡,然後根據失衡型別進行調整。

public E remove(E e){
	Node node = getNode(root, e);
	if(node != null){
		root = remove(root, e);
		return node.e;
	}
	return null;
}

private Node remove(Node node, E e){

	if( node == null )
		return null;
	Node retNode;
	if( e.compareTo(node.e) < 0 ){
		node.left = remove(node.left , e);
		retNode = node;
	}
	else if(e.compareTo(node.e) > 0 ){
		node.right = remove(node.right, e);
		retNode = node;
	}
	else{   // e.compareTo(node.e) == 0
		// 待刪除節點左子樹為空的情況
		if(node.left == null){
			Node rightNode = node.right;
			node.right = null;
			size --;
			retNode = rightNode;
		}
		// 待刪除節點右子樹為空的情況
		else if(node.right == null){
			Node leftNode = node.left;
			node.left = null;
			size --;
			retNode = leftNode;
		}else {
			// 待刪除節點左右子樹均不為空的情況
			// 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
			// 用這個節點頂替待刪除節點的位置
			Node successor = minimum(node.right);
			successor.right = remove(node.right, successor.e);
			successor.left = node.left;

			node.left = node.right = null;

			retNode = successor;
		}
	}
	if(retNode==null)
		return null;
	//維護平衡
	//更新height
	retNode.height = 1+Math.max(getHeight(retNode.left),getHeight(retNode.right));
	//計算平衡因子
	int balanceFactor = getBalanceFactor(retNode);
	if(balanceFactor > 1 && getBalanceFactor(retNode.left)>=0) {
		//右旋LL
		return rightRotate(retNode);
	}
	if(balanceFactor < -1 && getBalanceFactor(retNode.right)<=0) {
		//左旋RR
		return leftRotate(retNode);
	}
	//LR
	if(balanceFactor > 1 && getBalanceFactor(retNode.left) < 0){
		node.left = leftRotate(retNode.left);
		return rightRotate(retNode);
	}
	//RL
	if(balanceFactor < -1 && getBalanceFactor(retNode.right) > 0){
		node.right = rightRotate(retNode.right);
		return leftRotate(retNode);
	}
	return retNode;
}

最後總結一下,平衡二叉樹是一棵高度平衡的二叉樹,所以查詢的時間複雜度是 O(logN)插入的話上面也說,失衡的情況有4種,左左,左右,右左,右右,即一旦插入新節點導致失衡需要調整,最多也只要旋轉2次,所以,插入複雜度是 O(1) ,但是平衡二叉樹也不是完美的,也有缺點,從上面刪除處理思路中也可以看到,就是刪除節點時有可能因為失衡,導致需要從刪除節點的父節點開始,不斷的回溯到根節點,如果這棵平衡二叉樹很高的話,那中間就要判斷很多個節點。所以後來也出現了綜合性能比其更好的樹—-紅黑樹