【数据结构】树(六)—— 二叉平衡树(C语言版)

本文介绍了平衡二叉搜索树(AVL树)的概念,强调了其在查找效率上的优势,特别是在插入操作后通过平衡因子进行调整以保持平衡。详细阐述了四种旋转操作(LL、RR、LR、RL)来修复不平衡,并提供了一个C语言版的AVL树插入操作的代码实现,展示了如何在插入后恢复树的平衡,确保平均查找长度为O(log2(n))。
摘要由CSDN通过智能技术生成

前言

在 《树(五)—— 二叉排序树》一文中,对二叉排序树进行性能分析时可知,当为二叉排序树为完全二叉树时,其查找性能最佳,与二分查找类似。故需要对二叉排序树进行优化为二叉平衡树。

1. 平衡二叉树的定义

平衡二叉树可定义为或者是一棵空树,或者是具有下列性质的二叉树:其左子树和右子树均为平衡二叉树,且左子树和右子树的高度差的绝对值不超过1。

平衡二叉树(Balanced Binary Tree)又叫平衡二叉搜索树(Self-balancing Binary Search Tree),又被称为AVL树。

平衡因子:定义结点左子树与右子树的高度差为该结点的,则平衡二叉树结点的平衡因子的值只可能是-1、0或1。

注意:平衡二叉树一定是二叉排序树。含有n个结点的平衡二叉树的最大深度为O(log2(n)),即平衡二叉树的平均查找长度为O(log2(n))。

如下图所示为一棵平衡二叉树和一棵非平衡二叉树:

在这里插入图片描述

typedef struct AVLNode{
    int key;        //数据域
    int balance;    //平衡因子
    struct AVLNode *lchild,*rchild;
}AVLNode, *AVLTree;

2. 平衡二叉树的插入

在二叉排序树中插入新结点后,如何保持平衡?

在这里插入图片描述

将最小不平衡子树调整平衡即可。

在这里插入图片描述

在插入操作中,只要将最小不平衡子树调整平衡,则其他祖先结点都会恢复平衡。

如何调整不平衡子树

在这里插入图片描述

LL平衡旋转(右单旋转)。由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。

在这里插入图片描述
RR平衡旋转(左单旋转)。由于在结点A的右孩子(R)的右子树®上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。

在这里插入图片描述

在这里插入图片描述

LR平衡旋转(先左后右双旋转)。由于在A的左孩子(L)的右子树(R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后把该C结点向右上旋转提升到A结点的位置。

在这里插入图片描述

注意:LR和RL旋转时,新结点究竟是插入C的左子树还是插入C的右子树不影响旋转过程,而图5.30和图5.31中以插入C的左子树中为例。

在这里插入图片描述

RL平衡旋转(先右后左双旋转)。由于在A的右孩子®的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后把该C结点向左上旋转提升到A结点的位置,

在这里插入图片描述

只有左孩子才能右上璇,只有右孩子才能左上璇

在这里插入图片描述

插入操作导致“最小不平衡子树”高度+1,经过调整后高度恢复。。

同理其他祖先结点也都会恢复

3. 查找效率分析

在这里插入图片描述

4. 完整代码

package Tree;

public class AVLTree {
	public static void main(String[] args) {
		int[] arr= {10,11,7,6,8,9};
		AVL avlTree=new AVL();
		for(int i=0;i<arr.length;i++) {
			 avlTree.add(new Node(arr[i]));
		}
		
		System.out.println("初始平衡二叉树的中序遍历");
		avlTree.inOrder();
		System.out.println("双旋转处理后:");
		avlTree.inOrder();
	    System.out.println("树的高度=" + avlTree.getRoot().height()); // 3
	    System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); // 2
	    System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight()); // 2
	    System.out.println("当前的根结点=" + avlTree.getRoot());// 8
	    System.out.println("根节点的左结点=" + avlTree.getRoot().left);// 7
	    System.out.println("根节点的右结点=" + avlTree.getRoot().right);// 10
	}
}

class AVL{
	private Node root;
	
	public Node getRoot() {//获取根结点
		return root;
	}
	
	public void add(Node node) {//添加子结点
		if (root == null) {//若根结点为空直接让添加结点成为子结点
			root = node;
		} else {
			root.add(node);
		}
	}
	
	public void inOrder() {//中序遍历
		if(root!=null) {//若根结点不为空则调用结点的inOrder
			root.inOrder();
		}else {
			System.out.println("平衡二叉树为空,无法遍历!");
		}
	}
}

class Node{
	int value;
	Node left;
	Node right;
	
	 public Node(int value) {//Node的构造函数
		 this.value=value;
	 }
	 
	@Override
	public String toString() {//重写toString方法
		return "Node [value=" + value + "]";
	}
	 
	public int height() {//返回以该结点为根结点的树的高度
		return Math.max(left==null ? 0:left.height(), right==null ? 0:right.height())+1;
	}
	
	public int leftHeight() {//返回左子树的高度
		if(left==null) {//左子树为空直接返回0
			return 0;
		}
		return left.height();//递归左子树的高度
	}
	
	public int rightHeight() {//返回右子树的高度
		if(right==null) {//右子树为空直接返回0
			return 0;
		}
		return right.height();//递归右子树的高度
	}
	
	//RR平衡旋转(左单旋转)
	private void leftRotate() {
		Node newNode =new Node(value);//创建一个新的结点newNode
		newNode.left=left;//将新结点的左子树设置为当前结点的左子树
		newNode.right=right.left;//将新结点的右子树设置为当前结点的右子树的左子树
		value=right.value;//将当前结点的值换为右子结点的值
		right=right.right;//将当前结点的右子树设置成右子树的右子树
		left=newNode;//将当前结点的左子树设置成新结点
	}
	
	//LL平衡旋转(右单旋转)
	private void rightRotate() {
		Node newNode =new Node(value);//创建一个新的结点newNode
		newNode.right=right;//将新结点的右子树设置为当前结点的右子树
		newNode.left=left.right;//将新结点的左子树设置为当前结点的左子树的右子树
		value=left.value;//将当前结点的值换为左子结点的值
		left=left.left;//将当前结点的左子树设置成左子树的左子树
		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);//否则递归向当前结点的右子树遍历
			}
		}
		
		if(rightHeight()-leftHeight()>1) {//当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 ,左旋转
			if(right!=null&&right.leftHeight()>right.rightHeight()) {//若它的右子树的左子树的高度大于它的右子树的右子树的高度
				right.rightRotate();//RL平衡旋转(先右后左双旋转)
				leftRotate();
			}else {
				leftRotate();//RR平衡旋转(左单旋转)
			}
			return;
		}
		
		if(leftHeight()-rightHeight()>1) {//当添加完一个结点后,如果 :(左子树的高度 - 右子树的高度) > 1, 右旋转
			if(left!=null&&left.rightHeight()>left.leftHeight()) {//若它的左子树的右子树高度大于它的左子树的左子树的高度
				left.leftRotate();//LR平衡旋转(先左后右双旋转)
				rightRotate();
			}else {
				rightRotate();//LL平衡旋转(右单旋转)
			}
		}
	}
	
	public void inOrder() {//中序遍历
		if(this.left!=null) {
			this.left.inOrder();
		}
		System.out.println(this);
		if(this.right!=null) {
			this.right.inOrder();
		}
	}
}

中序遍历获得一个递增数列,所以删除非叶子结点替换的结点一个为中序遍历后的第一个结点
二叉排序树: 左<中<右(可能有多个)

大根堆:跟结点大于左右子树的结点值

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
平衡二叉树是一种特殊的二叉,它的左右子的高度差不超过1。AVL是一种自平衡的二叉搜索,它的高度始终保持在O(log n)。 下面是C语言实现平衡二叉树(AVL)的代码: ``` #include <stdio.h> #include <stdlib.h> /* 定义平衡二叉树节点结构体 */ struct AVLNode { int data; // 存储的数据 int height; // 节点高度 struct AVLNode *leftChild; // 左子 struct AVLNode *rightChild; // 右子 }; /* 获取节点高度 */ int getHeight(struct AVLNode *node) { if (node == NULL) { return -1; } else { return node->height; } } /* 获取节点平衡因子 */ int getBalanceFactor(struct AVLNode *node) { if (node == NULL) { return 0; } else { return getHeight(node->leftChild) - getHeight(node->rightChild); } } /* 更新节点高度 */ void updateHeight(struct AVLNode *node) { node->height = 1 + (getHeight(node->leftChild) > getHeight(node->rightChild) ? getHeight(node->leftChild) : getHeight(node->rightChild)); } /* 右旋操作 */ struct AVLNode *rotateRight(struct AVLNode *node) { struct AVLNode *newRoot = node->leftChild; node->leftChild = newRoot->rightChild; newRoot->rightChild = node; updateHeight(node); updateHeight(newRoot); return newRoot; } /* 左旋操作 */ struct AVLNode *rotateLeft(struct AVLNode *node) { struct AVLNode *newRoot = node->rightChild; node->rightChild = newRoot->leftChild; newRoot->leftChild = node; updateHeight(node); updateHeight(newRoot); return newRoot; } /* 插入操作 */ struct AVLNode *insert(struct AVLNode *root, int data) { if (root == NULL) { root = (struct AVLNode *) malloc(sizeof(struct AVLNode)); root->data = data; root->height = 0; root->leftChild = NULL; root->rightChild = NULL; } else if (data < root->data) { root->leftChild = insert(root->leftChild, data); if (getHeight(root->leftChild) - getHeight(root->rightChild) == 2) { if (data < root->leftChild->data) { root = rotateRight(root); } else { root->leftChild = rotateLeft(root->leftChild); root = rotateRight(root); } } } else if (data > root->data) { root->rightChild = insert(root->rightChild, data); if (getHeight(root->rightChild) - getHeight(root->leftChild) == 2) { if (data > root->rightChild->data) { root = rotateLeft(root); } else { root->rightChild = rotateRight(root->rightChild); root = rotateLeft(root); } } } updateHeight(root); return root; } /* 中序遍历 */ void inOrderTraversal(struct AVLNode *root) { if (root != NULL) { inOrderTraversal(root->leftChild); printf("%d ", root->data); inOrderTraversal(root->rightChild); } } int main() { struct AVLNode *root = NULL; int data[] = {5, 2, 8, 1, 3, 6, 9}; int len = sizeof(data) / sizeof(data[0]); int i; for (i = 0; i < len; i++) { root = insert(root, data[i]); } inOrderTraversal(root); return 0; } ``` 以上代码实现了平衡二叉树的插入和中序遍历操作。在插入操作中,根据插入节点的值和当前节点的值的大小关系,不断递归向左或向右子进行插入操作,并在递归返回时更新节点高度和进行平衡操作。在平衡操作中,根据节点的平衡因子进行旋转操作,使重新平衡。在中序遍历操作中,按照左子、根节点、右子的顺序遍历中的节点,输出节点的值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何为xl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值