Java实现二叉搜索树

为了更加深入了解二叉搜索树,博主自己用Java写了个二叉搜索树,有兴趣的同学可以一起探讨探讨

首先,二叉搜索树是啥?它有什么用呢?

二叉搜索树, 也称二叉排序树,它的每个节点的数据结构为1个父节点指针,1个左孩子指针,1个有孩子指针,还有就是自己的数据部分了,因为只有左右两孩子,所以才叫二叉树,在此基础上,该二叉树还满足另外一个条件:每个结点的左孩子都不大于该结点&&每个结点的右孩子都大于该结点。这样,我们队这棵树进行中序遍历,就能把key从小到大排序了……

那么问题来了,我都有线性表有链表了,我还要它干啥?两个字!效率!

相比线性表,你要搜索一个key,就要执行一次线性时间,算法复杂度为O(n);而用二叉搜索树,算法效率是O(lgn)!这是很诱人的数字。下面我用Java实现以下二叉搜索树,你自然就明白为什么算法复杂度是O(lgn)了。

其次,写一个数据结构,自然而然也要实现对这个数据结构的增、删、查、改了。

下面是我的思路:

  1. 创建树:我是通过一个一个结点的插入来建立一棵二叉搜索树。
  2. 搜索结点:从根节点开始,进行key的比较,小了就往左走,大了就往右走,最后到了叶子结点都还没有的话,那么该树就不存在要搜索的结点了。
  3. 修改结点:修改其实就是查询,在查询之后把结点的数据部分给改了而已,这里我就不重复去实现了。
  4. 删除结点:这个应该就是最难的了,所以我有必要详细讲,先上图(不好意思,懒得用软件画图了,将就将就下哈):

当我们要删除一个结点时,分如下几种情况:
  • 此结点是叶子结点,这个最简单啦,直接把结点给释放掉就行了。(如图删除9)
  • 此结点只有左孩子,这个也简单啦,直接把左子树替换过来就行了。(如图删除3)
  • 此结点只有右孩子,同上。(如图删除8)
  • 此结点有左右孩子,当出现这种情况时(如图删除7),我们就要找出该结点的后继结点(因为右子树肯定存在,所以找肯定在右子树中),然后把这个后继结点替换到要删除的结点中,然后继续执行对这个后继结点的删除操作(递归删除操作就行了)。

发现没?现在我的解题思路是自顶向下去分析……自顶向下,逐级求精是一个很伟大的思想!

现在问题来了!后继结点怎么求?我们来分析一下,当求一个结点的后继结点时,分为以下两种情况:
  • 当该结点有右孩子时,后继结点就在右子树中,就是该右子树的最小结点
  • 当该结点没有右孩子时,那后继结点就满足这个条件:该后继结点是该结点的祖先&&该结点位于该结点的左子树中(如图中的9的后继结点是12)
哎呀呀!问题又来了!最小结点咋办!很简单!
当求一棵树的最小结点时,那么就要从这颗树的根节点开始,一直往左子树走,就能找到它的最小结点了!
好了,现在问题逐步解决了!删除结点的功能也就完成了!
最后,没代码说个锤子,上代码!

首先,写个测试类:
public class Test {
	public static void main(String[] args) {
		int[] datas={12,4,5,7,4,8,3,2,6,9};
		BinTree tree=new BinTree(datas);
		tree.preOrderTraverse();//先序遍历
		tree.midOrderTraverse();//中序遍历
		tree.postOrderTraverse();//后序遍历
		tree.insert(15);	//插入结点
		tree.search(7);		//查询结点
		tree.search(100);	//查询一个不存在的结点
		tree.getMax();		//获取最大值
		tree.getMin();		//获取最小值
		tree.getPre(7);		//前驱结点
		tree.getPre(2);		//最前的前驱结点
		tree.getPost(7);	//后继结点
		tree.getPost(15);	//最后的后继结点
		tree.delete(5);		//删除结点
		tree.delete(0);		//删除一个不存在的结点
	}
}



然后,二叉搜索树:
public class BinTree {
	Node root=null;
	private class Node{
		Node parent=null;
		Node leftChild=null;
		Node rightChild=null;
		int key;
		public Node(int data) {
			this.key=data;
		}
	}
	public BinTree(int[] datas) {
		buildTree(datas);
	}
	private void buildTree(int[] datas) {
		for (int i = 0; i < datas.length; i++) {
			Node node=new Node(datas[i]);
			insertNode(node);
		}
	}
	private void insertNode(Node node) {	//插入结点
		Node next=this.root;	
		Node cur=null;	//用来保存当前结点
		while(next!=null){	//当到达叶子结点时,确认位置!
			cur=next;
			if(node.key>=cur.key){
				next=next.rightChild;
			}else{
				next=next.leftChild;
			}
		}
		node.parent=cur;	//插入该结点!
		if(cur==null){
			this.root=node;  //该树为空树,所以这个是根节点
		}else if(node.key>=cur.key){
			cur.rightChild=node;
		}else{
			cur.leftChild=node;
		}
	}
	
	public void insert(int data){	//供外部测试调用
		Node node=new Node(data);
		System.out.println("插入结点:"+data);
		insertNode(node);
		this.midOrderTraverse();
	}
	
	
	private void preOrderTraverse(Node node){	//先序遍历
		if(node!=null){
			System.out.print("-"+node.key+"-");
			preOrderTraverse(node.leftChild);
			preOrderTraverse(node.rightChild);
		}
	}
	
	public void preOrderTraverse(){	
		System.out.println("先序遍历:");
		preOrderTraverse(root);
		System.out.println();
	}
	
	private void midOrderTraverse(Node node){	//中序遍历
		if(node!=null){
			midOrderTraverse(node.leftChild);
			System.out.print("-"+node.key+"-");
			midOrderTraverse(node.rightChild);
		}
		
	}
	
	public void midOrderTraverse(){	
		System.out.println("中序遍历:");
		midOrderTraverse(root);
		System.out.println();
	}
	
	private void postOrderTraverse(Node node){ 	//后序遍历
		if(node!=null){
			System.out.print("-"+node.key+"-");
			postOrderTraverse(node.leftChild);
			postOrderTraverse(node.rightChild);
		}
	}
	public void postOrderTraverse(){
		System.out.println("后序遍历:");
		postOrderTraverse(root);
		System.out.println();
	}
	
	private Node searchNode(Node node){	//private供内部调用,搜索结点
		if(node==null){
			System.out.println("输入为空,查找失败!");
		}else{
			if(root==null){
				System.out.println("该树为空树!");
			}else{						//开始查找
				boolean isFound=false;	
				Node x=root;
				Node y=null;
				while(!isFound&&x!=null){	//当查到或者到了叶子节点还没查到时,终结!
					y=x;
					if(node.key==x.key){	
						isFound=true;
					}else{					//通过比较大小往下面查找
						if(node.key>x.key){	
							x=x.rightChild;
						}else{
							x=x.leftChild;
						}
					}
				}
				if(isFound){	//没找到的话,在最后返回null
					return y;
				}
			}
		}
		return null;
	}
	
	public void search(int data){	//public供外部调用,搜索结点
		System.out.println("您要查找的是:"+data);
		Node node;
		if((node=searchNode(new Node(data)))==null){
			System.out.println("树中没有该结点!");
		}else{
			System.out.println("查找"+node.key+"成功!");
		}
	}
	
	private Node getMaxNode(Node node){	//获取最大值
		if(node!=null){
			Node x=node;
			Node y=null;
			while(x!=null){	//一直往右遍历直到底就是最大值了!
				y=x;
				x=x.rightChild;
			}
			return y;
		}
		return null;
	}
	
	public void  getMax(){	//获取最大值,供外部调用
		Node node;
		if((node=getMaxNode(root))==null){
			System.out.println("该树为空!");
		}else{
			System.out.println("最大的结点是:"+node.key);
		}
		
	}
	
	private Node getMinNode(Node node){	//获取最大值
		if(node!=null){
			Node x=node;
			Node y=null;
			while(x!=null){	//一直往左遍历直到底就是最小值了!
				y=x;
				x=x.leftChild;
			}
			return y;
		}
		return null;
	}
	
	public void getMin(){	//获取最小值,供外部调用
		Node node;
		if((node=getMinNode(root))==null){
			System.out.println("该树为空!");
		}else{
			System.out.println("最小的结点是:"+node.key);
		}
	}
	
	
	private Node getPreNode(Node node){	//获取前驱结点
		if(node==null){
			return null;
		}
		if(node.leftChild!=null){	//当有左孩子时,前驱结点就是左子树的最大值
			return getMaxNode(node.leftChild);
		}else{//当不存在左孩子时,前驱结点就是——它的祖先,而且,它在这个祖先的右子树中。这句话自己画图就能理解了
			Node x=node;
			Node y=node.parent;
			while(y!=null&&x==y.leftChild){
				x=y;
				y=y.parent;
			}
			return y;
		}
	}
	
	public void getPre(int data){	//public供外部调用,获取前驱结点
		Node node=null;
		System.out.println(data+"的前驱结点:");
		if((node=getPreNode(searchNode(new Node(data))))==null){
			System.out.println("该结点不存在或无前驱结点!");
		}else{
			System.out.println(data+"的前驱结点为:"+node.key);
		}
	}
	
	private Node getPostNode(Node node){	//获取后继结点
		if(node==null){
			return null;
		}
		if(node.rightChild!=null){	//当有右孩子时,前驱结点就是右子树的最小值
			return getMinNode(node.rightChild);
		}else{//当不存在右孩子时,后继结点就是——它的祖先,而且,它在这个祖先的左子树中。这句话自己画图就能理解了
			Node x=node;
			Node y=node.parent;
			while(y!=null&&x==y.rightChild){
				x=y;
				y=y.parent;
			}
			return y;
		}
	}
	
	public void getPost(int data){	//public供外部调用,获取后继结点
		Node node=null;
		System.out.println(data+"的后继结点:");
		if((node=getPostNode(searchNode(new Node(data))))==null){
			System.out.println("该结点不存在或无后继结点!");
		}else{
			System.out.println(data+"的后继结点为:"+node.key);
		}
	}
	
	
	
	private void deleteNode(Node node){
		if(node==null){
			System.out.println("删除结点不能为空!");
			return;
		}
		replacedNode(node);
	}
	
	private void replacedNode(Node node) {	//替换结点
		if(node.leftChild!=null
				&&node.rightChild!=null){	//当有左右孩子时,用后继结点替换
			replacedNodeOfPost(node);
		}
		else
		{
			if(node.leftChild!=null){	//当只有左孩子时,直接用左子树替换
				node=node.leftChild;
			}else if(node.rightChild!=null){	//只有右孩子时,直接有子树替换
				node=node.rightChild;
			}else{			//当没有左右孩子时,就直接释放了这个结点
				freeNode(node);
			}
		}
	}
	
	
	private void freeNode(Node node) {	//释放该结点,断掉其与父结点的链接
		if(node==node.parent.leftChild){
			node.parent.leftChild=null;
		}else{
			node.parent.rightChild=null;
		}
	}
	
	private void replacedNodeOfPost(Node node) {	
		Node y=this.getPostNode(node);	//找后继结点
		node.key=y.key;
		replacedNode(y);	//替换了key之后,再一次递归把现在这个结点给替换了!
	}
	
	public void delete(int data){	//public供外部调用,删除结点
		Node node;
		if((node=searchNode(new Node(data)))==null){//注意!这里不能new结点!你必须从树中找该结点!new就是初始化了
			System.out.println("二叉树中不存在此结点!");
			return;
		}
		deleteNode(node);
		System.out.println("删除结点"+data+"后:");
		this.midOrderTraverse();
	}
}

最后是测试结果:
------------------分割线-------------------------
先序遍历:
-12--4--3--2--5--4--7--6--8--9-
中序遍历:
-2--3--4--4--5--6--7--8--9--12-
后序遍历:
-12--4--3--2--5--4--7--6--8--9-
插入结点:15
中序遍历:
-2--3--4--4--5--6--7--8--9--12--15-
您要查找的是:7
查找7成功!
您要查找的是:100
树中没有该结点!
最大的结点是:15
最小的结点是:2
7的前驱结点:
7的前驱结点为:6
2的前驱结点:
该结点不存在或无前驱结点!
7的后继结点:
7的后继结点为:8
15的后继结点:
该结点不存在或无后继结点!
删除结点5后:
中序遍历:
-2--3--4--4--6--7--8--9--12--15-
二叉树中不存在此结点!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值