二叉搜索树

二叉搜索树是以一棵二叉树来组织的,如下图所示。这样一棵树可以使用一个链表数据结构来表示,其中每个结点就是一个对象。除了key和卫星数据之外,每个结点还饮食属性left, right 和 p, 它们分别指向结点的左孩子,右孩子和双新。如果某个孩子结点和父结点不存在,则相应属性的值为NIL。根结点是树中唯一父指针为NIL的结点 。
二叉搜索树中的关键字总是以满足二叉搜索树性质的方式来存储:
设x是二叉搜索树中的一个结点。如果y是x左子树中的一个结点,那么y.key<=x.key。如果y是x右子树中的一个结点,那么y.key >= x.key。
所以创建一个关于它节点的类Node:

结构图可参看:

参考代码:
public class Node{
		//依次保存左右节点以及父节点
		Node left;
		Node right;
		Node parent;
		//存放数据值
		private Object data;
		public Node(){
		}
		public Node(Object data){
			this.data = data;
		}
		public Node(Object data, Node parent, Node left, Node right){
			this.data = data;
			this.parent = parent;
			this.left = left;
			this.right = right;
		}
		public String toString(){//只返回data的值
			return    data + "";
		}
		public boolean equals(Object obj){//判断两个对象是否相等
			if(this == obj){
				return true;
			}
			if(obj.getClass() == Node.class){ //getClass()进行类型判断  instanceof: 判断左边对象是否是右边类的实例 --boolean类型的值
				Node target = (Node)obj;
				return data.equals(target.data)
						&& left == target.left
						&& right == target.right
						&& parent == target.parent;
			}
			return false;
		}
	}

遍历:

这里采用的是中序遍历的方法。可参考: 点击打开链接
参考代码:
//中序
	public List<Node> in_order(){
		return in_order(root);
	}

	public List<Node> in_order(Node node){
		List<Node> list = new ArrayList<Node>();
		if(node.left != null){
			list.addAll(in_order(node.left));
		}
		list.add(node);
		if(node.right != null){
			list.addAll(in_order(node.right));
		}
		return list;
	}

查找:
就是根据它的存储特点去不断搜寻,因为他遵循左节点小于根节点,右节点大于根结点的特点。所以沿一条简单路径向下,然后找到相等为止。
//查询
	public Node tree_search(T data){
		return tree_search(root, data);
	}
	private Node tree_search(Node node, T data){// 根据元素去访问这个值
		if(node == null){
			return null;
		}
		//x.compareTo(y) x=y -- 0, x > y - 1, x < y - -1
		int cmp = data.compareTo(node.data);
		if(cmp < 0){
			return tree_search(node.left, data);
		} 
		else if(cmp > 0){
			return tree_search(node.right,data);
		}
		else{
			return node;
		}	
	}

最大/最小关键值元素:
以最小值为例:
如果结点x没有左子树,那么由于x右子树中的每个关键字都至少大于或等于x.key,则以x为根的子树中的最小关键字是x.key。如果结点x有左子树,那么由于其右子树中没有关键字小于x.key,且在左子树中的每个关键字不大于x.key,则以x为根的子树中的最小关键字一定在经x.left为根的子树中。
//返回最大 和最小关键字元素
	public Node mininum(){
		return mininum(root);
	}
	public Node mininum(Node node){
		while(node.left != null){
			node = node.left;
		}
		return node;
	}

	public Node maxinum(){
		return maxinum(root);
	}
	public Node maxinum(Node node){
		while(node.right != null){
			node = node.right;
		}
		return node;
	}

前驱和后继:
这里是根据它排序后的顺序,根据指定的值找到它的前驱和后继
//寻找前驱和后继 简单的遵循一条路径沿树向下 或者 沿树向上。
	public Node successor(Node node){
		if(node.right != null){
			return mininum(node.right);
		}
		Node newNode = node.parent;
		while(newNode != null && node == newNode.right){
			node = newNode;
			newNode = newNode.parent;
		}
		return newNode;
	}

	public Node presuccessor(Node node){
		if(node.left != null){
			return mininum(node.left);
		}
		Node newNode = node.parent;
		while(newNode != null && node == newNode.left){
			node = newNode;
			newNode = newNode.parent;
		}
		return newNode;
	}

插入:
因为要保证二叉搜索树的特点,所以先根据根结点的位置, 和输入的值进行比较,如果大于必然在右子树,如果小于必然在左子树里,不断更新这个根节点,直到找到能插入这个新的元素的位置,在根据它的特点判断是它的右节点还是左节点即可。
//插入数据
	public Node insert(T data) {
		// 如果根节点为空
		if(root == null){
			root = new Node(data, null, null, null); 
		}
		else{
			Node current = root;
			Node parent = null;
			int cmp = 0;
			//搜索到合适的叶子节点,以该节点为父节点添加新的节点
			while(current != null){//找到合适的位置 假定的"null"会被我指定的值替换
				parent = current;
				cmp = data.compareTo(current.data);
				if(cmp < 0){
					current = current.left;
				}
				else{
					current = current.right;
				}
			}
			//创建新节点, 通过直接的比较知道了它的父节点的值, 接下来就是通过比较 判断是他父节点的   左还是右节点
			Node newNode = new Node(data, parent, null, null);
			if(cmp > 0){
				parent.right = newNode;
			}
			else{
				parent.left = newNode;
			}
			return newNode;
		}
		return null;
	}

删除:
  • 如果z没有左孩子(图12-4(a)),那么用其右孩子来替换z,这个右孩子可以是NIL,也可以不是。当z的右孩子是NIL时,此时这种情况归为z没有孩子结点的情形。当z的右孩子非NIL时,这种情况就是z仅有一个孩子结点的情形,该孩子是其右孩子。
  • 如果z仅有一个孩子且为其左孩子(图12-4(b)),那么用其左孩子来替换z。
  • 否则,z既有一个左孩子又有一个右孩子。我们要查找z的后继y,这个后继位于z的右子树中并且没有左孩子(见练习12.2-5)。现在需要将y移出原来的位置进行拼接,并替换树中的z。
  • 如果y是z的右孩子(图12-4(c)),那么用y替换z,并仅留下y的右孩子。
  • 否则,y位于z的右子树中但并不是z的右孩子(图12-4(d))。在这种情况下,先用y的右孩子替换y,然后再用y替换z。
我在这里使用一个transplate()方法,是用一棵"子树"替换另一棵"子树"并成为其双亲孩子节点的方法。讨论了输入的其中一个子树为根(第一个if)、左节点(else if())中、右节点(else)中不断的对其更新。
//删除指定节点
	public void delete(T element){

		//获取要删除的节点   注意这里是通过查找而不是new
		Node target = tree_search(element);
		if(target == null){
			return;
		}
		else{
			Node newNode = null;

			//没有左孩子, 删除改节点之后把自己的右节点补上来即可
			if(target.left == null){
				transplant(target, target.right);
			}
			//由一个左孩子, 没有右孩子
			else if(target.right == null){
				transplant(target, target.left);
			}
			else{
				//如果它有右子树(或者叫右子树非空) 那么它的后继一定是他右子树的最小值
				newNode = mininum(target.right);
				if(newNode.parent != target){
					transplant(newNode, newNode.right);
					newNode.right = target.right;
					newNode.right.parent = newNode;
				}
				transplant(target, newNode);
				newNode.left = target.left;
				newNode.left.parent = newNode;
			}	
		}
	}
	private void transplant(Node node1, Node node2) {
		if(node1.parent == null){
			Node current = root;
			current = node2;
		}
		else if(node1 == node1.parent.left){
			node1.parent.left = node2;
		}
		else{
			node1.parent.right = node2;
		}
		if(node2 != null){
			node1.parent = node2.parent;
		}
	}

测试部分和截图:

构建二叉搜索树:
//构建二叉排序树
	public SortBinTree(){
		root = null;
	}
	public SortBinTree(T data){
		root = new Node(data, null, null, null);
	}
测试:
public static void main(String[] args) {
		SortBinTree<Integer> sbt = new SortBinTree<Integer>(15);
		SortBinTree.Node node = sbt.insert(5);
		sbt.insert(20);
		sbt.insert(10);
		sbt.insert(3);
		sbt.insert(8);
		sbt.insert(30);
		System.out.println(sbt.in_order());
		System.out.println("最小关键元素值" + sbt.mininum());
		System.out.println("最大关键元素值" +sbt.maxinum());
		sbt.insert(17);
		System.out.println(sbt.in_order());
		System.out.println("节点5的前驱是:" + sbt.presuccessor(node));
		System.out.println("节点5的后继是:" + sbt.successor(node));
		sbt.delete(8);
		System.out.println(sbt.in_order());
	}


参考: 《算法导论》


以上就是这篇的内容了,如果您有什么觉得改进的地方或者发现了错误的部分,请指教。谢谢!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值