数据结构与算法(十六)——二叉排序树、平衡二叉树、多叉树

二叉排序树(BST)

二叉排序树:BST(Binary Sort(Search) Tree),对于BST中任何一个非叶子结点,要求左子节点的值比当前结点的值小,右子节点的值比当前结点的值大。对于相同的值,可以将该结点放在左子节点或者右子节点

代码实现

  1. 添加结点:根据结点的值,将值小的结点放在左边,值大的结点放在右边
  2. 遍历结点:与一般树的前中后序遍历无异
  3. 删除结点:
  1. 删除叶子结点:找到需要删除的结点的父结点,然后将叶子结点置空
  2. 删除只有一个子结点的结点:找到需要删除的结点的父结点,将父结点的子结点位置改成子的子,即用子结点替换掉需要删除的节点
  3. 删除有两个子结点的结点:找到需要删除的结点的父结点,找到右子树的最左(小)结点、或左子树最右(大)结点替代需要删除的结点
/***
 * 二叉排序树
 * 
 * @author laowa
 *
 */
class BinarySortTree {

	Node root;

	/**
	 * 添加结点
	 * 
	 * @param node
	 */
	public void add(Node node) {
		if (root == null) {
			root = node;
			return;
		}
		root.add(node);
	}

	/**
	 * 删除结点
	 * 
	 * @param no
	 *            需要删除的结点的no
	 */
	public void delete(int no) {
		// 判空
		if (this.root == null) {
			System.out.println("树为空");
			return;
		}
		// 找到目标节点和目标节点的父结点
		Node target = this.search(no);
		// 如果没有找到目标结点直接退出方法
		if (target == null) {
			return;
		}
		Node parent = this.root.searchParent(no);

		// 如果该树只有一颗结点,则直接根结点置空
		if (root.left == null && root.right == null) {
			this.root = null;
			return;
		}

		if (target.left == null && target.right == null) {
			// 如果左右子结点均为空,表示当前结点为叶子结点
			// 找到目标结点在父结点的左边还是右边,然后置空
			if (parent.left == target) {
				parent.left = null;
			} else {
				parent.right = null;
			}
			return;
		}

		// 如果左子结点为空,说明只有一棵右子树
		if (target.left == null) {
			if (parent == null) {
				// 如果父结点为空,即删除的结点是根结点
				this.root = target.right;
			} else {
				// 找到目标结点在父结点的左边还是右边,然后指向目标结点的右边
				if (parent.left == target) {
					parent.left = target.right;
				} else {
					parent.right = target.right;
				}
			}

		}

		// 如果右子结点为空,说明只有一棵左子树
		if (target.right == null) {
			if (parent == null) {
				// 如果父结点为空,即删除的结点是根节点
				this.root = target.left;
			} else {
				// 找到目标结点在父结点的左边还是右边,然后指向目标结点的左边
				if (parent.left == target) {
					parent.left = target.left;
				} else {
					parent.right = target.left;
				}
			}

		}

		if (target.left != null && target.right != null) {
			// 如果目标结点的左右子结点均不为空
			Node temp = target.right;
			// 向左循环查找一直找到最左边的结点,此时temp保存了这个结点
			while (temp.left != null) {
				temp = temp.left;
			}
			// 删除该结点
			delete(temp.no);
			// 将目标结点的左右子结点赋值给该结点
			temp.left = target.left;
			temp.right = target.right;
			// 如果parent为空表示删除的结点是根节点,没有父结点,否则找到目标结点在父结点的左边还是右边,并且设置为temp结点
			if (parent != null) {
				if (parent.left == target) {
					parent.left = temp;
				} else {
					parent.right = temp;
				}
			} else {
				this.root = temp;
			}

		}

	}

	/**
	 * 查找目标结点
	 * 
	 * @param no
	 *            目标结点的编号
	 * @return 结点
	 */
	public Node search(int no) {
		if (this.root == null) {
			return null;
		}
		return this.root.search(no);
	}

	/**
	 * 中序遍历
	 */
	public void infixOrder() {
		if (root == null) {
			System.out.println("树为空");
			return;
		}
		root.infixOrder();
	}
}

/***
 * 二叉排序树结点
 * 
 * @author laowa
 *
 */
class Node {
	int no;
	Node left;
	Node right;

	public Node(int no) {
		this.no = no;
	}

	@Override
	public String toString() {
		return "[Node value=" + no + "]";
	}

	/**
	 * 查找当前需要删除的结点
	 * 
	 * @param no
	 *            结点编号
	 */
	public Node search(int no) {
		if (this.no == no) {
			// 如果当前结点为需要查找的结点,返回
			return this;
		}
		if (this.no > no) {
			// 如果目标编号小于当前结点,表示目标结点在左边
			if (this.left != null) {
				return this.left.search(no);
			}
			// 没找到
			return null;
		}
		// 如果目标编号大于当前结点,表示目标结点在右边
		if (this.right != null) {
			return this.right.search(no);
		}
		// 没找到
		return null;
	}

	/**
	 * 查找需要删除的结点的父结点
	 * 
	 * @param no
	 * @return
	 */
	public Node searchParent(int no) {
		// 如果作右子结点中存在目标结点,则放回当前结点
		if ((this.left != null && this.left.no == no) || (this.right != null && this.right.no == no)) {
			return this;
		}
		// 如果当前结点大于目标结点,向左递归查找
		if (this.no > no && this.left != null) {
			return this.left.searchParent(no);
		}
		// 如果当前结点小于目标结点,向右递归查找
		if (this.no < no && this.right != null) {
			return this.right.searchParent(no);
		}
		// 没找到返回null
		return null;
	}

	/**
	 * 添加结点
	 * 
	 * @param node
	 *            待添加的结点
	 */
	public void add(Node node) {
		// 判空,添加的结点为空则不添加
		if (node == null) {
			return;
		}
		if (node.no < this.no) {
			// 如果待添加的结点小于当前节点,想左子树添加
			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);
			}
		}
	}

	/**
	 * 中序遍历
	 */
	public void infixOrder() {
		if (this.left != null) {
			this.left.infixOrder();
		}
		System.out.println(this);
		if (this.right != null) {
			this.right.infixOrder();
		}
	}
}

平衡二叉树(AVL)

问题导入

使用数列{1,2,3,4,5,6}创建一棵二叉排序树,将会得到这样一棵树:
在这里插入图片描述
这棵二叉排序树存在这些问题:

  1. 左子树全部为空,从形式上看,更像一个单链表,失去了树结构的意义
  2. 插入速度没有影响
  3. 查询速度明显降低,不能发挥BST的优势

基本介绍

  1. 平衡二叉树也叫平衡二叉搜索树,又称为AVL树,它可以保证较高的查询效率
  2. 它具有以下特点:它是一颗空树或它的左右两个子树的高度查的绝对值不超过1,并且左右两棵子树都是一颗平衡二叉树,平衡二叉树的常用实现有:红黑树AVL替罪羊树Treap伸展树等

代码实现

  1. 当右边子树高度高于左边子树高度+1时,进行左旋转
    在这里插入图片描述

  2. 当左边子树高度高于右边子树高度+1时,进行右旋转
    在这里插入图片描述

  3. 当右边子树高度高于左边子树高度+1,并且右子树的左子树高于右边子树+1,左旋转后右子树高的一部分被转到了左子树,这样仍然不平衡,此时需要双旋转,即先将右子节点进行右旋转,让右子树平衡,再让当前结点进行左旋转,对于左子树亦然

/**
 * AVL树
 * 
 * @author laowa
 *
 */
class AvlTree {

	Node root;

	/**
	 * 添加结点
	 * 
	 * @param node
	 */
	public void add(Node node) {
		if (root == null) {
			root = node;
			return;
		}
		root.add(node);
	}

	/**
	 * 获取树的高度
	 * @return 树的高度
	 */
	public int height() {
		return this.root.height();
	}
	
	/**
	 * 删除结点
	 * 
	 * @param no
	 *            需要删除的结点的no
	 */
	public void delete(int no) {
		// 判空
		if (this.root == null) {
			System.out.println("树为空");
			return;
		}
		// 找到目标节点和目标节点的父结点
		Node target = this.search(no);
		// 如果没有找到目标结点直接退出方法
		if (target == null) {
			return;
		}
		Node parent = this.root.searchParent(no);

		// 如果该树只有一颗结点,则直接根结点置空
		if (root.left == null && root.right == null) {
			this.root = null;
			return;
		}

		if (target.left == null && target.right == null) {
			// 如果左右子结点均为空,表示当前结点为叶子结点
			// 找到目标结点在父结点的左边还是右边,然后置空
			if (parent.left == target) {
				parent.left = null;
			} else {
				parent.right = null;
			}
			return;
		}

		// 如果左子结点为空,说明只有一棵右子树
		if (target.left == null) {
			if (parent == null) {
				// 如果父结点为空,即删除的结点是根结点
				this.root = target.right;
			} else {
				// 找到目标结点在父结点的左边还是右边,然后指向目标结点的右边
				if (parent.left == target) {
					parent.left = target.right;
				} else {
					parent.right = target.right;
				}
			}

		}

		// 如果右子结点为空,说明只有一棵左子树
		if (target.right == null) {
			if (parent == null) {
				// 如果父结点为空,即删除的结点是根节点
				this.root = target.left;
			} else {
				// 找到目标结点在父结点的左边还是右边,然后指向目标结点的左边
				if (parent.left == target) {
					parent.left = target.left;
				} else {
					parent.right = target.left;
				}
			}

		}

		if (target.left != null && target.right != null) {
			// 如果目标结点的左右子结点均不为空
			Node temp = target.right;
			// 向左循环查找一直找到最左边的结点,此时temp保存了这个结点
			while (temp.left != null) {
				temp = temp.left;
			}
			// 删除该结点
			delete(temp.no);
			// 将目标结点的左右子结点赋值给该结点
			temp.left = target.left;
			temp.right = target.right;
			// 如果parent为空表示删除的结点是根节点,没有父结点,否则找到目标结点在父结点的左边还是右边,并且设置为temp结点
			if (parent != null) {
				if (parent.left == target) {
					parent.left = temp;
				} else {
					parent.right = temp;
				}
			} else {
				this.root = temp;
			}

		}

	}

	/**
	 * 查找目标结点
	 * 
	 * @param no
	 *            目标结点的编号
	 * @return 结点
	 */
	public Node search(int no) {
		if (this.root == null) {
			return null;
		}
		return this.root.search(no);
	}

	/**
	 * 中序遍历
	 */
	public void infixOrder() {
		if (root == null) {
			System.out.println("树为空");
			return;
		}
		root.infixOrder();
	}
}

/***
 * AVL树结点
 * 
 * @author laowa
 *
 */
class Node {
	int no;
	Node left;
	Node right;

	public Node(int no) {
		this.no = no;
	}

	@Override
	public String toString() {
		return "[Node value=" + no + "]";
	}

	/**
	 * 获取以当前结点为根节点的树的高度
	 * 
	 * @return 一个整数,为以当前结点为根节点的树的高度
	 */
	public int height() {
		// 递归向左右获取高度,去左右高度的最大值,每次获取到会加一,即每一层高度加一
		return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;
	}

	/**
	 * 获取左子树的高度
	 * 
	 * @return 一个整数,左子树的高度
	 */
	public int leftHeight() {
		return this.left == null ? 0 : this.left.height();
	}

	/**
	 * 获取右子树的高度
	 * 
	 * @return 一个整数,右子树的高度
	 */
	public int rightHeight() {
		return this.right == null ? 0 : this.right.height();
	}
	
	/**
	 * 左旋转
	 */
	private void leftRotate() {
		//以当前结点的值创建一个新的结点,这个新节点后面会被作为左子节点
		Node newNode = new Node(this.no);
		//新结点的左子节点为当前结点的左子节点
		newNode.left = this.left;
		//新节点的右子结点为当前结点的右子节点的左子节点
		newNode.right = this.right.left;
		
		//当前节点的值改为右子节点的值
		this.no = this.right.no;
		//右子节点指向右子结点的右子节点,丢弃右子节点
		this.right = this.right.right;
		//左子节点指向新节点,丢弃左子节点
		this.left  = newNode;
	}
	
	/**
	 * 右旋转
	 */
	private void rightRotate() {
		//以当前结点的值创建一个新的结点,这个结点后面会被作为右子节点
		Node newNode = new Node(this.no);
		//新结点的左子节点指向当前结点的左子结点的右子节点
		newNode.left = this.left.right;
		//新结点的右子节点指向当前结点的右子节点
		newNode.right = this.right;
		
		//将当前节点的值改为左子节点的值,即左子节点上移
		this.no = this.left.no;
		//当前结点左子节点指向左子节点的左子节点,丢弃原来的左子节点
		this.left = this.left.left;
		//当前节点的右子节点指向新节点
		this.right = newNode;
	}

	/**
	 * 查找结点
	 * 
	 * @param no
	 *            结点编号
	 */
	public Node search(int no) {
		if (this.no == no) {
			// 如果当前结点为需要查找的结点,返回
			return this;
		}
		if (this.no > no) {
			// 如果目标编号小于当前结点,表示目标结点在左边
			if (this.left != null) {
				return this.left.search(no);
			}
			// 没找到
			return null;
		}
		// 如果目标编号大于当前结点,表示目标结点在右边
		if (this.right != null) {
			return this.right.search(no);
		}
		// 没找到
		return null;
	}

	/**
	 * 查找结点的父结点
	 * 
	 * @param no
	 * @return
	 */
	public Node searchParent(int no) {
		// 如果作右子结点中存在目标结点,则放回当前结点
		if ((this.left != null && this.left.no == no) || (this.right != null && this.right.no == no)) {
			return this;
		}
		// 如果当前结点大于目标结点,向左递归查找
		if (this.no > no && this.left != null) {
			return this.left.searchParent(no);
		}
		// 如果当前结点小于目标结点,向右递归查找
		if (this.no < no && this.right != null) {
			return this.right.searchParent(no);
		}
		// 没找到返回null
		return null;
	}

	/**
	 * 添加结点
	 * 
	 * @param node
	 *            待添加的结点
	 */
	public void add(Node node) {
		// 判空,添加的结点为空则不添加
		if (node == null) {
			return;
		}
		if (node.no < this.no) {
			// 如果待添加的结点小于当前节点,想左子树添加
			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);
			}
		}
		
		//当右子树的高度-左子树的高度之后大于1,进行左旋转
		if(this.rightHeight()>this.leftHeight()) {
			//如果右子节点的左子树高度-右节点的右子树高度>1,先让右子树平衡
			if(this.right!=null&&this.right.leftHeight()>this.right.rightHeight()) {
				this.right.rightRotate();
			}
			this.leftRotate();
			//当左子树的高度-右子树的高度之后大于1,进行右旋转
		}else if(this.leftHeight()>this.rightHeight()) {
			//如果左子节点的右子树高度-左节点的左子树高度>1,先让左子树平衡
			if(this.left!=null&&this.left.rightHeight()>this.left.leftHeight()) {
				this.left.leftRotate();
			}
			this.rightRotate();
		}
	}

	/**
	 * 中序遍历
	 */
	public void infixOrder() {
		if (this.left != null) {
			this.left.infixOrder();
		}
		System.out.println(this);
		if (this.right != null) {
			this.right.infixOrder();
		}
	}
}

多叉树

二叉树的问题分析

二叉树需要加载到内存,如果二叉树的结点少,没有什么问题,但如果二叉树的结点非常多(比如上亿),就存在如下问题:

  1. 再构建二叉树时,需要进行多次操作(海量数据存在数据库或者文件中),结点海量,构建二叉树时的速度就慢
  2. 结点海量也会操作二叉树的高度很大,降低其他的操作速度

多叉树

  1. 在二叉树中每个结点有数据项,最多两个子结点,如果允许每个结点可以有更多的数据项和更多的子结点,就是多叉树
  2. 如2-3树,2-3-4树就是多叉树,多叉树通过重新组织结点,降低树的高度,提高树的操作速度

2-3树

  1. 2-3树的所有结点都在同一层(只要是B树都满足这个条件)
  2. 有两个子结点的结点,叫做二节点,二节点要么没有子结点要么有两个子结点
  3. 有三个子结点的结点叫做三节点,三节点要么没有子节点要么有三个子结点
  4. 2-3树就是有二节点和三节点构成的树
    在这里插入图片描述

B树、B+树、B*树

B树

B-tree即B树而不是B-树,B即Balance;如2-3树和2-3-4树都是B树

  1. B树的阶:结点的最多子结点个数,比如2-3树的阶是3,2-3-4树的阶是4
  2. B树的搜索,是从根节点开始,对结点内的关键子序列进行二分查找,如果命中则结束,否则进入查询关键字范围的子结点,重复找直到找到对应的子结点为空或已经是叶子结点
  3. 关键字集合分布在整棵树中,即叶子节点和非叶子节点都存放数据
  4. 其搜索性能等价于在关键字全集内进行一次二分查找

B+树

B+树是B树的变体,也是一种多路搜索树

  1. B+树的搜索与B树也基本相同,区别在于B+树只有达到叶子结点才会命中,即真实数据只存在于叶子结点,其性能也相当于关键字全集内进行一次二分查找
  2. 所有关键子都出现在叶子结点的链表中,(即数据值能在叶子结点[也叫稠密索引]),且链表中的关键字恰好是有序的
  3. 非叶子结点相当于是叶子节点的索引(稀疏索引),叶子结点相当于存储数据的数据层
  4. B+树更适合文件索引系统
  5. B树和B+树各有各自的适合的场景,不能说那一个完全更好

B*树

B*树是B+树的变体,也是一种多路搜索树

  1. B树定义了非叶子结点关键字个数至少为2/3M,即块的最低使用率为2/3,而B+树块的利用率为1/2
  2. B*树分配新结点的概率比B+树要低,空间使用率更高
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值