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

平衡二叉树ALVBinaryTreeList

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

平衡二叉树ALVBinaryTreeList

简介:本文介绍平衡二叉树基本概念,并通过继承AbstractBinaryTree抽象类,java实现ALVBinaryTreeList,主要操作算法有插入和删除。

一、平衡二叉树基本概念 1.平衡二叉树是什么?

平衡二叉树时一种结构平衡的二叉树,即叶节点高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

2.为什么会出现平衡二叉树?

1)二叉树插入时存在最坏情况,导致查找的时间复杂度为O(n)。例如:顺序插入1,2,3,...,时,二叉树严重失衡,退化为普通链表,查找时间复杂度为O(n)。

2)平衡二叉树通过调整二叉树的结构使二叉树平衡,最坏查找的时间复杂度变为

3.平衡二叉树有哪些性质?

1)本身是一棵二叉搜索树。

2)带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。


4.平衡二叉树节点如何定义?

除了需要像二叉树一样存在父节点、左孩子、右孩子、节点值之外,还需要可以求节点平衡因子的树高度。

二、平衡二叉树ALVBinaryTreeList实现和布局

1.实现思路

和上一篇BinaryTreeList一样,参照java集合ArrayList实现一个ALVBinaryTreeList,并增加平衡二叉树特有算法。

2.ALVBinaryTreeList如何布局?

由于平衡二叉树是二叉树的一种,其基本的遍历、深度等操作已经在AbstractTreeList中实现,因此ALVBinaryTreeList只需要继承AbstractTreeList,并实现节点插入、删除算法即可。

三、基于平衡二叉树的ALVBinaryTreeList实现 1.节点定义

平衡二叉树属性比普通二叉树多一个高度,用于计算节点平衡度,因此在二叉树节点增加一个symbol作为高度,并增加一个构造方法。

    static class Node {
        int symbol;//此处表示节点的高度
        E value;
        Node parent;
        Node leftChild;
        Node rightChild;
        Node(Nodeparent,Node left,Node right,E value,int symbol){
            this.parent = parent;
            this.leftChild = left;
            this.rightChild = right;
            this.value = value;
            this.symbol = symbol;
        }
        Node(Nodeparent,Node left,Node right,E value){
            this.parent = parent;
            this.leftChild = left;
            this.rightChild = right;
            this.value = value;
        }
        Node(Node left,Node right,E value){
            this(null,left,right,value);
        }
        Node(){}
    }
2.ALVBinaryTreeList结构
public class ALVBinaryTreeList extends AbstractTreeList{
//各种实现代码在此

}
3.平衡二叉树插入后调整思路

思想:平衡二叉树插入操作和普通二叉树一样,都需要在已有树节点某个叶子节点上插入,但是多了一个调整平衡的操作。

待插入节点z,在以r为根节点的子树上插入z,分为r的左孩子左子树插入、r的左孩子右子树插入、r的右孩子右子树插入、r的右孩子左子树插入。

1)r的左孩子左子树插入

如下图所示,需要以r的左孩子为轴进行右旋。

2)r的左孩子的右子树插入

如下图所示,需要先以r的左孩子为轴左旋,然后以旋转后的r的新的左孩子为轴,右旋。

 3)r的右孩子右子树插入

需要以r的右孩子为轴左旋。

4)r的右孩子左子树插入

需要以r的右孩子为轴右旋,然后以新的r的右孩子为轴左旋 。

4.平衡二叉树插入代码实现

1)左旋、右旋

	
	void lRotate(Node x) {
		Nodey = x.rightChild;
		x.rightChild = y.leftChild;
		
		if(y.leftChild!=null) y.leftChild.parent = x;
		y.parent = x.parent;
		if(x.parent == null) root = y;
		else if(x == x.parent.leftChild) x.parent.leftChild = y;
		else x.parent.rightChild = y;
		y.leftChild = x;
		x.parent = y;
		
		x.symbol = Math.max(height(x.leftChild), height(x.rightChild))+1;
		y.symbol = Math.max(height(y.leftChild), height(y.rightChild))+1;
	}

    
	void rRotate(Node x) {
		Node y = x.leftChild;
		x.leftChild = y.rightChild;
		
		if(y.rightChild!=null) y.rightChild.parent = x;
		y.parent = x.parent;
		if(x.parent == null) root = y;
		else if(x == x.parent.leftChild) x.parent.leftChild = y;
		else x.parent.rightChild = y;
		y.rightChild = x;
		x.parent = y;
		
		x.symbol = Math.max(height(x.leftChild), height(x.rightChild))+1;
		y.symbol = Math.max(height(y.leftChild), height(y.rightChild))+1;
	}
2)获取子树高度、平衡因子
    int height(Node node){
		return (node == null)?0:node.symbol;
	}
	int getBalanceFactory(Node node) {
		return (node == null) ? 0:(height(node.leftChild) - height(node.rightChild));
	}
3)插入节点
    @Override
	public boolean add(E e) {
		if(e == null) throw new NullPointerException("插入的元素为空");
		addVal(e);
		return true;
	}

    void addVal(E val){
		Node r = root,p=null;
		while (r!=null) {
			p = r;
			if(compare(val, r.value) < 0) r=r.leftChild;
			else if(compare(val, r.value) > 0) r=r.rightChild;
			else return ;
		}
		Node e = new Node(p,null,null,val,1);
		if(p == null) root = e;
		else if(compare(val, p.value) < 0) p.leftChild = e;
		else if(compare(val, p.value) > 0) p.rightChild = e;
		else if(compare(val, p.value) == 0) return;

		adjust(e);//从添加的元素e开始向上调整二叉树,直到根节点
		size++;
	}
4)调整平衡
    void adjust(Node p) {
		while(p != null) {
			int pHeight = Math.max(height(p.leftChild), height(p.rightChild)+1);
			p.symbol = pHeight;
			int pBf = getBalanceFactory(p);
			if(pBf == 2) {//左高
				if(getBalanceFactory(p.leftChild) == 1) {//左孩子左子树插入
					rRotate(p);
				}else if(getBalanceFactory(p.leftChild) == -1) {//左孩子右子树插入
					lRotate(p.leftChild);
					rRotate(p);
				}
			}else if(pBf == -2) {
				if(getBalanceFactory(p.rightChild) == -1) {
					lRotate(p);
				}else if(getBalanceFactory(p.rightChild) == 1) {
					rRotate(p.rightChild);
					lRotate(p);
				}
			}
			if(p.parent == null) {
				root = p;
				return;
			}
			p = p.parent;
		}
	}
5.平衡二叉树删除代码实现

寻找到待删除节点z,然后:

1)z是叶子节点:直接删除。然后从叶子结点向上校验其各父节点是否失衡,失衡调整。
2)z是单孩子节点:用其子树代替。然后从删除节点父亲节点向上校验其父亲节点是否失衡,失衡调整。
3) z是双孩子节点:找其右子树最大节y点替换当前节点。如果y是z的右孩子,直接将y替换z。否则,需要先将y.right替换y,然后y替换z。最后从该节点父亲节点向上调整平衡。

    //用newNode为根的树替代oldNode
    void transplant(NodeoldNode,NodenewNode) {
		if (oldNode.parent == null) root = newNode;
		else if(oldNode == oldNode.parent.leftChild) oldNode.parent.leftChild = newNode;
		else oldNode.parent.rightChild = newNode;
		if(newNode != null) newNode.parent = oldNode.parent;
	}
	
	@Override
	public boolean remove(Object o) {
		if(o == null) throw new NullPointerException();
		Noder;
		if((r = root) == null) return false;
		while(r!=null) {
			if(compare((E)o,r.value) < 0) r = r.leftChild;
			else if (compare((E)o,r.value) > 0) r = r.rightChild;
			else {
				//找到了节点r对应值为o
				if(r.leftChild == null) transplant(r, r.rightChild);//没有左孩子
				else if(r.rightChild == null) { //没有右孩子
					transplant(r, r.leftChild);
					adjust(r.parent);
				}else {//有2个孩子
					Noder2 = r.rightChild;
					while(r2.leftChild!=null) r2 = r2.leftChild;
					if(r2.parent != r) {
						transplant(r2, r2.rightChild);
						r2.rightChild = r.rightChild;
						r2.rightChild.parent = r2;
					}
					transplant(r, r2);
					r2.leftChild = r.leftChild;
					r2.leftChild.parent = r2;
					adjust(r2);
				}
				return true;
			}
		}
		return false;
	}
6.ALVBinaryTreeList节点清空实现

节点z清空需要节点z的左右子树都为空时才可,因此需要借助二叉树后续遍历实现节点清空。 

 首先Node类中新增节点清空方法

//在Node类中新增clear方法用于清空节点
        void clear() {
			this.parent = null;
			this.leftChild = null;
			this.rightChild = null;
			this.value = null;
		}

其次实现ALVBinaryTreeList清空方法

	@Override
	public void clear() {
		//clear需要让节点的左右子树全为空,因此需要后续遍历
		Nodecur=root,pre=null;
		linkedList> stack = new linkedList<>();
		while(cur!=null || !stack.isEmpty()) {
			while(cur!=null) {
				stack.push(cur);
				cur = cur.leftChild;
			}
			cur = stack.pop();
			if(cur.rightChild==null || cur.rightChild==pre) {
				pre = cur;
				cur.clear();
				cur = null;
				size--;
			}else {
				stack.push(cur);
				cur = cur.rightChild;
			}
		}
		root = null;
	}

五、ALVBinaryTreeList测试

测试思路:生成随机数添加到ALVBinaryTreeList中,然后同时添加到set中,中序遍历获取ALVBinaryTreeList元素,并且将set中的元素排序,最后比较排序好的setList和中序遍历list元素是否一致。

节点删除方法测试是同样的思路。下面给出节点删除测试代码:

static void binaryTest() {
		AbstractTreeList binaryTreeList = new ALVBinaryTreeList();
		Random random = new Random();
		Set set1= new HashSet();
		for(int i=0;i<10000;i++) {
			int e = random.nextInt(100000);
			set1.add(e);
			binaryTreeList.add(Integer.valueOf(e));
		}
		Object[] arr1 = set1.toArray();
		for(Object el : arr1) {
			binaryTreeList.remove(el);
			set1.remove(el);
			
			Object[] binaryarr = binaryTreeList.toArray();
			Object[] setarr = set1.toArray();
			Arrays.sort(setarr);
			for (int j=0;j 

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

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

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