• 二叉查找树详解


    转自:伯乐在线http://blog.jobbole.com/79305/

    一 定义

      二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

      1. 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

      2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

      3. 任意节点的左、右子树也分别为二叉查找树。

      4. 没有键值相等的节点(no duplicate nodes)。

      如下图,这个是普通的二叉树:

      在此基础上,加上节点之间的大小关系,就是二叉查找树:

    二 实现

      在实现中,我们需要定义一个内部类Node,它包含两个分别指向左右节点的Node,一个用于排序的Key,以及该节点包含的值Value,还有一个记录该节点及所有子节点个数的值Number。

    private static class BinaryNode<T> {
    	T data;
    	BinaryNode<T> left;
    	BinaryNode<T> right;
    	public BinaryNode(T data) {
    		this(data, null, null);
    	}
    	public BinaryNode(T data, BinaryNode<T> left, BinaryNode<T> right) {
    		this.data = data;
    		this.left = left;
    		this.right = right;
    	}
    }
    

    查找

      查找操作和二分查找类似,将key和节点的key比较,如果小于,那么就在Left Node节点查找,如果大于,则在Right Node节点查找,如果相等,直接返回Value。

      该方法实现有迭代和递归两种。递归的方式实现如下:

    public boolean contains(T data) {
    	return contains(data, root);
    }
    private boolean contains(T data, BinaryNode<T> node) {
    	if (node == null) {
    		return false;
    	}
    	int compareResult = data.compareTo(node.data);
    	if (compareResult < 0) {
    		return contains(data, node.left);
    	} else if (compareResult > 0) {
    		return contains(data, node.right);
    	} else {
    		return true;
    	}
    }
    

    插入

      插入和查找类似,首先查找有没有和key相同的,如果有,更新;如果没有找到,那么创建新的节点。并更新每个节点的Number值,代码实现如下:

    public void insert(T data) {
    	root = insert(data, root);
    }
    private BinaryNode<T> insert(T data, BinaryNode<T> node) {
    	if(node==null){
    		return new BinaryNode(data, null, null);
    	}
    	int compareResult=data.compareTo(node.data);
    	if(compareResult<0)
    		node.left=insert(data, node.left);
    	else if(compareResult>0)
    		node.right=insert(data,node.right);
    	else
    		;
    	return node;
    }
    

      插入操作图示如下:

      下面是插入动画效果:

      随机插入形成树的动画如下,可以看到,插入的时候树还是能够保持近似平衡状态:

    最大最小值

      如下图可以看出,二叉查找树的最大最小值是有规律的:

      从图中可以看出,二叉查找树中,最左和最右节点即为最小值和最大值,所以我们只需迭代调用即可。

    public T findMin() throws Exception {
    	if (isEmpty())
    		throw new Exception();
    	return findMin(root).data;
    }
    public T findMax() throws Exception {
    	if (isEmpty())
    		throw new Exception();
    	return findMax(root).data;
    }
    private BinaryNode<T> findMin(BinaryNode<T> node) {
    	if (node == null)
    		return null;
    	else if (node.left == null)
    		return node;
    	else
    		return findMin(node.left);
    	}
    private BinaryNode<T> findMax(BinaryNode<T> node) {
    	if (node != null) {
    		while (node.right != null) {
    			node = node.right;
    		}
    	}
    	return node;
    }
    

    Floor和Ceiling

      查找Floor(key)的值就是所有<=key的最大值,相反查找Ceiling的值就是所有>=key的最小值,下图是Floor函数的查找示意图:

      以查找Floor为例,我们首先将key和root元素比较,如果key比root的key小,则floor值一定在左子树上;如果比root的 key大,则有可能在右子树上,当且仅当其右子树有一个节点的key值要小于等于该key;如果和root的key相等,则floor值就是key。根据 以上分析,Floor方法的代码如下,Ceiling方法的代码类似,只需要把符号换一下即可:

    删除

      删除元素操作在二叉树的操作中应该是比较复杂的。首先来看下比较简单的删除最大最小值得方法。以删除最小值为例,我们首先找到最小值,及最左边左子树为空的节点,然后返回其右子树作为新的左子树。操作示意图如下:

    代码实现如下:

      删除最大值也是类似。

      现在来分析一般情况,假定我们要删除指定key的某一个节点。这个问题的难点在于:删除最大最小值的操作,删除的节点只有1个子节点或者没有子节点,这样比较简单。但是如果删除任意节点,就有可能出现删除的节点有0个,1 个,2个子节点的情况,现在来逐一分析。

      当删除的节点没有子节点时,直接将该父节点指向该节点的link设置为null。

      当删除的节点只有1个子节点时,将该自己点替换为要删除的节点即可。

      当删除的节点有2个子节点时,问题就变复杂了。

      假设我们删除的节点t具有两个子节点。因为t具有右子节点,所以我们需要找到其右子节点中的最小节点,替换t节点的位置。这里有四个步骤:

      1. 保存带删除的节点到临时变量t

      2. 将t的右节点的最小节点min(t.right)保存到临时节点x

      3. 将x的右节点设置为deleteMin(t.right),该右节点是删除后,所有比x.key最大的节点。

      4. 将x的做节点设置为t的左节点。

      整个过程如下图:

    对应代码如下:

    public void remove(T data) {
    	root = remove(data, root);
    }
    private BinaryNode<T> remove(T data, BinaryNode<T> node) {
    	if(node==null)
    		return node;
    	int compareRresult=data.compareTo(node.data);
    	if(compareRresult<0)
    		node.left=remove(data, node.left);
    	else if(compareRresult>0)
    		node.right=remove(data, node.right);
    	else if(node.left!=null&&node.right!=null){
    		node.data=findMin(node.right).data;
    		node.right=remove(node.data, node.right);
    	}else
    		node=(node.left!=null)?node.left:node.right;
    	return node;
    }
    

      以上二叉查找树的删除节点的算法不是完美的,因为随着删除的进行,二叉树会变得不太平衡,下面是动画演示。

    三 分析

      二叉查找树的运行时间和树的形状有关,树的形状又和插入元素的顺序有关。在最好的情况下,节点完全平衡,从根节点到最底层叶子节点只有lgN个节点。在最差的情况下,根节点到最底层叶子节点会有N各节点。在一般情况下,树的形状和最好的情况接近。

      在分析二叉查找树的时候,我们通常会假设插入的元素顺序是随机的。对BST的分析类似与快速排序中的查找:

      BST中位于顶部的元素就是快速排序中的第一个划分的元素,该元素左边的元素全部小于该元素,右边的元素均大于该元素。

      对于N个不同元素,随机插入的二叉查找树来说,其平均查找/插入的时间复杂度大约为2lnN,这个和快速排序的分析一样,具体的证明方法不再赘述,参照快速排序。

    四 总结

    有了前篇文章 二分查找的分析,对二叉查找树的理解应该比较容易。下面是二叉查找树的时间复杂度:

      它和二分查找一样,插入和查找的时间复杂度均为lgN,但是在最坏的情况下仍然会有N的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是后面要讲的平衡查找树的内容了。

    英文原文:http://algs4.cs.princeton.edu/32bst/

  • 相关阅读:
    破解密码那些事儿(Hacking Secret Ciphers with Python)
    Hacking Secret Ciphers with Python翻译序言
    闲话高并发的那些神话,看京东架构师如何把它拉下神坛
    实现rabbitmq 延迟队列功能
    日志文件的编写
    依赖倒置、控制反转和依赖注入的区分
    Oracle ORA-01722: 无效数字 处理方法
    Time.timeScale 对 协程WaitForSeconds的影响
    [转]Coroutine,你究竟干了什么?
    转:Automatic Layout Groups
  • 原文地址:https://www.cnblogs.com/wxgblogs/p/5497444.html
Copyright © 2020-2023  润新知