栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

【数据结构】树(六)—— 二叉平衡树(C语言版)
    • 前言
      • 平衡二叉树的定义
      • 平衡二叉树的插入
        • 如何调整不平衡子树
      • 练习
      • 查找效率分析
      • 完整代码

前言

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

平衡二叉树的定义

平衡二叉树可定义为或者是一棵空树,或者是具有下列性质的二叉树:其左子树和右子树均为平衡二叉树,且左子树和右子树的高度差的绝对值不超过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;
平衡二叉树的插入

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

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

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

如何调整不平衡子树

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,经过调整后高度恢复。。

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

练习 查找效率分析

完整代码
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
			 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();
		}
	}
}

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

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/997254.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号