数据结构之二叉搜索树

二叉搜索树

二叉搜索树,又称二叉排序树,二叉查找树,它是一种结点值之间具有一定数量级次序的二叉树,对于树中每个结点:

  • 若其左子树存在,则其左子树中每个节点的值都不大于该节点值;
  • 若其右子树存在,则其右子树中每个节点的值都不小于该节点值。

设x是二叉搜索树中的一个结点。如果y是x左子树中的一个结点,那么y.key<=x.key。如果y是x右子树中的一个结点,那么y.key>=x.key。 《算法导论》

二叉搜索树的性质允许我们通过一个简单的递归算法来按序输出二叉搜索树中的所有结点的值,即二叉搜索树的中序遍历算法(INORDER-TREE-WALK(T.root)):

//令x=T.root
INORDER-TREE-WALK(x){
	if(x!=null){
		INORDER-TREE-WALK(x.left);
		System.out.print(x.key+" ");
		INORDER-TREE-WALK(x.right);
	}
}

如下图所示:输出结果为 1 2 3 5 7 8
在这里插入图片描述

详述二叉搜索树的一系列操作

查找(查找指定值,最大,小值)

查找指定值 查找具有给定关键字的值的结点
输入:二叉搜索树的根结点root,关键字k

//返回给定值结点
TREE-SEARCH(x,k){
	if(x==null||k==x.key){
		return x;
	}
	if(k<x.key){
		return TREE-SEARCH(x.left,k);
	}
	return TREE-SEARCH(x.right,k)
}

若该结点存在,TREE-SEARCH返回一个结点值为k的结点;否则返回null。

最大值或者最小值 由二叉搜索树的性质,很简单就能找出二叉搜索树中结点值最大/小的结点:
结点值最大的结点:从树根开始,一直沿着结点的right直到遇到一个null。
结点值最小的结点:从树根开始,一直沿着结点的left直到遇到一个null。

//x的初始值为根结点
//查找最小值(返回最小值结点)
TREE-MIN(x){
	while(x.left!=null)
		x=x.left;
	return x;
}
//查找最大值(返回最大值结点)
TREE-MAX(x){
	while(x.right!=null)
		x=x.right;
	return x;
}

查找结点的父结点 遍历树,根据结点的值查找其父结点

//查找结点的父结点(返回结点)node的初始为根结点,x为要查找父结点的结点
SEARCHPARENT(node,x){
	if(node==null)
		return null;
	if(node.left!=null&&node.key==x.key)
		return node;
	if(node.right!=null&&node.key=x.key)
		return node;
	if(node.key>x.key&&node.left!=null){
		return SEARCHPARENT(node.left,x)
	}else if(node.key<x.key&&node.right!=null){
		return SEARCHPARENT(node.right,x)
	}else{
		return null;
	}
}

由上述的查找算法容易得出结论:在一颗高度为h的二叉搜索树,其查找操作TREE-SEARCH,TREE-MIN,TREE-MAX都可以在O(h)时间内完成(每一层只进行一次查找操作)。

插入和删除

二叉搜索树的插入和删除会引起二叉搜索树结构的变化,因此,一定要修改数据结构来反映这个变化,在修改中还要注意保持二叉搜索树性质的成立。

插入 也是从树的根结点开始,递归比较树中结点的值与要插入结点的值,向左或是向右遍历取决于当前遍历结点的值于要插入结点的值的大小关系,直到遇到一个为null的位置,该位置即是要插入的结点的位置。

//node初始为根结点,x为要插入的二叉搜索树结点(无返回值)
INSERT(node,x){
	if(x=null)
		return;
	if(x.key<node.key){
		if(node.left=null){
			node.left=x;
		}else{
			INSERT(node.left,x)
		}
	}else{
		if(node.right==null) {
			node.right=x;
		}else {
			INSERT(node.right,x);
		}
	}	
}

删除 删除操作大致分三种基本情况(以下设要删除的结点为x):

  • 如果x没有孩子结点(即x是叶子结点),那么只需要简单地将它删除,并修改它的父结点,用null作为孩子来替换x;
  • 如果x只有一个孩子结点,那么将x的孩子结点提升到树中x的位置,并修改x的父结点,用x的孩子结点来替换x;
  • 如果x有两个孩子(较为复杂),那么要找出x的右子树中值最小的结点y,并把y提升到x的位置,删除原来的y结点(如上述1,2情况)。
//node初始为根结点,x为要删除的二叉搜索树结点(无返回值)(此处设结点类为Node)
DELETE(node,x){
	if(node==null)
		return;
	Node target = TREE-SEARCH(node,x);
	if(target==null)  //若要删除的结点不存在树中
		return null;
	Node parent = SEARCHPARENT(node,x);   //查找要删除结点的父结点
	//1.要删除的结点没有孩子结点
	if(target.left==null&&target.right==null){
		if(parent.left==target)
			parent.left=null;
		if(parent.right==target)
			parent.right=null;
	//3.要删除的结点有两个孩子
	}else if(target.left!=null&&target.right!=null){
		//策略:使用值替换简便操作
		int min = DELETEMIN(node.right);
		target.key=min;
	//2.要删除的结点只有一个孩子
	}else{
		if(target.left!=null){
			if(parent.left==target){
				parent.left=target.left;
			}else{
				parent.right=target.left;
			}
		}else{
			if(parent.left==target){
				parent.left=target.right;
			}else{
				parent.right=target.right;
			}
		}
	}
}
删除以node为根的树中最小的值的结点,并返回最小值
DELETEMIN(node){
	Node target = node;
	while(target.left!=null)
		target=target.left;
	DELETE(node,x);   
	return target.key;
}

显然,在高度为h的二叉搜索树中,INSERT,DELETE操作同样可以在O(h)的时间内完成。

二叉搜索树的实现 (包括上述操作的完整代码)

结点类(SortNode):

package util;

public class SortNode {
	//二叉排序树
	
	public int value;
	public SortNode left;
	public SortNode right;
	
	public SortNode(int value) {
		this.value=value;
	}

	//向子树中添加节点
	public void add(SortNode node) {
		if(node==null) {
			return;
		}
		//判断传入的节点的值与当前子树的根节点的值大小关系
		//传入的节点的值比当前节点的值小
		if(node.value<this.value) {
			//如果左节点为空
			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 midshow(SortNode node) {
		if(node==null) {
			return;
		}
		midshow(node.left);
		System.out.print(node.value+" ");
		midshow(node.right);
	}

	//查找节点
	public SortNode search(int value) {
		if(this.value==value) {
			return this;
		}else if(value<this.value) {
			if(this.left==null) {
				return null;
			}
			return this.left.search(value);
		}else {
			if(this.right==null) {
				return null;
			}
			return this.right.search(value);
		}	
	}

	//搜索父节点
	public SortNode searchparent(int value) {
		if((this.left!=null&&this.left.value==value)||(this.right!=null&&this.right.value==value)) {
			return this;
		}else if(this.value>value&&this.left!=null) {
			return this.left.searchparent(value);
		}else if(this.value<value&&this.right!=null) {
			return this.right.searchparent(value);
		}else {
			return null;
		}
	}
}

二叉搜索树构造类:

package other;

import util.SortNode;

public class BinarySortTree {
	//二叉排序树
	//创建根节点
	SortNode root;
	
	//向二叉排序树中添加节点
	public void addNode(SortNode node) {
		//如果是一颗空树
		if(root==null) {
			root=node;
		}else {
			root.add(node);
		}
	}
	
	//中序遍历二叉排序树,节点的值从小到大排序
	public void midShow() {
		if(root!=null) {
			root.midshow(root);
		}
	}
	
	//根据指定值查找对应节点
	public SortNode searchNode(int value) {
		if(root==null) {
			return null;
		}else {
			return root.search(value);
		}
	}
	
	//删除指定值对应的节点
	public void deleteNode(int value) {
		if(root==null) {
			return;
		}else {
			//找到这个节点
			SortNode target = searchNode(value);
			//如果对应节点不存在
			if(target==null) {
				return;
			}
			//找到对应节点,找出改节点的父节点(通过改父节点删除)
			SortNode parent = searchParentNode(value);
			//1.要删除的节点是叶子节点
			if(target.left==null&&target.right==null) {
				//要删除的节点是父节点的左子节点
				if(parent.left.value==value) {
					parent.left=null;
				//要删除的节点是父节点的右子节点
				}else {
					parent.right=null;
				}
			//2.要删除的节点有两个子节点
			}else if(target.left!=null&&target.right!=null) {
				//删除右子树中值最小的节点,且获取到该节点的值
				int min = deleteMinNode(target.right);
				//替换要删除节点的值
				target.value=min;
			//3.要删除的节点只有一个子节点
			}else {
				//只有一个左子节点
				if(target.left!=null) {
					//要删除的节点是父节点的左子节点
					if(parent.left.value==value) {
						parent.left=target.left;
					//要删除的节点是父节点的右子节点
					}else {
						parent.right=target.left;
					}
				//只有一个右子节点
				}else {
					//要删除的节点是父节点的左子节点
					if(parent.left.value==value) {
						parent.left=target.right;
					//要删除的节点是父节点的右子节点
					}else {
						parent.right=target.right;
					}
				}
			}
		}
	}
	
	//删除树中最小的值的节点,并获取返回最小值
	private int deleteMinNode(SortNode node) {
		SortNode target = node;
		//递归向左找
		while(target.left!=null) {
			target=target.left;
		}
		//删除值最小的节点(delete方法中该情况在调用deleteMinNode之前已解决)
		deleteNode(target.value);
		return target.value;
	}

	//搜索父节点
	public SortNode searchParentNode(int value) {
		if(root==null) {
			return null;
		}else {
			return root.searchparent(value);
		}
	}
}

简单Test:

package classify;

import other.BinarySortTree;
import util.SortNode;

public class BinarySortTreeTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//二叉排序树
		int[] arr = {7,3,10,12,5,1,9};
		//创建一颗二叉排序树
		BinarySortTree bst = new BinarySortTree();
		//循环添加节点
		for(int i:arr) {
			bst.addNode(new SortNode(i));
		}
		
		//中序遍历
		System.out.print("中序遍历二叉排序树:");
		bst.midShow(); //中序遍历二叉排序树:1 3 5 7 9 10 12 
		System.out.println();
		
		//查找
		System.out.print("查找指定值对应节点:");  
		SortNode node = bst.searchNode(10);  
		System.out.println(node);  //返回该结点的地址  查找指定值对应节点:util.SortNode@15db9742
		System.out.print("查找指定值对应节点:");  
		SortNode nodes = bst.searchNode(20);
		System.out.println(nodes);  //不存在的结点   查找指定值对应节点:null
		System.out.print("查找指定值对应的父节点:");
		SortNode nodess = bst.searchParentNode(10);  
		System.out.println(nodess.value);  //7
		
		//删除节点
		//删除叶子节点  1,5,9,12
		bst.deleteNode(12);
		System.out.print("中序遍历二叉排序树:");
		bst.midShow();   //中序遍历二叉排序树:1 3 5 7 9 10 
		System.out.println();
		//删除的节点有一个子节点  10(删了12之后)
		bst.deleteNode(10);
		System.out.print("中序遍历二叉排序树:");
		bst.midShow();   // 中序遍历二叉排序树:1 3 5 7 9 
		System.out.println();
		//删除的节点有两个子节点  7是根节点此时有两个子节点
		bst.deleteNode(7);
		System.out.print("中序遍历二叉排序树:");
		bst.midShow();  // 中序遍历二叉排序树:1 3 5 9 
		System.out.println();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值