二叉搜索树

1、思考

  • 在n个动态的整数中搜索某个整数?即查看是否存在。

如果使用动态数组存储元素,从第0个元素开始遍历搜索,那么平均时间复杂度为O(n)。
如果动态数组中存储的是有序元素,那么使用二分法查找,最坏时间复杂度为O(logn)。但是添加、删除的平均时间复杂度为O(n)。

  • 针对这个需求有没有更好的方案呢?

使用二叉搜索树,添加、删除、搜索的最坏时间复杂度可以优化至O(logn)。

2、二叉搜索树(Binary Search Tree)

二叉搜索树是二叉树的一种,应用比较广泛,又被称为二叉查找树、二叉排序树。
二叉搜索树的特点:

1、任意一个节点的值都大于其左子树所有节点的值。
2、任意一个节点的值都小于其右子树所有节点的值。
3、它的左右子树同样是一个二叉搜索树。
4、二叉搜索树中存储的元素必须具有可比较性。
- a、比如int、double类型数据(其包装类内部已经实现了Comparable接口使其具有可比较性)。如果是自定义的类型,需要自己指定比较方式。
- b、元素不能为null。null不能进行比较。
5、二叉搜索树可以大大提高搜索的效率。
下图中表示的就是一个二叉搜索树:

image.png

2.1、接口设计

//元素的数量
public int size() {}

//元素是否为空
public boolean isEmpty() {}

//清空元素
public void clear() {}

//添加元素
public void add(E element) {}

//删除元素
public void remove(E element) {}

//是否包含某个元素
public boolean contains(E element) {}

注意:对于我们现在使用的二叉树中,并不存在索引的概念。这又是为什么呢?
比如将如下数据添加到二叉搜索树中

8,3,10,1,6,14,4,7,13

效果如下图


image.png

然后我们再添加一个9,此时二叉搜索树如下图


image.png

新添加的元素在第三层,而正常来说应该在最后添加元素的后面,即在最后一层节点13的右边,所以索引对二叉树来说并没有什么意义。

2.2、添加元素add(E element)

添加的步骤:

  • 1、找到parent节点。
  • 2、新建node节点。
  • 3、新建的节点可能在parent的左边,也可能在parent的右边。代码表示为parent.left=node或parent.right=node。
    从上可知:一个节点需要包含元素的值、left节点、right节点、parent节点。增加一个内部类:
private static class Node<E> {
    E element;
    Node<E> left;
    Node<E> right;
    Node<E> parent;

    public Node(Node<E> left, E element, Node<E> right) {
        this.element = element;
        this.left = left;
        this.right = right;
    }
}
  • 1、由于二叉搜索树中的元素不能为null,在添加前需要进行判断。
  • 2、添加的第一个节点为根节点root。
  • 3、如果添加的不是第一个节点,则需要找到要添加节点的父节点。从根节点开始查找,根据二叉搜索树的特性,如果要添加的节点元素的值大于根节点的值,则在根节点的右子树中去查找。然后再和右子树的根节点进行判断,在右子树的左子树或右子树中去查找,直到节点的left或者right等于null。具体实现如下:
// 添加元素
public void add(E element) {
    // 由于二叉搜索树中不能添加null元素,需要进行判断
    elementNotNullCheck(element);

    // 添加第一个节点
    if (root == null) {
        root = new Node<>(element, null);
        size++;
        return;
    }
    // 不是第一个节点
    // 找到parent节点
    Node<E> node = root;
    Node<E> parent = root;
    int cmp = 0;
    while (node != null) {
        cmp = compare(element, node.element);
        parent = node;
        if (cmp > 0) {
            node = node.right;
        } else if (cmp < 0) {
            node = node.left;
        } else {
            node.element=element;
            return;
        }
    }
    // 创建node节点
    Node<E> newNode = new Node<>(element, parent);
    if (cmp > 0) {
        parent.right=newNode;
    }else if(cmp<0) {
        parent.left=newNode;
    }
    // paent.left=node或parent.right=node
    size++;
}

其中compare()方法我们还没有去实现,下面看下如何实现。
二叉搜索树中可以存入任何类型的元素,比如int,其包装类的内部已经实现了Comparable接口,它是可比较的。如果元素类型为自定义的,比如Person类型的,此时就需要让Person类实现Comparable接口。

public class Person implements Comparable<Person> {
    private int age;

    public Person(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    @Override
    public int compareTo(Person person) {
        return this.age - person.age;
    }
}

但是这样的话,如果我们想修改比较方式,就需要修改Person中compareTo()的具体实现,可是其他地方还想使用Person的原有的比较方式呢?我们可以让外部决定比较方式,具体实现如下:

public BinarySearchTree() {
    this(null);
}

public BinarySearchTree(Comparator<E> comparator) {
    this.comparator = comparator;
}

private int compare(E e1, E e2) {
    if (comparator != null)
        return comparator.compare(e1, e2);
    return ((Comparable<E>) e1).compareTo(e2);
}

注意:如果遇到相同的值,我们这里直接将值进行了覆盖,如果二叉搜索树中传入的基本类型的值,覆盖和不覆盖并没有什么意义,但是如果传入的是自定义类型的话,比如Person,如果传入的两个Person年龄相同,但是其他属性不同的话,建议进行覆盖。

2.3、打印二叉树

工具:打印二叉树
将BinarySearchTree实现接口BinaryTreeInfo,并实现方法

@Override
public Object root() {
    return root;
}

@Override
public Object left(Object node) {
    return ((Node<E>) node).left;
}

@Override
public Object right(Object node) {
    return ((Node<E>) node).right;
}

然后调用BinaryTrees.println(bst)

BinarySearchTree<Person> bst = new BinarySearchTree<Person>();
int[] array = { 8, 3, 10, 1, 6, 14, 4, 7, 13, 9 };
String[] nameArray = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" };
for (int i = 0; i < array.length; i++) {
    bst.add(new Person(array[i], nameArray[i]));
}
BinaryTrees.println(bst);

效果如下图


image.png

2.4、删除节点

2.4.1、删除叶子节点

删除叶子节点,直接删除即可。
下图中的节点3 5 8 10以及节点7都是叶子节点。

image.png

下面看下如何删除叶子节点:

  • 1、node.parent ! = null
    如果node==node.parent.left,则node.parent.left==null;
    如果node==node.parent.right,则node.parent.right.right=null;
  • 2、node.parent==null,node是根节点
    root=null;

2.4.2、删除度为1的节点

用子节点代替原节点的位置
下图中的**4、9、9 **都是要删除的度为1的节点

image.png

  • 1、child是node.left或者child是node.right。
  • 2、用child来替代node的位置。
  • 3、如果node.parent != null
    - 1、如果node是node.parent左子节点
    - a、child.parent=node.parent;
    - b、node.parent.left = child;
    -2 、如果node是node.parent的右子节点
    - a、child.parent=node.parent;
    - b、node.parent.right=child;
  • 4、如果node.parent==null,node是根节点
    - a、child.parent=null;
    - b、root=child;

2.4.3、删除度为2的节点

比如删除下图中节点5

image.png

  • 1、先用前驱或者后继节点的值,覆盖要删除的节点的值
  • 2、然后再删除相应的前驱或后继节点。
  • 3、如果要删除的节点度为2,那么它的前驱或后继节点的度只能为1或0。
    所以删除度为2的节点,其实最终删除的是度为1或0的节点。

2.4.4、删除的具体实现

我们知道删除度为2的节点,其实最终会转化成删除度为1的节点或者删除叶子节点,所以为了避免重复判断,可以将删除度为2的节点逻辑写在最前面。

private void remove(Node<E> node) {
    if (node == null)
        return;
    // 删除度为2的节点
    if (node.hasTwoChildren()) {
        // 找到前驱节点
        Node<E> precurNode = precursor(node);
        // 用前驱节点的值覆盖要删除节点的值
        node.element = precurNode.element;
        // 删除前驱节点
        node = precurNode;
    }

    // 删除的node节点的度为1或0
    Node<E> child = node.left != null ? node.left : node.right;
    if (child != null) { // 度为1的节点
        // 使用子节点替代要删除节点的位置
        child.parent = node.parent;
        if (node.parent == null)
            root = child;
        else if (node == node.parent.left)
            node.parent.left = child;
        else if (node == node.parent.right)
            node.parent.right = child;
    } // 删除叶子节点
    else if (node.parent == null)
        root = null;
    else if (node == node.parent.left)
        node.parent.left = null;
    else if (node == node.parent.right)
        node.parent.right = null;
    size--;
}

完整代码如下

public class BinarySearchTree<E> implements BinaryTreeInfo {

    private int size;
    // 根节点
    private Node<E> root;

    private Comparator<E> comparator;

    public static abstract class Visitor<E> {
        public boolean stop;

        public abstract boolean visit(E e);
    }

    public BinarySearchTree() {
        this(null);
    }

    public BinarySearchTree(Comparator<E> comparator) {
        this.comparator = comparator;
    }

    // 元素的数量
    public int size() {
        return size;
    }

    // 元素是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 清空元素
    public void clear() {
        size = 0;
        root = null;
    }

    public void levelorderTraserval() {
        if (root == null)
            return;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            Node<E> node = queue.poll();
            System.out.println(node.element);
            if (node.left != null)
                queue.offer(node.left);
            if (node.right != null)
                queue.offer(node.right);
        }
    }

    // 添加元素
    public void add(E element) {
        // 由于二叉搜索树添加的元素不能为null
        elementNotNullCheck(element);

        // root等于null 第一次添加
        if (root == null) {
            root = new Node<>(element, null);
            size++;
            return;
        }
        // 非第一次添加 元素添加的步骤
        // 1、找到要添加节点的parent节点
        Node<E> node = root;
        Node<E> parent = root;
        int cmp = 0;
        while (node != null) {
            cmp = compare(element, node.element);
            parent = node;
            if (cmp > 0) {
                node = node.right;
            } else if (cmp < 0) {
                node = node.left;
            } else {
                // 当元素的值相等时进行覆盖
                node.element = element;
                return;
            }
        }
        // 2、创建新的节点
        Node<E> newNode = new Node<>(element, parent);
        // 3、该节点是parent的左子节点还是右子节点
        if (cmp > 0) {
            parent.right = newNode;
        } else if (cmp < 0) {
            parent.left = newNode;
        }
        size++;
    }

    public void preorder(Visitor<E> visitor) {
        if (visitor == null)
            return;
        preorder(root, visitor);
//      preorder2();
    }

    // 前序遍历---递归方式
    private void preorder(Node<E> node, Visitor<E> visitor) {
        if (node == null || visitor.stop)
            return;
        visitor.stop = visitor.visit(node.element);
        preorder(node.left, visitor);
        preorder(node.right, visitor);
    }

    // 前序遍历---迭代方式:使用栈来实现
    /**
     * 1、将root入栈 2、弹出栈顶元素 3、将栈顶元素的右子节点入栈 4、将栈顶元素的左子节点入栈 5、栈为空则结束遍历
     */
    private void preorder2() {
        if (root == null)
            return;
        Stack<Node<E>> stack = new Stack<>();
        Node<E> node = root;
        stack.add(node);
        while (!stack.isEmpty()) {
            node = stack.pop();
            System.out.println(node.element);
            if (node.right != null)
                stack.add(node.right);
            if (node.left != null)
                stack.add(node.left);
        }
    }

    public void inorder(Visitor<E> visitor) {
        if (visitor == null)
            return;
        inorder(root, visitor);
//      inorder2();
    }

    // 中序遍历--递归方式实现
    private void inorder(Node<E> node, Visitor<E> visitor) {
        if (node == null || visitor.stop)
            return;
        inorder(node.left, visitor);
        // 在遍历左子树时visitor.stop可能已经为true,所以需要在此处加上判断
        if (visitor.stop)
            return;
        visitor.stop = visitor.visit(node.element);
        inorder(node.right, visitor);
    }

    /**
     * 中序遍历--使用迭代方式实现--使用栈来实现 1、设置node=root 2、进行如下循环操作: 1、如果node!=null a、将node入栈
     * b、设置node=node.left 2、如果node==null a、如果栈为空则结束循环 b、将栈顶元素出栈,并赋值给node c、对node进行访问
     * d、设置node=node.right
     */
    private void inorder2() {
        if (root == null)
            return;
        Stack<Node<E>> stack = new Stack<>();
        // 设置node=root
        Node<E> node = root;
        while (true) {
            /**
             * 如果node不为空,将node加入到栈中 设置node=node.left
             */
            if (node != null) {
                stack.add(node);
                node = node.left;
            } else {
                /**
                 * 如果node==null 此时如果栈为空,则退出循环 将栈顶元素出栈,并赋值给node 访问node 设置node=node.right
                 */
                if (stack.isEmpty())
                    break;
                node = stack.pop();
                System.out.println(node.element);
                node = node.right;
            }

        }

    }

    // 后序遍历
    public void postorder(Visitor<E> visitor) {
        if (visitor == null)
            return;
        postorder(root, visitor);
    }

    // 递归方式实现后序遍历
    private void postorder(Node<E> node, Visitor<E> visitor) {
        if (node == null || visitor.stop)
            return;
        postorder(node.left, visitor);
        postorder(node.right, visitor);
        if (visitor.stop)
            return;
        visitor.stop = visitor.visit(node.element);
    }

    /**
     * 后序遍历---使用迭代方式实现
     * 
     */
    private void postorder2() {

    }

    // 层序遍历
    public void levelorder(Visitor<E> visitor) {
        if (root == null || visitor == null)
            return;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            Node<E> node = queue.poll();
            if (visitor.visit(node.element))
                break;
            if (node.left != null)
                queue.offer(node.left);
            if (node.right != null)
                queue.offer(node.right);
        }
    }

    public int height(E element) {
        return height(node(element));
//      return height2(node(element));
    }

    // 通过元素查找节点
    public Node<E> node(E element) {
        if (root == null)
            return null;
        Node<E> node = root;
        int cmp = 0;
        while (node != null) {
            cmp = compare(element, node.element);
            if (cmp == 0)
                return node;
            if (cmp > 0)
                node = node.right;
            else
                node = node.left;
        }
        return null;
    }

    private int height;// 树的高度
    private int levelCount = 1;// 一层的元素数量
    // 计算二叉树的高度 使用层序遍历的方式

    private int height(Node<E> node) {
        if (node == null)
            return 0;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(node);
        while (!queue.isEmpty()) {
            Node<E> newNode = queue.poll();
            levelCount--;
            if (newNode.left != null)
                queue.offer(newNode.left);
            if (newNode.right != null)
                queue.offer(newNode.right);
            if (levelCount == 0) {
                levelCount = queue.size();
                height++;
            }
        }
        return height;
    }

    private int height2(Node<E> node) {
        if (node == null)
            return 0;
        return 1 + Math.max(height2(node.left), height2(node.right));
    }

    public boolean isCompleteTree() {
        if (root == null)
            return false;
        boolean leaf = false;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            Node<E> node = queue.poll();
            if (leaf && !node.isLeaf())
                return false;

            // 如果node.left!=null就入队
            if (node.left != null)
                queue.offer(node.left);
            else { //
                if (node.right != null)
                    return false;
//              和下面叶子节点判断重复,可去掉
//              else if (node.right == null)
//                  leaf = true;
            }

            if (node.right != null)
                queue.offer(node.right);
            else {
//              if(node.left!=null) {
//                  leaf = true;
//              }else if(node.left==null) { //重复
//                  leaf = true;
//              }
                // 上面判断重复,简化为
                leaf = true;
            }
        }

        return true;
    }

    public E precursor(E element) {
        Node<E> node = precursor(node(element));
        return node == null ? null : node.element;
    }

    // 获取指定节点的前驱结点
    private Node<E> precursor(Node<E> node) {
        if (node == null)
            return node;
        Node<E> leftNode = node.left;
        // node.left.right.right....
        if (leftNode != null) {
            while (leftNode.right != null) {
                leftNode = leftNode.right;
            }
            return leftNode;
        }

        // node.parent.parent....
        while (node.parent != null && node == node.parent.left) {
            node = node.parent;
        }

        // 走到这里条件 node.parent===null || node == node.parent.right
        // 这两种情况下都会返回node.parent
        return node.parent;
    }

    public E successor(E element) {
        Node<E> node = successor(node(element));
        return node == null ? null : node.element;
    }

    // 后继节点
    private Node<E> successor(Node<E> node) {
        if (node == null)
            return null;
        Node<E> rightNode = node.right;
        // node.right.left.left....
        if (rightNode != null) {
            while (rightNode.left != null) {
                rightNode = rightNode.left;
            }
            return rightNode;
        }

        // node.parent.parent...
        while (node.parent != null && node == node.parent.right) {
            node = node.parent;
        }
        return node.parent;
    }

    private int compare(E e1, E e2) {
        if (comparator != null)
            return comparator.compare(e1, e2);
        return ((Comparable<E>) e1).compareTo(e2);
    }

    private void elementNotNullCheck(E element) {
        if (element == null)
            throw new IllegalArgumentException("element must not be null");
    }

    // 删除元素
    public void remove(E element) {
        remove(node(element));
    }

    private void remove(Node<E> node) {
        if (node == null)
            return;
        // 删除度为2的节点
        if (node.hasTwoChildren()) {
            // 找到前驱节点
            Node<E> precurNode = precursor(node);
            // 用前驱节点的值覆盖要删除节点的值
            node.element = precurNode.element;
            // 删除前驱节点
            node = precurNode;
        }

        // 删除的node节点的度为1或0
        Node<E> child = node.left != null ? node.left : node.right;
        if (child != null) { // 度为1的节点
            // 使用子节点替代要删除节点的位置
            child.parent = node.parent;
            if (node.parent == null)
                root = child;
            else if (node == node.parent.left)
                node.parent.left = child;
            else if (node == node.parent.right)
                node.parent.right = child;
        } // 删除叶子节点
        else if (node.parent == null)
            root = null;
        else if (node == node.parent.left)
            node.parent.left = null;
        else if (node == node.parent.right)
            node.parent.right = null;
        size--;
    }

    // 是否包含某个元素
    public boolean contains(E element) {
        return node(element) != null;
    }

    private static class Node<E> {
        E element;
        Node<E> left;
        Node<E> right;
        Node<E> parent;

        public Node(E element, Node<E> parent) {
            this.element = element;
            this.parent = parent;
        }

        public boolean isLeaf() {
            return left == null && right == null;
        }

        public boolean hasTwoChildren() {
            return left != null && right != null;
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        toString(sb, root, "");
        return sb.toString();
    }

    // 使用前序遍历方式打印二叉树
    private void toString(StringBuilder sb, Node<E> node, String prefix) {
        if (node == null)
            return;
        sb.append(prefix).append("【").append(node.element).append("】").append("\n");
        toString(sb, node.left, prefix + "〖L〗");
        toString(sb, node.right, prefix + "〖R〗");
    }

    @Override
    public Object root() {
        return root;
    }

    @Override
    public Object left(Object node) {
        // TODO Auto-generated method stub
        return ((Node<E>) node).left;
    }

    @Override
    public Object right(Object node) {
        // TODO Auto-generated method stub
        return ((Node<E>) node).right;
    }

    @Override
    public Object string(Object node) {
        // TODO Auto-generated method stub
        Node<E> newNode = (Node<E>) node;
        return newNode.element + "_p_" + (newNode.parent == null ? "null" : newNode.parent.element);
    }

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容

  • 如需转载, 请咨询作者, 并且注明出处.有任何问题, 可以关注我的微博: coderwhy, 或者添加我的微信: ...
    coderwhy阅读 8,865评论 12 35
  • 🤔在n个动态的整数中搜索某个整数?(查看其是否存在) 如下列整数 方法一: 使用动态数组存放元素,从第0个位置开始...
    ducktobey阅读 197评论 0 0
  • 概念 二叉搜索树是二叉数的一种特例,二叉树又是树的其中一种,树又是图的一种特例。二叉搜索树作为特例中的特例,结构最...
    invincine阅读 887评论 0 4
  • 接上章: 二叉树简介 本章主要介绍二叉搜索树的性质以及二叉搜索树节点的增加和删除操作。 ◼二叉搜索树是二叉树的一种...
    翀鹰精灵阅读 560评论 0 1
  • 1、前言 二叉树是非常重要的一种数据结构,二叉搜索树是其中的一种类型,其任意节点x,左子树中的关键字最大不超过x....
    某昆阅读 623评论 0 4