二叉查找树

来源:互联网 发布:长春市盘古网络 法人 编辑:程序博客网 时间:2024/04/27 18:28

使二叉树成为尔叉查找树的性质是:
对于树中的每个节点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 *******************************trueMin is: 1Max 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
原创粉丝点击