龙空技术网

Java数据结构学习系列之二叉搜索树详解

Java耕耘 188

前言:

眼前兄弟们对“java 多叉树”大致比较注重,同学们都想要了解一些“java 多叉树”的相关知识。那么小编在网上汇集了一些有关“java 多叉树””的相关资讯,希望同学们能喜欢,各位老铁们一起来学习一下吧!

介绍

二叉搜索树(Binary Search Tree),也叫作二叉排序树(Binary Sort Tree),后文中统一简称为BST,顾名思义,它的每个结点的叶子数量不得超过两个:

BST插入都是按照一定的规则顺序的,例如图中的结点,F的左边一定比F的值小,F右边一定比F的值大,当然这些都是可以自定义的,不过到头来,总是要有一定的规则进行存储,这样才会方便我们用来检索需要的内容。

BST的基本操作相对于平衡树或者红黑树来讲算是非常简单的,BST并不在乎整个树的平衡性,仅仅保证了结点的有序规则不被打乱,这种问题将会导致大量结点下的查询性能下降,因此便引出了更高级的树种(之后也将会进一步学习)。因此,实现一个BST并不难,我们可以通过Java去实现一个简单的BST来加深对二叉树的了解!

实现

首先是实现思路,我们主要实现插入、查询和删除的功能,其中查询是最简单的,因为不需要变动整个树的结构,删除是较复杂的,接下来将会一一解析其实现思路。

插入

被插入结点与root结点做比较,如果大于,则取root的右结点继续比较,反之则取左结点继续比较,如果相等,则更新当前结点的值。

public Object insert(int index, Object value, Node cur) {		while(true) {			if(cur.index == index) {				//命中则更新				cur.value = value;				break;			}else {				if(index < cur.index) {					if(null == cur.left) {						//无命中则添加						cur.left = new Node(index, value, cur);						cur.left.isLeft = true;						size ++;						break;					}else {						//指向左结点						cur = cur.left;					}				}else{					if(null == cur.right) {						//无命中则添加						cur.right = new Node(index, value, cur);						size ++;						break;					}else {						//指向右结点						cur = cur.right;					}				}			}		}		return value;	}

这就是一个简单的深度查找过程,如果使用递归可能更好理解一些,但是为了防止StackOverFlow,并没用采用之。

代码中从cur这个结点开始,首先判断是否命中目标,也就是cur.index == index如果为true,则代表命中,命中的话则直接更新。

如果没有命中,则要向cur结点的分支方向搜索,如果到了叶子结点还未命中,则要进行插入操作(直接加结点),否则就修改cur指针,继续重复当前动作。

查询

查询类似于插入操作,不同的是查询更为简单:

public Node find(int index, Node cur) {	while(cur != null && cur.index != index) {		cur = index < cur.index ? cur.left : cur.right;	}	return cur;}

深度搜索即可!

删除

BST的删除是比较复杂的,不像插入那般,只需要操作一个结点的left或者right变量引用就可以了,它涉及着多个结点的变化:

@Overridepublic Object remove(int index) {	//首先寻找该结点	Node target = find(index, root);	if(target == null) {		//该结点为空则直接返回null		return null;	}else {		//优先考虑将被移除结点的左结点连接到右结点的左分支		Node n = target.right != null ? target.right : target.left;		if(target.left != null && target.right != null) {			Node leftLoopStarter = n;			//寻找目标结点的右结点下的最左结点			while(leftLoopStarter.left != null) {				leftLoopStarter = leftLoopStarter.left;			}			leftLoopStarter.setLeft(target.left);		}		if(target.parent == null) {			//只剩下root结点不允许删除			if(target.left == null && target.right == null) {				throw new RuntimeException("再删就没了!");			}else {				//当删除root结点时,优先考虑将它的右结点作为新的root结点				root = n;				n.parent = null;			}		}else {			//移除响应的结点			if(target.isLeft) {				target.parent.setLeft(n);			}else {				target.parent.setRight(n);			}		}		size --;		return target.value;	}}

上述代码不足表达整个过程,我们以具体的demo来演示删除过程,假如初始的树形状如下:

 8  4 12  2 6 10 16 1 3 5 7 # 11 14 17

移除结点1,叶子结点,直接移除:

 8  4 12  2 6 10 16 # 3 5 7 # 11 14 17

移除结点2,用右结点代替之:

 8  4 12  3 6 10 16 # # 5 7 # 11 14 17

移除结点4,其左结点放于右结点最左结点之后,并用右结点代替之:

 8  6 12  5 7 10 16 3 # # # # 11 14 17

移除结点16,同上:

 8  6 12  5 7 10 17 3 # # # # 11 14 #

移除结点8,同上:

 12  10 17  6 11 14 #  5 7 # # # # # # 3 # # # # # # # # # # # # # # #

格式化

在调试树的正确性时,无法避免要去查看当前树结点的状态,所以将树格式化并显示出来很有必要,再着手之前,我们首先要找规律:

 a 			 //4-15 首距2^4-1	无结点间隔 b c 			 //3-7 首距2^3-1	结点间隔15 d e f g 		 //2-3 首距2^2-1	结点间隔7 h i g k l m n o 		//1-1 首距2^1-1	结点间隔3a a a a a a a a a a a a a a a a 	 //0-0 首距2^0-1	结点间隔1设高度为n求出首距的公式:2^n-1求出两结点间隔的公式:2^(n+1)-1

由上引出,二叉树的格式化还是很有规律的,我们便可以依据以上公式去写一个算法来格式化输出BST:

@Overridepublic String toString() {	//保存树形	StringBuilder builder = new StringBuilder();	//按层序保留结点	List<Object> cache = new ArrayList<>(50);	//使用栈对树做层序遍历	Queue<Node> queue = new LinkedBlockingQueue<Node>();	queue.add(root);	int depth = 0; //临时深度	int maxDepth = getMaxDepth(); //最大深度	while(! queue.isEmpty()) {		Node cur = queue.poll();		if(cur.left != null) {			queue.add(cur.left);		}else if(cur.depth() < maxDepth){			//填补空缺			queue.add(new Node(0, '#', cur));		}		if(cur.right != null) {			queue.add(cur.right);		}else if(cur.depth() < maxDepth){			//填补空缺			queue.add(new Node(0, '#', cur));		}		if(depth != cur.depth()) {			//深度切换,将高度保存			depth = cur.depth();			cache.add(depth);		}		//加入该结点		cache.add(cur);	}	//将保留结点渲染为树形	for(int index = 0; index < cache.size(); index ++) {		Object o = cache.get(index);		if(o instanceof Integer) {			builder.append(System.lineSeparator());			builder.append(getOffset(maxDepth - (Integer)o));		}else {			if(index == 0) {				builder.append(getOffset(maxDepth));			}			builder.append(((Node)o).value);			builder.append(getOffset(maxDepth - ((Node)o).depth() + 1));		}	}	return builder.toString();}/** * @param height 当前结点高度 = 最大深度 - 当前结点深度 * @return 首距或者两结点的间隔距离 */public String getOffset(double height) {	StringBuilder builder = new StringBuilder();	int count = (int) Math.pow(2d, height) - 1;	while(count -- > 0) {		builder.append(" ");	}	return builder.toString();}/** * @return 当前树的最大深度 */public int getMaxDepth() {	Queue<Node> queue = new LinkedBlockingQueue<Node>();	queue.add(root);	int depth = 0;	while(! queue.isEmpty()) {		Node cur = queue.poll();		if(cur.left != null) queue.add(cur.left);		if(cur.right != null) queue.add(cur.right);		if(depth != cur.depth()) depth = cur.depth();	}	return depth;}

总结

通过BST引导入门是一个很不错的选择!

关注、转发、评论头条号每天分享java 知识,私信回复“555”赠送一些Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式资料

标签: #java 多叉树 #java实现二叉排序树 #求二叉树的高度java