三、Java数据结构实现之二叉树


二叉树,他为啥出现呢,因为数组还不够快

0、树的常用术语

mark

  • 路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。

  • :树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。

  • 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;B是D的父节点。

  • 子节点:一个节点含有的子树的根节点称为该节点的子节点;D是B的子节点。

  • 兄弟节点:具有相同父节点的节点互称为兄弟节点;比如上图的D和E就互称为兄弟节点。

  • 叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的A、E、F、G都是叶子节点。

  • 子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。

  • 节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。

  • 深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;

  • 高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
    二叉树呢,特点左节点一定会比右节点小,先这样,边刷题边总结

1、数据结构

    private Node root;             // root of BST

    private class Node {
        private Key key;           // sorted by key
        private Value val;         // associated data
        private Node left, right;  // left and right subtrees
        private int size;          // number of nodes in subtree

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

2、遍历

  • 前序遍历:根结点 —> 左子树 —> 右子树
  • 中序遍历:左子树—> 根结点 —> 右子树
  • 后序遍历:左子树 —> 右子树 —> 根结点
    public void print(Node x){
        if(x==null)return;
        print(x.left);
        Klog.e(x.key);
        print(x.right);
    }
  • 先序遍历:在第一次遍历到节点时就执行操作,一般只是想遍历执行操作(或输出结果)可选用先序遍历;
  • 中序遍历:对于二分搜索树,中序遍历的操作顺序(或输出结果顺序)是符合从小到大(或从大到小)顺序的,故要遍历输出排序好的结果需要使用中序遍历后序遍历:
  • 后续遍历:是执行操作时,肯定已经遍历过该节点的左右子节点,故适用于要进行破坏性操作的情况,比如删除所有节点
    这里我总是记忆不住具体的…

联想记忆:前序就是正规的遍历顺序 根左右 中序就是把左往前移,后序就是再把右往移

3、添加元素

要插入节点,必须先找到插入的位置。
与查找操作相似,由于二叉搜索树的特殊性,待插入的节点也需要从根节点开始进行比较,小于根节点则与根节点左子树比较
反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置,在比较的过程中要注意保存父节点的信息 及 待插入的位置是父节点的左子树还是右子树,才能插入到正确的位置

    public void put(Key key,Value val){
        if (key == null) throw new IllegalArgumentException("calls put() with a null key");
        if (val == null) {
            delete(key);
            return;
        }
        //查找key,找到则更新他的值,否则为它创建一个新的节点
        root = put(root, key, val);
    }
    private Node put(Node x, Key key, Value val) {
        //如果x为空就新建节点存放key和value,如果不为空就继续对比下去,直到为空,
        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.size = 1 + size(x.left) + size(x.right);
        return x;
    }

这里是根据key 排序注意了
在put的时候,就已经在按照规则排序了,按照文章老规矩会说删除,但是删除是二叉树最难得部分,我们慢慢来

4、查找最大值和最小值

    public Key max() {
        if (isEmpty()) throw new NoSuchElementException("calls max() with empty symbol table");
        return max(root).key;
    }

    private Node max(Node x) {
        if (x.right == null) return x;
        else                 return max(x.right);
    }

    public Key min() {
        if (isEmpty()) throw new NoSuchElementException("calls min() with empty symbol table");
        return min(root).key;
    }

    /**
     * 该节点下最小子节点
     * @param x
     * @return
     */
    public Node min(Node x) {
        if (x.left == null) return x;
        else return min(x.left);
    }

其实按照左节点一定比父节点小,右节点一定比父节点大,就可以想到 一直递归下去就行了

5、删除

5.1、热身运动:删除最大键和最小键
    public void deleteMin() {
        if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
        root = deleteMin(root);
    }
    /**
     * 不断深入根节点的左子树,直到遇到该节点的左节点是一个空节点,然后就把指向该节点链接指向该节点右节点
     * @param x
     * @return
     */
    private Node  deleteMin(Node x) {
        if (x.left == null) return x.right;
        x.left = deleteMin(x.left);
        x.size = size(x.left) + size(x.right) + 1;
        return x;
    }

deleteMax 基本就是deleteMin 差不多 一个就是寻找右节点,一个是寻找左节点
热身结束~

5.2、核心思路:
  • 将指向即将被删除的节点的链接保存为t
  • 将x指向它的后继节点min(t.right)
  • 将x的右链接(原本指向一颗所有节点都大于x.key的二叉树)指向deleteMin(t.right)也就是在删除后所有节点仍然大于x.key的二叉查找树
  • 将x的左链接(本为空)设为t.left(其下所有键都小于被删除的节点和他的后继节点)
 public void delete(Key key) {
        if (key == null) throw new IllegalArgumentException("calls delete() with a null key");
        root = delete(root, key);
    }

    public Node delete(Node x, Key key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0) x.left = delete(x.left, key);//如果小就继续去找他的左节点
        else if (cmp > 0) x.right = delete(x.right, key);//如果大就继续去找他的右节点
        else{
            if(x.left==null)return x.right;
            if(x.right==null)return x.left;
            Node t = x;
            x = min(t.right);
            x.right = deleteMin(t.right);
            x.left = t.left;
        }
        x.size = size(x.left) + size(x.right) + 1;
        return x;
    }

如图所示:
mark
仔细结合图体会代码

6、二叉树的效率

从前面的大部分对树的操作来看,都需要从根节点到下一层一层的查找。

一颗满树,每层节点数大概为2n-1,那么最底层的节点个数比树的其它节点数多1,因此,查找、插入或删除节点的操作大约有一半都需要找到底层的节点,另外四分之一的节点在倒数第二层,依次类推。

总共N层共有2n-1个节点,那么时间复杂度为O(logn),底数为2。

在有1000000 个数据项的无序数组和链表中,查找数据项平均会比较500000 次,但是在有1000000个节点的二叉树中,只需要20次或更少的比较即可。

有序数组可以很快的找到数据项,但是插入数据项的平均需要移动 500000 次数据项,在 1000000 个节点的二叉树中插入数据项需要20次或更少比较,在加上很短的时间来连接数据项。

同样,从 1000000 个数据项的数组中删除一个数据项平均需要移动 500000 个数据项,而在 1000000 个节点的二叉树中删除节点只需要20次或更少的次数来找到他,然后在花一点时间来找到它的后继节点,一点时间来断开节点以及连接后继节点。

所以,树对所有常用数据结构的操作都有很高的效率。

遍历可能不如其他操作快,但是在大型数据库中,遍历是很少使用的操作,它更常用于程序中的辅助算法来解析算术或其它表达式。

7、全部代码

下面是用Java实现二叉树

public class BST<Key extends Comparable<Key>, Value> {compareTo
    private Node root;             // root of BST

    private class Node {
        private Key key;           // sorted by key
        private Value val;         // associated data
        private Node left, right;  // left and right subtrees
        private int size;          // number of nodes in subtree

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

    public BST() {
    }


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


    public Value get(Node root, Key key) {
        if (key == null) throw new IllegalArgumentException("calls get() with a null key");
        if (key == null) return null;
        int compareTo = key.compareTo(root.key);
        // 如果指定的数小于参数返回 -1。
        if (compareTo < 0) return get(root.left, key);
            // 如果指定的数大于参数返回 1。
        else if (compareTo > 0) return get(root.right, key);
        else return root.val;
    }
    public void put(Key key,Value val){
        if (key == null) throw new IllegalArgumentException("calls put() with a null key");
        if (val == null) {
            delete(key);
            return;
        }
        root = put(root, key, val);
    }
    private Node put(Node x, Key key, Value 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.size = 1 + size(x.left) + size(x.right);
        return x;
    }
    public void delete(Key key) {
        if (key == null) throw new IllegalArgumentException("calls delete() with a null key");
        root = delete(root, key);
    }

    public Node delete(Node x, Key key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0) x.left = delete(x.left, key);
        else if (cmp > 0) x.right = delete(x.right, key);
        else{
            if(x.left==null)return x.right;
            if(x.right==null)return x.left;
            Node t = x;
            x = min(t.right);
            x.right = deleteMin(t.right);
            x.left = t.left;
        }
        x.size = size(x.left) + size(x.right) + 1;
        return x;
    }
    public void deleteMin() {
        if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
        root = deleteMin(root);
    }

    public Iterable<Key> keys() {
        if (isEmpty()) return new Queue<Key>() {
        };
        return keys(min(), max());
    }
    public Iterable<Key> keys(Key lo, Key hi) {
        if (lo == null) throw new IllegalArgumentException("first argument to keys() is null");
        if (hi == null) throw new IllegalArgumentException("second argument to keys() is null");

        Queue<Key> queue = new Queue<Key>();
        keys(root, queue, lo, hi);
        return queue;
    }
    private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
        if (x == null) return;
        int cmplo = lo.compareTo(x.key);
        int cmphi = hi.compareTo(x.key);
        if (cmplo < 0) keys(x.left, queue, lo, hi);
        if (cmplo <= 0 && cmphi >= 0) queue.enqueue(x.key);
        if (cmphi > 0) keys(x.right, queue, lo, hi);
    }

    /**
     * 不断深入根节点的左子树,直到遇到该节点的左节点是一个空节点,然后就把指向该节点链接指向该节点右节点
     * @param x
     * @return
     */
    private Node  deleteMin(Node x) {
        if (x.left == null) return x.right;
        x.left = deleteMin(x.left);
        x.size = size(x.left) + size(x.right) + 1;
        return x;
    }

    public Key min() {
        if (isEmpty()) throw new NoSuchElementException("calls min() with empty symbol table");
        return min(root).key;
    }

    /**
     * 该节点下最小子节点
     * @param x
     * @return
     */
    public Node min(Node x) {
        if (x.left == null) return x;
        else return min(x.left);
    }

    public boolean isEmpty() {
        return size() == 0;
    }

    public void deleteMax() {
        if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
        root = deleteMax(root);
    }

    private Node deleteMax(Node x) {
        if (x.right == null) return x.left;
        x.right = deleteMax(x.right);
        x.size = size(x.left) + size(x.right) + 1;
        return x;
    }
    public Key max() {
        if (isEmpty()) throw new NoSuchElementException("calls max() with empty symbol table");
        return max(root).key;
    }

    private Node max(Node x) {
        if (x.right == null) return x;
        else                 return max(x.right);
    }

    public int rank(Key key) {
        if (key == null) throw new IllegalArgumentException("argument to rank() is null");
        return rank(key, root);
    }

    public int rank(Key key, Node x) {
        if (x == null) return 0;
        int cmp = key.compareTo(x.key);
        if (cmp < 0) return rank(key, x.left);
        else if (cmp > 0) return 1 + size(x.left) + rank(key, x.right);
        else return size(x.left);
    }

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

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

    public int size(TreeNode root) {
        return root.N;
    }
    public boolean contains(Key key) {
        if (key == null) throw new IllegalArgumentException("argument to contains() is null");
        return get(key) != null;
    }

    /**
     前序遍历:根结点 ---> 左子树 ---> 右子树
     中序遍历:左子树---> 根结点 ---> 右子树
     后序遍历:左子树 ---> 右子树 ---> 根结点
     * @param x
     */
    public void print(Node x){
        if(x==null)return;
        print(x.left);
        Klog.e(x.key);
        print(x.right);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值