二叉搜索树的Java实现

在二叉搜索树中,对于一个节点 x ,其左子树中所有节点的值都不大于 x 的值,其右子树中所有节点的值都不小于 x 的值。

树节点类

要实现二叉搜索树,首先需要构造一个 TreeNode 的类,在这个类中 value 属性用于存储节点的值,parentleftChildrightChild 分别为其父节点、左孩子节点和右孩子节点。

public class TreeNode<T extends Comparable<T>> {
	public T value;
	public TreeNode<T> parent;  // 父节点
	public TreeNode<T> leftChild;  // 左孩子
	public TreeNode<T> rightChild;  // 右孩子
	
	public TreeNode(T value) {
		this.value = value;
	}
}

二叉搜索树类

然后,可以在 TreeNode 的基础上实现 BSTree (二叉搜索树)类。支持对二叉搜索树的插入、删除、查找、前序遍历、中序遍历和后序遍历等操作。

插入节点

插入新节点的思路比较简单。如果当前树的根节点为 null ,则直接把要插入的节点设置为根节点即可。否则,就从根节点开始,查找当前节点应该插入的位置,与堆不同,在二叉搜索树中新插入的节点一定是个叶子节点。

/**
	 * 插入一个对象
	 * @param value
	 */
	public void add(T value) {
		TreeNode<T> node = new TreeNode<>(value);
		this.insert(node);
	}
	
	/**
	 * 插入一个新节点
	 * @param node
	 */
	private void insert(TreeNode<T> node) {
		TreeNode<T> current = this.root;
		TreeNode<T> p = null;  // 记录current的父节点
		while(current!=null) {
			p = current;
			if(node.value.compareTo(current.value)<0) {
				current = current.leftChild;
			}else {
				current = current.rightChild;
			}
		}
		
		node.parent = p;
		if(p==null) {  // 二叉树为空
			this.root = node;
		}else {  // 二叉树不为空
			if(node.value.compareTo(p.value)<0) {
				p.leftChild = node;
			}else {
				p.rightChild = node;
			}
		}
		
		this.size++;		
	}

删除节点

相比于插入节点来讲,删除节点会麻烦一些,需要分几种情况:

  • 如果要删除的节点没有孩子,直接将它删掉即可;
  • 如果要删除的节点只有一个孩子,直接用它的孩子来代替它就行了;
  • 如果要删除的节点有两个孩子(左孩子和右孩子),这种情况最麻烦。我们需要用它左子树中最大的节点或右子树中最小的节点来代替它。下面的代码用的是右子树中最小的节点。

为了实现方便,用 transplant(TreeNode<T> u, TreeNode<T> v) 函数来实现用节点 v 替换节点 u 的功能。

	/**
	 * 在树中删掉一个值为 value 的节点
	 * @param value
	 */
	public void delete(T value) {
		TreeNode<T> node = this.search(value);
		if(node != null) {
			this.delete(node);
		}	
	}
	
	/**
	 * 删除一个节点
	 * @param node
	 */
	private void delete(TreeNode<T> node) {
		if(node.leftChild==null) {  // 要删除的节点没有左子树
			transplant(node, node.rightChild);
		}else if(node.rightChild==null) {  // 要删除的节点没有右子树
			transplant(node, node.leftChild);
		}
		else {  // 既有左子树也有右子树,需要用node的后继节点来代替node
			TreeNode<T> current = this.minimum(node.rightChild);
			if(current.parent!=node) {
				this.transplant(current, current.rightChild);
				current.rightChild = node.rightChild;
				current.rightChild.parent = current;
			}
			this.transplant(node, current);
			current.leftChild = node.leftChild;
			current.leftChild.parent = current;
		}
		
		this.size--;
		
	}
	
	/**
	 * 在树种用 v 代替 u
	 * 是删除操作的一个子过程
	 * @param u
	 * @param v
	 */
	private void transplant(TreeNode<T> u, TreeNode<T> v) {
		if(u.parent==null) {
			this.root = v;
		}else if(u==u.parent.leftChild) {
			u.parent.leftChild = v;
		}else {
			u.parent.rightChild = v;
		}
		
		if(v!=null) {
			v.parent = u.parent;
		}
	}

BSTree类的完整代码

其他的函数实现原理相对比较简单,因而不再一一详细记录。这里把 BSTree 类的完整实现放在下面,也方便以后使用时,可以直接复制。

/**
 * 二叉搜索树
 * @author brz
 *
 */
public class BSTree<T extends Comparable<T>> {
	
	public TreeNode<T> root;
	public int size;
	
	public BSTree() {
		size = 0;
	}
	
	/**
	 * 插入一个对象
	 * @param value
	 */
	public void add(T value) {
		TreeNode<T> node = new TreeNode<>(value);
		this.insert(node);
	}
	
	/**
	 * 插入一个新节点
	 * @param node
	 */
	private void insert(TreeNode<T> node) {
		TreeNode<T> current = this.root;
		TreeNode<T> p = null;  // 记录current的父节点
		while(current!=null) {
			p = current;
			if(node.value.compareTo(current.value)<0) {
				current = current.leftChild;
			}else {
				current = current.rightChild;
			}
		}
		
		node.parent = p;
		if(p==null) {  // 二叉树为空
			this.root = node;
		}else {  // 二叉树不为空
			if(node.value.compareTo(p.value)<0) {
				p.leftChild = node;
			}else {
				p.rightChild = node;
			}
		}
		
		this.size++;		
	}
	
	/**
	 * 在树中删掉一个值为 value 的节点
	 * @param value
	 */
	public void delete(T value) {
		TreeNode<T> node = this.search(value);
		if(node != null) {
			this.delete(node);
		}	
	}
	
	/**
	 * 删除一个节点
	 * @param node
	 */
	private void delete(TreeNode<T> node) {
		if(node.leftChild==null) {  // 要删除的节点没有左子树
			transplant(node, node.rightChild);
		}else if(node.rightChild==null) {  // 要删除的节点没有右子树
			transplant(node, node.leftChild);
		}
		else {  // 既有左子树也有右子树,需要用node的后继节点来代替node
			TreeNode<T> current = this.minimum(node.rightChild);
			if(current.parent!=node) {
				this.transplant(current, current.rightChild);
				current.rightChild = node.rightChild;
				current.rightChild.parent = current;
			}
			this.transplant(node, current);
			current.leftChild = node.leftChild;
			current.leftChild.parent = current;
		}
		
		this.size--;
		
	}
	
	/**
	 * 在树种用 v 代替 u
	 * 是删除操作的一个子过程
	 * @param u
	 * @param v
	 */
	private void transplant(TreeNode<T> u, TreeNode<T> v) {
		if(u.parent==null) {
			this.root = v;
		}else if(u==u.parent.leftChild) {
			u.parent.leftChild = v;
		}else {
			u.parent.rightChild = v;
		}
		
		if(v!=null) {
			v.parent = u.parent;
		}
	}
	
	/**
	 * 对整棵树进行中序遍历
	 * @return
	 */
	public String inorderTraversal() {
		return this.inorderTraversal(this.root);
	}
	
	/**
	 * 中序遍历以 node 为根节点的二叉搜索子树
	 * @param node
	 */
	private String inorderTraversal(TreeNode<T> node) {
		if(node==null) {
			return "";
		}
		
		String str1 = this.inorderTraversal(node.leftChild);
		String str2 = node.value + " \t";
		String str3 = this.inorderTraversal(node.rightChild);
		
		String str = str1 + str2 + str3;
		return str;
	}
	
	/**
	 * 对整棵树进行前序遍历
	 * @return
	 */
	public String preorderTraversal() {
		return this.preorderTraversal(this.root);
	}
	
	/**
	 * 前序遍历以 node 为根节点的二叉搜索子树
	 * @param node
	 */
	private String preorderTraversal(TreeNode<T> node) {
		if(node==null) {
			return "";
		}
		
		String str1 = node.value + " \t";
		String str2 = this.preorderTraversal(node.leftChild);
		String str3 = this.preorderTraversal(node.rightChild);
		
		String str = str1 + str2 + str3;
		return str;
	}
	
	/**
	 * 对整棵树进行后序遍历
	 * @return
	 */
	public String postorderTraversal() {
		return this.postorderTraversal(this.root);
	}
	
	/**
	 * 后序遍历以 node 为根节点的二叉搜索子树
	 * @param node
	 */
	private String postorderTraversal(TreeNode<T> node) {
		if(node==null) {
			return "";
		}
		
		String str1 = this.postorderTraversal(node.leftChild);
		String str2 = this.postorderTraversal(node.rightChild);
		String str3 = node.value + " \t";
		
		String str = str1 + str2 + str3;
		return str;
	}
	
	/**
	 * 在二叉搜索树种查找值为 value 的节点
	 * @param value
	 * @return
	 */
	public TreeNode<T> search(T value){
		return this.search(this.root, value);
	}
	
	/**
	 * 在以 node 为根节点的子树中查找 值为 value 的节点
	 * @param node
	 * @param value
	 * @return
	 */
	private TreeNode<T> search(TreeNode<T> node, T value){
		TreeNode<T> current = node;
		while(current!=null) {
			if(current.value.compareTo(value)==0) {
				return current;
			}
			else if(current.value.compareTo(value)<0) {
				current = current.rightChild;
			}else {
				current = current.leftChild;
			}
		}
		return current;
	}
	
	/**
	 * 判断树种是否包含值为value的节点
	 * @param value
	 * @return
	 */
	public boolean contains(T value) {
		TreeNode<T> node = this.search(this.root, value);
		return !(node==null);
	}
	
	/**
	 * 返回整棵树中最小的节点
	 * @return
	 */
	public TreeNode<T> minimum(){
		return this.minimum(this.root);
	}
	
	/**
	 * 返回以node为根节点的最小节点
	 * @return
	 */
	private TreeNode<T> minimum(TreeNode<T> node){
		if(node == null) {
			return null;
		}
		
		TreeNode<T> current = node;
		// 一路向左即可
		while(current.leftChild!=null) {
			current = current.leftChild;
		}
		
		return current;
	}
	
	/**
	 * 返回整棵树中值最大的节点
	 * @return
	 */
	public TreeNode<T> maximum(){
		return this.maximum(this.root);
	}
	
	/**
	 * 返回以node为根节点的子树中,值最大的节点
	 * @return
	 */
	private TreeNode<T> maximum(TreeNode<T> node){
		if(node==null) {
			return null;
		}
		
		TreeNode<T> current = node;
		// 一路向右即可
		while(current.rightChild != null) {
			current = current.rightChild;
		}
		
		return current;
	}
	
	/**
	 * 返回 node 的后继节点
	 * 也就是最小的比 node 大的节点
	 * @return
	 */
	public TreeNode<T> successor(TreeNode<T> node){
		if(node == null) {
			return null;
		}
		
		TreeNode<T> current = node.rightChild;
		while(current.leftChild != null) {
			current = current.leftChild;
		}
		
		return current;
	}
	
	/**
	 * 返回 node 节点的前驱节点
	 * 也就是比 node 小的节点中最大的那个
	 * @param node
	 * @return
	 */
	public TreeNode<T> predecessor(TreeNode<T> node){
		if(node==null)
			return null;
		
		TreeNode<T> current = node.leftChild;
		while(current.rightChild != null) {
			current = current.rightChild;
		}
		return current;
	}

}

测试用例

下面是一个用于测试前面代码的例子:

public class BSTreeTest {
	
	/**
	 * 测试自己实现的二叉搜索树
	 */
	public void myTest() {
		Integer[] nums = {6, 2, 8, 1, 4, 3};
		BSTree<Integer> tree = new BSTree<>();
		for(int i=0; i<nums.length; i++) {
			tree.add(nums[i]);
		}
		System.out.println("初始的树有 "+tree.size+" 个节点:");
		System.out.print("  前序遍历:\t");
		System.out.println(tree.preorderTraversal());
		System.out.print("  中序遍历:\t");
		System.out.println(tree.inorderTraversal());
		System.out.print("  后序遍历:\t");
		System.out.println(tree.postorderTraversal());
		
		tree.add(5);
		System.out.println("增加一个节点之后的树有 "+tree.size+" 个节点:");
		System.out.print("  前序遍历:\t");
		System.out.println(tree.preorderTraversal());
		System.out.print("  中序遍历:\t");
		System.out.println(tree.inorderTraversal());
		System.out.print("  后序遍历:\t");
		System.out.println(tree.postorderTraversal());
		
		tree.delete(6);
		System.out.println("删除一个节点之后的树有 "+tree.size+" 个节点:");
		System.out.print("  前序遍历:\t");
		System.out.println(tree.preorderTraversal());
		System.out.print("  中序遍历:\t");
		System.out.println(tree.inorderTraversal());
		System.out.print("  后序遍历:\t");
		System.out.println(tree.postorderTraversal());
		
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		BSTreeTest test = new BSTreeTest();
		test.myTest();
	}

}

上面这段代码首先逐个插入元素构造了一颗二叉搜索树,然后测试了对这棵树增加和删除节点的效果,并输出了三种遍历的结果,其执行结果如下

初始的树有 6 个节点:
  前序遍历:	6 	2 	1 	4 	3 	8 	
  中序遍历:	1 	2 	3 	4 	6 	8 	
  后序遍历:	1 	3 	4 	2 	8 	6 	
增加一个节点之后的树有 7 个节点:
  前序遍历:	6 	2 	1 	4 	3 	5 	8 	
  中序遍历:	1 	2 	3 	4 	5 	6 	8 	
  后序遍历:	1 	3 	5 	4 	2 	8 	6 	
删除一个节点之后的树有 6 个节点:
  前序遍历:	8 	2 	1 	4 	3 	5 	
  中序遍历:	1 	2 	3 	4 	5 	8 	
  后序遍历:	1 	3 	5 	4 	2 	8 	

下面这幅图显示了测试用例中的建树过程
在这里插入图片描述
下图则显示了插入5和删除6的过程
在这里插入图片描述
本文是在准备实习的过程中复习《算法导论》的练手代码,如有过有不足之处欢迎留言或直接联系我,如有实习岗位推荐也欢迎联系。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值