Java实现二叉查找树(Binary Search Tree)

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

  1. 若任意节点的左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
  2. 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 任意节点的左、右子树也分别为二叉查找树。
  4. 没有键值相等的节点(英语:no duplicate nodes)。

二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低。

如下所示的二叉查找树,

 



查找

根据数据表示的递归结构,我们可以得到查找一个键的递归算法:如果树是空树,则查找为命中;如果查找的键值和根节点的键值相等,则查找命中,否则就递归地在相应的子树中继续查找。如果查找的键值较小,就选择左子树,较大则选择右子树。对于命中的查找,路径在含有被查找的键的节点处结束。对于未命中的查找,路径的终点是一个空链接。

在二叉搜索树b中查找x的过程为:

  1. 若b是空树,则搜索失败,否则:
  2. 若x等于b的根结点的数据域之值,则查找成功;否则:
  3. 若x小于b的根结点的数据域之值,则搜索左子树;否则:
  4. 查找右子树。

 



插入

递归实现的put方法的实现逻辑和递归查找很相似:如果树是空的,就返回一个含有该键值对的新节点;如果被查找的键小于根节点的键,我们会继续在左子树中插入该键,否则在右子树中插入该键。这些递归调用值得我们花点时间去理解其中的运行细节。可以将递归调用前的代码想象成沿着树向下走:他会将给定的键和每个节点 的键相比较并根据结果向左或者向右移动到下一个节点。然后可以将递归调用后的代码想象成沿着树向上爬。对于get方法,这对应着一系列的返回指令return,但是对于put方法,这意味着重置搜索路径上每个父节点指向子节点的链接,并增加路径上每个节点中的计数器的值。在一棵简单的二叉查找树中,唯一的新链接就是在最底层指向新节点的链接。

 



分析

使用二叉查找树的算法的运行时间取决于树的形状,而树的形状取决于键值被插入的先后顺序。在最好的情况下,一棵含有N个节点的树是平衡的,每条空链接和根节点的距离都为

~lgN 。在最坏的情况下,搜索路径上可能有N个节点。

命题一:在由N个随机构造的二叉查找树中,查找命中平均所需的比较次数为 ~2lnN(大约 1.39lgN)。

命题一:在由N个随机构造的二叉查找树中,插入操作和查找未命中平均所需的比较次数为 ~2lnN(大约 1.39lgN)。

 



代码实现

package com.usoft;

/**
 * @author: Lenovo(2015-06-30 16:32)
 */
public class BinarySearchTree2<Key extends Comparable<Key>, Value> {

    private Node root; //根节点

    public int size() {
        return size(root);
    }

    private int size(Node x) {
        if (x == null) {
            return 0;
        } else {
            return x.N;
        }
    }

    public Value get(Key key) {
        return get(root, key);
    }

    private Value get(Node x, Key key) {
        if (x == null) {
            return null;
        }
        int cmp = key.compareTo(x.key);
        if (cmp < 0) {
            return get(x.left, key);
        } else if (cmp > 0) {
            return get(x.right, key);
        } else {
            return x.val;
        }
    }

    public void put(Key key, Value val) {
        root = put(root, key, val);
    }

    private Node put(Node x, Key key, Value val) {
        //如果key存在于以x为根节点的子树中则更新他的值;
        //否则将以key和val的键值对为新节点插入到该子树中
        if (x == null) {
            return new Node(key, val, 1);
        }
        int cmp = key.compareTo(x.key);
        if (cmp < 0) {
            x.left = put(x.left, key, val);
        } else if (cmp > 0) {
            x.right = put(x.right, key, val);
        } else {
            x.val = val;
        }
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    private class Node {
        private Key key; // 键值
        private Value val;
        private Node left, right;
        private int N;  // 以该节点为根的子树中的节点总数(节点计数器)

        public Node(Key key, Value val, int N) {
            this.key = key;
            this.val = val;
            this.N = N;
        }
    }

}

=============END=============