二叉查找树

使二叉树成为尔叉查找树的性质是:
对于树中的每个节点X,它的左子树中所有项的值小于X中的项,而它的右子树中所有的项的值大于X中的项。

由于树的递归定义,通常是递归地编写有关树的操作例程。因为二叉查找树的平均深度是O(log N),所以一般不必担心栈空间被用尽。



1 contains方法

//contains的包装方法
    public boolean contains(int val) {
        return contains(val, root);
    }

    private boolean contains(int val, TreeNode r) {
        if(r == null){
            return false;
        }

        if(val - r.val < 0){
            return contains(val, r.left);
        }
        else if (val - r.val > 0) {
            return contains(val, r.right);
        }
        else {
            return true;
        }
    }


2 findMin 和 findMax 方法

    //findMin包装方法
    public TreeNode findMin() {
        return findMin(root);
    }


    private TreeNode findMin(TreeNode t) {
        if(t == null){
            return null;
        }
        else if (t.left == null) {
            return t;
        }
        else {
            return findMin(t.left);
        }
    }
    //findMax的包装方法
    public TreeNode findMax() {
        return findMax(root);
    }

    private TreeNode findMax(TreeNode t) {
        if(t == null){
            return null;
        }
        else if (t.right == null) {
            return t;
        }
        else {
            return findMax(t.right);
        }
    }


3 insert操作

跟contains类似,就是通过比较,二分查找到要插入的位置;如果发现已经存在,则什么也不做。

    // insert的包装方法
    public void insert(int val) {
        root = insert(root, val);
    }

    private TreeNode insert(TreeNode t, int val) {
        // 由于是递归编写,所以每次找准了要插入的位置才新建节点;不然没调用该方法一次就新建一个节点是一种浪费
        if (t == null) {
            TreeNode node = new TreeNode(val);
            return node;
        }

        // 小于0,则插在在左子树
        if (val - t.val < 0) {
            // 如果左子树为空,则新建节点,插入为左节点即可
            if (t.left == null) {
                TreeNode node = new TreeNode(val);
                t.left = node;
            }
            // 如果左子树不为空,则接着判断一次
            else {
                insert(t.left, val);
            }
        }
        // 如果大于0,则插在右子树
        else if (val - t.val > 0) {
            if (t.right == null) {
                TreeNode node = new TreeNode(val);
                t.right = node;
            } else {
                insert(t.right, val);
            }
        }
        // 发现二叉搜索树中已经存在这个值了
        else {
            // do nothing
        }

        return t;

    }


4 remove操作

remove操作比较复杂。
(1)如果要删除的节点是一个叶子,那么直接去掉就可以了;
(2)如果要删除的节点A有一个儿子,那么调整一下A的父节点的指针,使它直接指向A的儿子即可;
(3)如果要删除的节点A有两个儿子,那么可以用A的右子树中的最小值,代替A的值,然后再去删除那么最小节点即可。这样的好处是,最小节点是一定没有左子树的,所以,删除它,就是用情况2中的步骤。

    // remove的包装方法
    public void remove(int val) {
        root = remove(root, val);
    }

    private TreeNode remove(TreeNode t, int val) {
        if (t == null) {
            return null;
        }

        // 第一步是,找到要删除的节点
        // 小于0,说明该节点在左子树中
        if (val - t.val < 0) {
            t.left = remove(t.left, val);
        }
        // 大于0,说明该节点在右子树中
        else if (val - t.val > 0) {
            t.right = remove(t.right, val);
        }
        // 等于0,说明找到了
        else {
            // 如果这是一个叶子,则直接删除这个节点
            if (t.left == null && t.right == null) {
                t = null;
            }
            // 如果该节点有一个儿子
            else if (t.left == null ^ t.right == null) {
                return t.left != null ? t.left : t.right;
            }
            // 如果有两个儿子
            else if (t.left != null && t.right != null) {
                TreeNode minNode = findMin(t.right);
                t.val = minNode.val;
                t.right = remove(t.right, minNode.val);
            }
        }

        return t;
    }


5 完整代码即测试结果

BinarySearchTree.java

package Tree;

import java.util.LinkedList;

public class BinarySearchTree {

    private TreeNode root;

    public BinarySearchTree() {
        root = null;
    }

    public void clear() {
        root = null;
    }

    public boolean isEmpty() {
        return root == null;
    }

    // remove的包装方法
    public void remove(int val) {
        root = remove(root, val);
    }

    private TreeNode remove(TreeNode t, int val) {
        if (t == null) {
            return null;
        }

        // 第一步是,找到要删除的节点
        // 小于0,说明该节点在左子树中
        if (val - t.val < 0) {
            t.left = remove(t.left, val);
        }
        // 大于0,说明该节点在右子树中
        else if (val - t.val > 0) {
            t.right = remove(t.right, val);
        }
        // 等于0,说明找到了
        else {
            // 如果这是一个叶子,则直接删除这个节点
            if (t.left == null && t.right == null) {
                t = null;
            }
            // 如果该节点有一个儿子
            else if (t.left == null ^ t.right == null) {
                return t.left != null ? t.left : t.right;
            }
            // 如果有两个儿子
            else if (t.left != null && t.right != null) {
                TreeNode minNode = findMin(t.right);
                t.val = minNode.val;
                t.right = remove(t.right, minNode.val);
            }
        }

        return t;
    }

    // insert的包装方法
    public void insert(int val) {
        root = insert(root, val);
    }

    private TreeNode insert(TreeNode t, int val) {
        // 由于是递归编写,所以每次找准了要插入的位置才新建节点;不然没调用该方法一次就新建一个节点是一种浪费
        if (t == null) {
            TreeNode node = new TreeNode(val);
            return node;
        }

        // 小于0,则插在在左子树
        if (val - t.val < 0) {
            // 如果左子树为空,则新建节点,插入为左节点即可
            if (t.left == null) {
                TreeNode node = new TreeNode(val);
                t.left = node;
            }
            // 如果左子树不为空,则接着判断一次
            else {
                insert(t.left, val);
            }
        }
        // 如果大于0,则插在右子树
        else if (val - t.val > 0) {
            if (t.right == null) {
                TreeNode node = new TreeNode(val);
                t.right = node;
            } else {
                insert(t.right, val);
            }
        }
        // 发现二叉搜索树中已经存在这个值了
        else {
            // do nothing
        }

        return t;

    }

    // contains的包装方法
    public boolean contains(int val) {
        return contains(val, root);
    }

    private boolean contains(int val, TreeNode r) {
        if (r == null) {
            return false;
        }

        if (val - r.val < 0) {
            return contains(val, r.left);
        } else if (val - r.val > 0) {
            return contains(val, r.right);
        } else {
            return true;
        }
    }

    // findMin包装方法
    public TreeNode findMin() {
        return findMin(root);
    }

    private TreeNode findMin(TreeNode t) {
        if (t == null) {
            return null;
        } else if (t.left == null) {
            return t;
        } else {
            return findMin(t.left);
        }
    }

    // findMax的包装方法
    public TreeNode findMax() {
        return findMax(root);
    }

    private TreeNode findMax(TreeNode t) {
        if (t == null) {
            return null;
        } else if (t.right == null) {
            return t;
        } else {
            return findMax(t.right);
        }
    }


    public void buildTree() {
        /**
         *         6
         *        / \
         *       2   8
         *      / \
         *     1   4
         *        /
         *       3
         * **/

        root = new TreeNode(6);
        TreeNode l = new TreeNode(2);
        TreeNode r = new TreeNode(8);
        TreeNode ll = new TreeNode(1);
        TreeNode lr = new TreeNode(4);
        TreeNode lrl = new TreeNode(3);

        root.left = l;
        root.right = r;
        l.left = ll;
        l.right = lr;
        lr.left = lrl;
    }

    public void display() {
        if (root == null) {
            return;
        }
        System.out.println("\n*******************************");
        LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
        TreeNode p;
        queue.addLast(root);
        int parentCount = 1;
        int childrenCount = 0;
        while (!queue.isEmpty()) {
            p = queue.removeFirst();
            System.out.print(p.val + " ");
            parentCount--;

            if (p.left != null) {
                queue.addLast(p.left);
                childrenCount++;
            }
            if (p.right != null) {
                queue.addLast(p.right);
                childrenCount++;
            }

            if (parentCount == 0) {
                System.out.println();
                parentCount = childrenCount;
                childrenCount = 0;
            }

        }
        System.out.println("*******************************\n");

    }

}

Main.java

package Tree;

public class Main {

    public static void main(String[] args) {
        BinarySearchTree bTree = new BinarySearchTree();

        /**新建一个用于测试的二叉搜索树
         *         6
         *        / \
         *       2   8
         *      / \
         *     1   4
         *        /
         *       3
         * **/
        bTree.buildTree();
        // 按层次打印这棵树
        bTree.display();

        // 是否包含 3 这个值?
        System.out.println(bTree.contains(3) + "\n");

        // 最小、最大值是多少?
        System.out.println("Min is: " + bTree.findMin().val);
        System.out.println("Max is: " + bTree.findMax().val);

        // 插入 5
        bTree.insert(5);
        System.out.print("插入 5");
        bTree.display();

        // 插入12
        bTree.insert(12);
        System.out.print("插入12:");
        bTree.display();

        // 删除 8
        // 目前 8 所在的节点有一个儿子 12
        bTree.remove(8);
        System.out.print("删除 8:");
        bTree.display();

        // 删除 2
        // 目前2所在节点有两个儿子 1和 4
        bTree.remove(2);
        System.out.print("删除 2:");
        bTree.display();

    }

}

output:


*******************************
6 
2 8 
1 4 
3 
*******************************

true

Min is: 1
Max is: 8

插入 5
*******************************
6 
2 8 
1 4 
3 5 
*******************************

插入12:
*******************************
6 
2 8 
1 4 12 
3 5 
*******************************

删除 8:
*******************************
6 
2 12 
1 4 
3 5 
*******************************

删除 2:
*******************************
6 
3 12 
1 4 
5 
*******************************


6 二叉搜索树的问题

执行大多数BST基本操作的时间显然依赖于树的形状。如果树是平衡的,即每个节点的左子树的节点数与右子树的节点数大致相等,则当查找指针下移一层的时候,需要查找的节点数降低为原来的一半。当BST变得不平衡的时候,比如极端的,一个BST退化为一个链表,使得查找的效率变成了O(N)。

所以问题有两个:
(1)上述的remove方法有助于使右子树的深度比左子树的浅,因为我们总是用右子树的最小节点来代替要删除的节点。
(2)如果是已经排序好的数据,那么可以想象一种极端情况是所有的节点都没有左儿子,或是,所有的节点都没有右儿子。

上面两个问题又可以总结为一类问题,那就是树的不平衡。为什么不平衡不好呢?因为这样的二叉搜索树,其查找等操作的平均时间将超过 O(log N)。比如,向一棵树输入预先排好的数据,那么这一连串的insert 操作将花费二次时间。为了解决这一类问题,有了平衡二叉树的概念。

平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。构造与调整方法 平衡二叉树的常用算法有红黑树、AVL、Treap等。系统的,将在下一篇博文里介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值