数据结构:AVL?撕就完了

本文详细介绍了AVL树的数据结构,包括Node结点定义,通过BST实现的insert和remove方法,以及关键的calBalance和rotate函数。着重展示了如何通过迭代方式处理插入和删除操作,以及如何通过旋转保持树的平衡特性。
摘要由CSDN通过智能技术生成

一、Node结点定义

可以自行设计
自己在这里仅增加了一项——平衡因子的记录,用于旋转时判断类型

public class Node {
	int val;
	int balance; // 左子树高 - 右子树高
	Node left;
	Node right;
	
	public Node(int val) {
		this.val = val;
	}
	
	public Node(int val, Node left, Node right) {
		this.val = val;
		this.left = left;
		this.right = right;
	}
}

二、逐步实现AVL

先定义一个类,定义出root结点,
此外,还定义了个保护结点,由于AVL旋转后根结点可能改变,root结点为其左儿子
以及一个HashSet用以O(1)判断防重,空间换时间

public class MyAVL {
	Node root;
	private Node protect; // 由于AVL需要旋转,则设置一个保护结点
	private static final int protectVal = 0; 
	private HashSet<Integer> set;

	public MyAVL(int rootVal) {
		this.root = new Node(rootVal);
		this.protect = new Node(protectVal, this.root, null);
		this.set = new HashSet<>();
		this.set.add(rootVal);
	}
	
	public MyAVL() {
		this.protect = new Node(protectVal);
		this.set = new HashSet<>();
	}
}

1、BST

1)insert(int insertVal)方法

可以先练习这道算法题:LeetCode 701 二叉搜索树中的插入操作

  • HashSet防重,则不会出现相同的val
  • insertVal < curNode.val则往左子树走
  • insertVal > curNode.val则往右子树走

递归实现:代码简洁,可读性高,易于维护
迭代实现:空间发杂度较低

这里自己使用了迭代实现,并且区分了内部外部方法

public boolean insert(int insertVal) {
	if (this.set.contains(insertVal))
		return false;
	return insert(this.protect, this.root, insertVal, true);
}

// @param fa 此次插入结点的父结点,由于是迭代实现,需要记录父结点来实现插入
// @param cur 用于找到需要插入的位置,当cur为null时则找到了
// @param insertLeft 此次插入结点是其父结点的哪个儿子?初始假设为左儿子
private boolean insert(Node fa, Node cur, int insertVal, boolean insertLeft) {
	// 找到插入位置
	while (cur != null) {
		fa = cur;
		if (cur.val < insertVal) {
			cur = cur.right;
			insertLeft = false;
		} else {
			cur = cur.left;
			insertLeft = true;
		}
	}
	// 插入操作
	if (insertLeft) fa.left = new Node(insertVal);
	else fa.right = new Node(insertVal);
	// 加入set
	this.set.add(insertVal);
	// 若初始化没有定义root
	if (this.root == null)
		this.root = this.protect.left;
}

2)remove(int removeVal)方法

可以先练习这道算法题:LeetCode 450 删除二叉搜索树中的结点

  • HashSet判断removeVal是否为有效的值
  • removeVal < curNode.val则往左子树走
  • removeVal > curNode.val则往右子树走
  • removeVal == curNode.val则进行删除:
  • 1、欲删除结点左右子树均空:直接删除
  • 2、欲删除结点左右子树有一个为空:删除并以其非空子树取代
  • 3、欲删除结点左右子树均非空:将当前结点替换为其前驱后驱,并删除其前驱后驱

递归实现:代码简洁,可读性高,易于维护
迭代实现:空间发杂度较低

这里自己仍使用迭代实现,以后驱代替欲删除结点,但仅是值的替换

public boolean remove(int removeVal) {
	if (!set.contains(removeVal))
		return false;
	return remove(this.protect, this.root, removeVal, true); 
}
	
// 其形参解释与insert()方法相同
// @param deleteLeft 初始时假设删除的是左节点
private boolean remove(Node fa, Node cur, int removeVal, boolean deleteLeft) {
	// 找到删除位置
	while (cur.val != removeVal) {
		fa = cur;
		if (cur.val < removeVal) {
			cur = cur.right;
			deleteLeft = false;
		} else {
			cur = cur.left;
			deleteLeft = true;
		}
	}
	// 删除操作
	if (cur.left == null && cur.right == null) { // 都null
		if (deleteLeft) fa.left = null;
		else fa.right = null;
	} else if (cur.left == null || cur.right == null) { // 一边null
		if (deleteLeft) fa.left = cur.left == null ? cur.right : cur.left;
		else fa.right = cur.left == null ? cur.right : cur.left;
	} else { // 都不null,找后驱,此处是修改结点的值
		Node nextDelete = cur.right;
		fa = cur; // 此时fa的定义修改为:nextDelete的父结点
		while (nextDelete.left != null) {
			fa = nextDelete;
			nextDelete = nextDelete.left;
		}
		if (fa.right == nextDelete) // 特殊情况:欲删除结点的后驱就是cur的右儿子
			fa.right = nextDelete.right;
		else // 一般情况:欲删除结点的后驱是 欲删除结点右儿子 的最左儿子
			fa.left = nextDelete.right;
		cur.val = nextDelete.val;
	}
	// 从set移除
	this.set.remove(removeVal);
}

2、计算平衡因子函数calBalance()

可以先练习这道算法题:LeetCode 110 平衡二叉树

1、对于每一次计算而言,只需要旋转第一个不平衡结点即可,所以设置了全局变量rotateFinished来保证只需旋转一次,并且可用于提前返回
2、对于左右子树高度差大于1的结点则要调取rotate()方法
3、对于形参fa_cur_isleftChild由于rotate()可能导致当前结点的结构变化,所以需要存储一下,当前结点cur与父结点fa的关系
4、另外,由于旋转完毕之后root可能发生了改变,所以要重新获取root

// 做好旋转前的准别工作,并调用计算平衡因子的方法
private void calBalanceInit() {
	this.rotateFinished = false;
	calBalance(this.protect, this.root, true);
}
	
// @param fa_cur_isleftChild 反映了cur是否是fa的左儿子
private int calBalance(Node fa, Node cur, boolean fa_cur_isleftChild) {
	if (cur == null)
		return 0;
	int l = calBalance(cur, cur.left, true);
	int r = calBalance(cur, cur.right, false);
	if (Math.abs(l - r) > 1) {
		if (fa_cur_isleftChild) fa.left = rotate();
		else fa.right = rotate();
		// 由于旋转完毕之后root可能发生了改变,所以要重新赋予root
		this.root = this.protect.left;
	}
	cur.balance = l - r;
	return Math.max(l, r) + 1;
}

3、旋转方法rotate()

1)LL,RR的单旋

LL
RR

LL:第一个L不平衡结点子树高度大于子树高度,第二个L指其左子结点子树高度大于子树高度
RR:第一个R不平衡结点子树高度大于子树高度,第二个R指其右子节点子树高度大于子树高度

在这里插入图片描述
RR
于是rotate()函数便设计了四个形参

// @param cur_child_isLeft 不平衡结点是否是左子树不平衡,或者是child结点是否是cur的左儿子
// @param child_isLeft child结点是否是左子树高度大于右子树
private Node rotate(Node cur, Node child, 
									boolean cur_child_isLeft, boolean child_isLeft) {
	if (cur_child_isLeft && child_isLeft) { // LL
		cur.left = child.right;
		child.right = cur;
	} else if (!cur_child_isLeft && !child_isLeft) { // RR
		cur.right = child.left;
		child.left = cur;
	}
	return child;
}

3)LR,RL的双旋

在这里插入图片描述

RL

LR:第一个L不平衡结点子树高度大于子树高度,第二个R指其左子结点子树高度大于子树高度
RR:第一个R不平衡结点子树高度大于子树高度,第二个R指其右子节点子树高度大于子树高度

LR
LR可以视作grandchildchild之间的一次RR旋转 + grandchildcur之间的一次LL旋转
RL
RL可以视作grandchildchild之间的一次LL旋转 + grandchildcur之间的一次RR旋转
所以直接递归实现即可

// @param cur_child_isLeft 不平衡结点是否是左子树不平衡,或者是child结点是否是cur的左儿子
// @param child_isLeft child结点是否是左子树高度大于右子树
private Node rotate(Node cur, Node child, 
									boolean cur_child_isLeft, boolean child_isLeft) {
	if (cur_child_isLeft && child_isLeft) { // LL
		cur.left = child.right;
		child.right = cur;
	} else if (!cur_child_isLeft && !child_isLeft) { // RR
		cur.right = child.left;
		child.left = cur;
	} else if (cur_child_isLeft && !child_isLeft) { // LR
		cur.left = rotate(child, child.right, false, false); // RR
		return rotate(cur, cur.left, true, true); // LL
	} else { // RL
		cur.right = rotate(child, child.left, true, true); // LL
		return rotate(cur, cur.right, false, false); // RR
	}
	return child;
}

3) 更新calBalance()

// rotateFinished 由于只需要第一个不平衡的结点旋转,用一个bool来控制是否已经旋转完毕
// @param fa_cur_isleftChild 反映了cur是否是fa的左儿子
private int calBalance(Node fa, Node cur, boolean fa_cur_isleftChild) {
	if (cur == null || this.rotateFinished) // 增加强行终止
		return 0;
	int l = calBalance(cur, cur.left, true);
	int r = calBalance(cur, cur.right, false);
	if (!this.rotateFinished && Math.abs(l - r) > 1) {
		this.rotateFinished = true;
		Node child = l > r ? cur.left : cur.right;
		Node finished = rotate(cur, child, l > r, child.balance > 0);
		if (fa_cur_isleftChild) fa.left = finished;
		else fa.right = finished;
		// 由于旋转完毕之后root可能发生了改变,所以要重新赋予root
		this.root = this.protect.left;
	}
	cur.balance = l - r;
	return Math.max(l, r) + 1;
}

另外在insert()函数和delete()函数结束时调用计算平衡因子的方法

三、AVL源码

public class MyAVL {
	Node root;
	private Node protect; // 由于AVL需要旋转,则设置一个保护结点
	private static final int protectVal = 0; 
	private HashSet<Integer> set;
	private boolean rotateFinished;
	
	public MyAVL(int rootVal) {
		this.root = new Node(rootVal);
		this.protect = new Node(protectVal, this.root, null);
		this.set = new HashSet<>();
		this.set.add(rootVal);
	}
	
	public MyAVL() {
		this.protect = new Node(protectVal);
		this.set = new HashSet<>();
	}
	
	public boolean insert(int insertVal) {
		if (this.set.contains(insertVal))
			return false;
		return insert(this.protect, this.root, insertVal, true);
	}
	
	// @param insertLeft 假设初始时插入的是左结点
	private boolean insert(Node fa, Node cur, int insertVal, boolean insertLeft) {
		// 找到插入位置
		while (cur != null) {
			fa = cur;
			if (cur.val < insertVal) {
				cur = cur.right;
				insertLeft = false;
			} else {
				cur = cur.left;
				insertLeft = true;
			}
		}
		// 插入操作
		if (insertLeft) fa.left = new Node(insertVal);
		else fa.right = new Node(insertVal);
		// 加入set
		this.set.add(insertVal);
		// 若初始化没有加入root
		if (this.root == null)
			this.root = this.protect.left;
		
		// ----此时插入已经完成----
		// 计算平衡因子并旋转
		calBalanceInit();
		return true;
	}
	
	public boolean remove(int removeVal) {
		if (!set.contains(removeVal))
			return false;
		return remove(this.protect, this.root, removeVal, true); 
	}
	
	// @param deleteLeft 初始时假设删除的是左节点
	private boolean remove(Node fa, Node cur, int removeVal, boolean deleteLeft) {
		// 找到删除位置
		while (cur.val != removeVal) {
			fa = cur;
			if (cur.val < removeVal) {
				cur = cur.right;
				deleteLeft = false;
			} else {
				cur = cur.left;
				deleteLeft = true;
			}
		}
		// 删除操作
		if (cur.left == null && cur.right == null) { // 都null
			if (deleteLeft) fa.left = null;
			else fa.right = null;
		} else if (cur.left == null || cur.right == null) { // 一边null
			if (deleteLeft) fa.left = cur.left == null ? cur.right : cur.left;
			else fa.right = cur.left == null ? cur.right : cur.left;
		} else { // 都不null,找后驱,此处是修改结点的值
			Node nextDelete = cur.right;
			fa = cur; // 此时fa为nextDelete的父结点
			while (nextDelete.left != null) {
				fa = nextDelete;
				nextDelete = nextDelete.left;
			}
			if (fa.right == nextDelete) // 也就是cur的后驱就是cur的右儿子
				fa.right = nextDelete.right;
			else // cur的后驱是cur右儿子的最左儿子
				fa.left = nextDelete.right;
			cur.val = nextDelete.val;
		}
		// 从set移除
		this.set.remove(removeVal);
		
		// ----此时删除已经完成----
		// 计算平衡因子并旋转
		calBalanceInit();
		return true;
	}
	
	// 做好旋转前的准别工作,并调用计算平衡因子的方法
	private void calBalanceInit() {
		this.rotateFinished = false;
		calBalance(this.protect, this.root, true);
	}
	
	// rotateFinished 由于只需要第一个不平衡的结点旋转,用一个bool来控制是否已经旋转完毕
	private int calBalance(Node fa, Node cur, boolean fa_cur_isleftChild) {
		if (cur == null || this.rotateFinished) // 增加强行终止
			return 0;
		int l = calBalance(cur, cur.left, true);
		int r = calBalance(cur, cur.right, false);
		if (!this.rotateFinished && Math.abs(l - r) > 1) {
			this.rotateFinished = true;
			Node child = l > r ? cur.left : cur.right;
			Node finished = rotate(cur, child, l > r, child.balance > 0);
			if (fa_cur_isleftChild) fa.left = finished;
			else fa.right = finished;
			// 由于旋转完毕之后root可能发生了改变,所以要重新赋予root
			this.root = this.protect.left;
		}
		cur.balance = l - r;
		return Math.max(l, r) + 1;
	}
	
	private Node rotate(Node cur, Node child, 
									boolean cur_child_isLeft, boolean child_isLeft) {
		if (cur_child_isLeft && child_isLeft) { // LL
			cur.left = child.right;
			child.right = cur;
		} else if (!cur_child_isLeft && !child_isLeft) { // RR
			cur.right = child.left;
			child.left = cur;
		} else if (cur_child_isLeft && !child_isLeft) { // LR
			cur.left = rotate(child, child.right, false, false);
			return rotate(cur, cur.left, true, true);
		} else { // RL
			cur.right = rotate(child, child.left, true, true);
			return rotate(cur, cur.right, false, false);
		}
		return child;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值