Balanced Binary (AVL) Tree

introduction

In the previous series of articles, we talked about the relevant knowledge and implementation of binary search trees ( binary tree (1)-overview (with binary search tree implementation) ). At the end of the article, we summarized the search time complexity of the binary search tree:

对于一个节点分布相对均衡的二叉查找树来说,如果节点总数是n,那么搜索节点的时间复杂度是O(logn),和树的深度是一样的。

但对于极端情况(每次插入数据大小都是增大的,或者减小的),外形上看就只有一半的树,查找时间复杂度就会退化成O(n)的

In order to solve the problem of the degradation of the search time complexity of the binary search tree, the self-balancing of the binary data is needed, which is what this section will talk about 平衡二叉树. Through its own balance adjustment, search, insert, and delete operations are O(logn) in the average and worst cases. The tree height of the binary search tree affects the efficiency of the search, and the height of the tree needs to be reduced as much as possible. The AVL tree is just such a tree.

Introduction to AVL Trees

The balanced binary tree is also called the AVL tree. The name of the AVL tree comes from its inventors GM Adelson-Velsky and EM Landis. The AVL tree is the first self-balancing binary search tree (Self-Balancing Binary Search Tree, referred to as the balanced binary search tree) invented.

Balanced Binary Tree Definition (AVL):

It is either an empty tree or a binary sort tree with the following properties: the absolute value of the difference (balance factor) between the depths of its left and right subtrees does not exceed 1, and its left and right subtrees The subtree is a balanced binary tree.

An AVL tree has the following necessary conditions:

  1. It must be a binary search tree (refer to binary search tree for specific definition )
  2. The height difference between the left subtree and the right subtree of any node is at most 1.

Insert picture description here
The left child 46 of node 45 of the binary tree on the left in Figure 1 is greater than 45, which does not meet the conditions of the binary search tree, so it is not a balanced binary tree.
The binary tree on the right satisfies the condition of the binary search tree, and at the same time it satisfies the condition two, so it is a balanced binary tree.

Insert picture description here
The node 45 of the binary tree on the left has a height of left subtree of 2, and a height of 0 of right subtree. The height difference of left and right subtrees is 2-0=2, which does not satisfy condition two;
the nodes of the binary tree on the right meet the height difference of left and right subtrees at most 1. It meets the requirements of a binary search tree, so it is a balanced binary tree.

AVL树的查找、插入、删除操作在平均和最坏的情况下都是O(logn),这得益于它时刻维护着二叉树的平衡。
如果我们需要查找的集合本身没有顺序,在频繁查找的同时也经常的插入和删除,AVL树是不错的选择。
不平衡的二叉查找树在查找时的效率是很低的,因此,AVL如何维护二叉树的平衡是我们的学习重点。

AVL tree related concepts

  1. 平衡因子:The value of the left subtree height minus the right subtree height of the node on the binary tree is called the balance factor BF (Balance Factor) of the node.
    On the AVL tree on the right of Figure 2: the
    height of the left subtree of node 50 is 3, the height of the right subtree is 2, BF= 3-2 = 1; the
    height of the left subtree of node 45 is 2, and the height of the right subtree is 1. , BF= 2-1 = 1; the
    height of the left subtree of node 46 is 0, the height of the right subtree is 0, BF=0-0 = 0; the
    height of the left subtree of node 65 is 0, and the height of the right subtree is 1. , BF= 0-1 = -1;
    For a balanced binary tree, the value range of BF is [-1,1]. If you find that the BF value of a node is not in this range, you need to adjust the tree.

  2. 最小不平衡子树: The node closest to the inserted node and whose absolute value of the balance factor is greater than 1 is the subtree of the root.
    Insert picture description here
    In Figure 3, the node 45 of the binary tree on the left has BF = 1. After inserting the node 43, the node 45 has BF = 2. The node 45 is the node whose BF closest to the insertion point 43 is not in the range of [-1,1], so the subtree rooted at the node 45 is the smallest unbalanced subtree.

Balance adjustment of AVL tree

Every time you add and delete elements, if there is an imbalance (judging by the balance factor), you need to perform one or more rotations to adjust the balance of the tree according to the position relationship between the newly inserted node and the lowest imbalance node.
There are 4 types, LL type, RR type, LR type and RL type. The adjustment methods are as follows (the lowest unbalanced node is represented by A below):

LL type adjustment

Since a new node is inserted into the left subtree (L) of A's left child (L), the original balanced binary tree becomes unbalanced. At this time, the balance factor of A increases from 1 to 2. Figure 1 below is the simplest form of LL type. Obviously, according to the size relationship, node B should be used as the new root node, and the remaining two nodes can be used as left and right child nodes to balance. Node A is like rotating node B clockwise.
Insert picture description here
The general form of LL-type adjustment is shown in Figure 2 below, which means that a node (shown in the shaded part in the figure) is inserted into the left subtree BL (not necessarily empty) of A's left child B, resulting in imbalance (h means subtree The depth of the tree). This situation is adjusted as follows:①将A的左孩子B提升为新的根结点;②将原来的根结点A降为B的右孩子;③各子树按大小关系连接(BL和AR不变,BR调整为A的左子树)。

Insert picture description here
Code:
Insert picture description here

RR type adjustment

Since a new node is inserted into the right child of A's right subtree, the original balanced binary tree becomes unbalanced, and the balance factor of A changes from -1 to -2. Figure 3 is the simplest form of the RR type. Obviously, according to the size relationship, node B should be used as the new root node, and the remaining two nodes can be used as left and right child nodes to balance. Node A is like rotating node B counterclockwise.
Insert picture description here
The general form of RR-type adjustment is shown in Figure 4 below, which means that a node (shown in the shaded part in the figure) is inserted into the right subtree BR (not necessarily empty) of A's right child B, resulting in imbalance (h means subtree The depth of the tree). This situation is adjusted as follows:

  1. Promote A's right child B to the new root node;
  2. Reduce the original root node A to the left child of B
  3. The subtrees are connected according to the size relationship (AL and BR remain unchanged, BL is adjusted to the right subtree of A).

Insert picture description here
Code:
Insert picture description here

LR type adjustment

Since a new node is inserted into the right subtree ® of A's left child (L), the original balanced binary tree becomes unbalanced. At this time, the balance factor of A changes from 1 to 2. Figure 5 is the simplest form of LR type. Obviously, according to the size relationship, node C should be used as the new root node, and the remaining two nodes can be balanced as left and right child nodes.

Insert picture description here
The general form of LR adjustment is shown in Figure 6 below, which means that a node (one of the two shaded parts in the figure) is inserted into the right subtree of A's left child B (the root node is C, not necessarily empty). Causes imbalance (h represents the depth of the subtree). This situation is adjusted as follows: ①The left child C of B is promoted to the new root node; ②The original root node A is reduced to the right child of C; ③The subtrees are connected according to the size relationship (BL and AR remain unchanged , CL and CR are respectively adjusted to the right subtree of B and the left subtree of A).

Insert picture description here
Code:
Insert picture description here

RL type adjustment

Since a new node is inserted into the left subtree (L) of A's right child®, the original balanced binary tree becomes unbalanced, and the balance factor of A changes from -1 to -2. Figure 7 is the simplest form of the RL type. Obviously, according to the size relationship, node C should be used as the new root node, and the remaining two nodes can be balanced as left and right child nodes.
Insert picture description here
The general form of RL adjustment is shown in the following figure, which means that a node (one of the two shaded parts in the figure) is inserted into the left subtree of A's right child B (the root node is C, not necessarily empty) Unbalanced (h represents the depth of the subtree). This situation is adjusted as follows: ①The left child C of B is promoted to the new root node; ②The original root node A is reduced to the left child of C; ③The subtrees are connected according to the size relationship (AL and BR remain unchanged , CL and CR are adjusted to the right subtree of A and the left subtree of B respectively).
Insert picture description here
Code:
Insert picture description here

Code

package main

/*
	AVL树
*/

import "fmt"

// 平衡二叉树
type AVLTree struct {
    
    
	RootNode *TreeNode //树的根节点
}

//平衡二叉树节点
type TreeNode struct {
    
    
	Data   int32     // 节点上的数据
	Left   *TreeNode // 指针指向左孩子节点
	Right  *TreeNode // 指针指向右孩子节点
	Height int       // 该节点高度,方便计算平衡因子(相比二叉查找树新增的节点信息,用来判断是否需要进行自平衡调整)
}

// 判断平衡二叉树是否为空树,只需要判断第一个元素是否是 nil
func (self *AVLTree) IsEmpty() bool {
    
    
	if self.RootNode == nil {
    
    
		return true
	} else {
    
    
		return false
	}
}

// look LL
func left_left_rotation(k *TreeNode) *TreeNode {
    
    
	var kl *TreeNode
	kl = k.Left
	k.Left = kl.Right
	kl.Right = k
	k.Height = max(k.Left.height(), k.Right.height()) + 1
	kl.Height = max(kl.Left.height(), k.height()) + 1
	return kl
}

//look RR
func right_right_rotation(k *TreeNode) *TreeNode {
    
    
	var kr *TreeNode
	kr = k.Right
	k.Right = kr.Left
	kr.Left = k
	k.Height = max(k.Left.height(), k.Right.height()) + 1
	kr.Height = max(k.height(), kr.Right.height()) + 1
	return kr
}

//look LR
func left_righ_rotation(k *TreeNode) *TreeNode {
    
    
	k.Left = right_right_rotation(k.Left)
	return left_left_rotation(k)
}

//look RL
func right_left_rotation(k *TreeNode) *TreeNode {
    
    
	k.Right = left_left_rotation(k.Right)
	return right_right_rotation(k)
}

// 取最大值方法
func max(a ...int) int {
    
    
	if len(a) < 1 {
    
    
		panic("params count < 1")
	}
	max := a[0]
	for _, i := range a {
    
    
		if i > max {
    
    
			max = i
		}
	}
	return max
}

func (node *TreeNode) Add(value int32) *TreeNode {
    
    
	/*
		在平衡二叉树里添加数据
		和二叉查找树的插入方法有所不同
		二叉查找树Add方法的拥有者是树,平衡二叉树的Add方法拥有者是头节点
		因为平衡二叉树在插入数据时需要更新其父代节点的高度,所以要使用递归
	*/
	if node == nil {
    
    
		node = &TreeNode{
    
    Data: value}
	} else if value < node.Data {
    
    
		node.Left = node.Left.Add(value)
		if node.Left.height()-node.Right.height() == 2 {
    
    
			if value < node.Left.Data {
    
     //LL
				node = left_left_rotation(node)
			} else {
    
     // LR
				node = left_righ_rotation(node)
			}
		}
	} else if value > node.Data {
    
    
		node.Right = node.Right.Add(value)
		if (node.Right.height() - node.Left.height()) == 2 {
    
    
			if value < node.Right.Data {
    
     // RL
				node = right_left_rotation(node)
			} else {
    
     //RR
				node = right_right_rotation(node)
			}
		}
	} else if value == node.Data {
    
    
		fmt.Println("the value", value, "has existed!")
	}
	//注意:更新高度(可能是不插入旋转值,因此您应该更新高度)
	node.Height = max(node.Left.height(), node.Right.height()) + 1
	return node
}

//删除指定index节点
//查找节点 ---> 删除节点 ----> 调整树结构
//删除节点时既要遵循二叉搜索树的定义又要符合二叉平衡树的要求   ---> 重点处理删除节点的拥有左右子树的情况
func (node *TreeNode) Delete(value int32) *TreeNode {
    
    
	//todo
	return nil
}

// 这个很关键,和更新节点高度息息相关
func (node *TreeNode) height() int {
    
    
	if node == nil {
    
    
		return -1
	}
	return node.Height
}

// 查找元素所在的节点
func (self *AVLTree) Get(value int32) *TreeNode {
    
    
	node := self.RootNode
	for node != nil {
    
    
		if value < node.Data {
    
    
			node = node.Left
		} else if value > node.Data {
    
    
			node = node.Right
		} else {
    
    
			return node
		}
	}
	return nil
}

//前序遍历
func (self *TreeNode) preOrderTraversal(res *[]int32) []int32 {
    
    
	if self == nil {
    
    
		return *res
	}
	*res = append(*res, self.Data)
	self.Left.preOrderTraversal(res)
	self.Right.preOrderTraversal(res)
	return *res
}

func NewAVLTree() *AVLTree {
    
    
	return new(AVLTree)
}

func main() {
    
    
	avlTree := NewAVLTree()
	data := []int32{
    
    9, 3, 2, 1, 4, 5, 6, 7, 16, 15, 14, 13, 12, 11, 10, 8}
	for _, value := range data {
    
    
		avlTree.RootNode = avlTree.RootNode.Add(value)
	}
	fmt.Println(avlTree.RootNode.Data)
	fmt.Println(avlTree.Get(7).height())
	var res []int32
	fmt.Println(avlTree.RootNode.preOrderTraversal(&res))
}

Reference:
data structure and algorithm (Golang implementation) (28) search algorithm-AVL tree

Guess you like

Origin blog.csdn.net/csdniter/article/details/111203731