二叉树入门-java语言版

161 阅读4分钟

树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家 谱、单位的组织架构、等等。 树是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就 是说它是根朝上,而叶朝下的。

树.png

树具有以下特点:
    1.每个结点有零个或多个子结点; 
    2.没有父结点的结点为根结点; 
    3.每一个非根结点只有一个父结点; 
    4.每个结点及其后代结点整体上可以看做是一棵树,称为当前结点的父结点的一个子树;
    

-- 树的相关术语:

    结点的度: 一个结点含有的子树的个数称为该结点的度;
    叶结点: 度为0的结点称为叶结点,也可以叫做终端结点
    分支结点: 度不为0的结点称为分支结点,也可以叫做非终端结点
    结点的层次: 从根结点开始,根结点的层次为1,根的直接后继层次为2,以此类推
    结点的层序编号:将树中的结点,按照从上层到下层,同层从左到右的次序排成一个线性序列,把他们编成连续的自然数。
    树的度: 树中所有结点的度的最大值
    树的高度(深度): 树中结点的最大层次
    森林: m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根 结点,森林就变成一棵树
    孩子结点: 一个结点的直接后继结点称为该结点的孩子结点
    双亲结点(父结点): 一个结点的直接前驱称为该结点的双亲结点
    兄弟结点: 同一双亲结点的孩子结点间互称兄弟结点

-- 二叉树的基本定义:

二叉树基本定义.png

满二叉树.png 完全二叉树: 叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

完全二叉树.png

-- 二叉树结点类API设计:

二叉树结点类.png

-- 代码 :

/**
 * 二叉树
 */
public class BinaryTree<Key extends Comparable<Key>,Value> {
    // 记录根结点
    private Node root;
    // 记录树中的元素个数
    private int N;

    // 构造方法
    public BinaryTree() {
        root = null;
        N = 0;
    }

    // 获取树中的元素个数
    public int size() {
        return N;
    }

    // 向树中添加key-value
    public void put(Key key,Value value) {
        root = put(root,key,value);
    }

    // 向指定树x中添加key-value,并返回添加元素后新的树
    private Node put(Node x,Key key,Value value) {
        if(x == null) { // 当树中还没有任何结点
            // 个数+1
            N++;
            // 构建新结点并返回
            return new Node(key,value,null,null);
        }
        // 记录key和当前结点key的比较结果
        int cmp = key.compareTo(x.key);
        if(cmp > 0) {
            // 如果新节点的key大于当前结点的key,继续找当前结点的右孩子
            x.right = put(x.right,key,value);
        }else if(cmp < 0) {
            // 如果新节点的key小于当前结点的key,继续找当前结点的左孩子
            x.left = put(x.left,key,value);
        }else {
            // 新结点的key等于当前结点的key,把当前结点value进行替换
            x.value = value;
        }
        return x;
    }

    // 查询树中指定key对应的value
    public Value get(Key key) {
        return get(root,key);
    }

    // 从指定的树x中,查询key对应的value
    public Value get(Node x,Key key) {
        if(x == null) {
            return null;
        }
        // 记录key和当前结点key的比较结果
        int cmp = key.compareTo(x.key);
        if(cmp > 0) {
            // 如果当前要查询的key大于当前结点的key,则继续查找当前结点的右子结点
            return get(x.right,key);
        }else if(cmp < 0) {
            // 如果当前要查询的key小于当前结点的key,则继续查找当前结点的左子节点
            return get(x.left,key);
        }else {
            // 如果要查询的key等于当前结点的key,则返回当前结点的value
            return x.value;
        }
    }

    // 删除树中key对应的value
    public void delete(Key key) {
        root = delete(root,key);
    }

    // 删除指定树x中的key对应的value,并返回删除后的新树
    public Node delete(Node x,Key key) {
        if(x == null) {
            return null;
        }
        // 记录key和当前结点key的比较结果
        int cmp = key.compareTo(x.key);
        if(cmp > 0) {
            // 如果新节点的key大于当前结点的key,继续找当前结点的右子结点
            x.right = delete(x.right,key);
        }else if(cmp < 0) {
            // 如果新节点的key小于当前结点的key,继续找当前结点的左子结点
            x.left = delete(x.left,key);
        }else {
            // 如果新结点的key等于当前结点的key,当前结点就是要删除的结点

            // 如果当前结点的右子树不存在,则直接返回当前结点的左子树
            if(x.right == null) {
                // 个数-1
                N--;
                return x.left;
            }
            // 如果当前结点的左子树不存在,则直接返回当前结点的右子树
            if(x.left == null) {
                // 个数-1
                N--;
                return x.right;
            }
            // 如果当前结点的左右子树都存在
            // 找到右子树中最小的结点
            Node minNode = x.right;
            while (minNode.left != null) {
                minNode = minNode.left;
            }
            // 删除右子树中最小的结点
            Node n = x.right;
            while (n.left != null) {
                if(n.left.left == null) {
                    n.left = null;
                }else {
                    n = n.left;
                }
            }
            // 让被删除结点的左子树成为minNode的左子树
            minNode.left = x.left;
            // 让被删除结点的右子树成为minNode的右子树
            minNode.right = x.right;
            // 让被删除结点指向最小结点minNode
            x = minNode;
            // 个数-1
            N--;
        }
        return x;
    }

    // 找出整个树中最小的键
    public Key min() {
        return min(root).key;
    }

    // 找出指定树x中最小的键所在的结点
    private Node min(Node x) {
        if(x.left != null) {
            // 如果当前结点的左子结点不为空,则继续找当前结点的左子结点
            return min(x.left);
        }else {
            return x;
        }
    }

    // 找出整个树中最大的键
    public Key max() {
        return max(root).key;
    }

    // 找出指定树x中最大的键所在的结点
    private Node max(Node x) {
        if(x.right != null) {
            // 如果当前结点的右子结点不为空,则继续找当前结点的右子结点
            return max(x.right);
        }else {
            return x;
        }
    }

    // 使用前序遍历,获取整个树中所有的键
    public Queue<Key> preErgodic() {
        Queue<Key> keys = new Queue<>();
        preErgodic(root,keys);
        return keys;
    }

    // 前序遍历顺序:先访问根结点,然后再访问左子树,最后访问右子树
    // 使用前序遍历,把指定树x中的所有键放入keys队列中
    private void preErgodic(Node x,Queue<Key> keys) {
        if(x == null) {
            return;
        }

        // 把当前结点的key放入队列中
        keys.enqueue(x.key);
        // 找到当前结点的左子树,如果不为空,递归遍历左子树
        if(x.left != null) {
            preErgodic(x.left,keys);
        }
        // 找到当前结点的右子树,如果不为空,递归遍历右子树
        if(x.right != null) {
            preErgodic(x.right,keys);
        }
    }

    // 使用中序遍历,获取整个树中的所有键
    public Queue<Key> midErgodic() {
        Queue<Key> keys = new Queue<>();
        midErgodic(root,keys);
        return keys;
    }

    // 中序遍历顺序:先访问左子树,中间访问根节点,最后访问右子树
    // 使用中序遍历,把指定树x中的所有键放入keys队列中
    public void midErgodic(Node x,Queue<Key> keys) {
        if(x == null) {
            return;
        }
        // 找到当前结点的左子树,如果不为空,递归遍历左子树
        if(x.left != null) {
            midErgodic(x.left,keys);
        }
        // 把当前结点的key放入队列中
        keys.enqueue(x.key);
        // 找到当前结点的右子树,如果不为空,递归遍历右子树
        if(x.right != null) {
            midErgodic(x.right,keys);
        }
    }

    // 使用后序遍历,获取整个树中的所有键
    public Queue<Key> afterErgodic() {
        Queue<Key> keys = new Queue<>();
        afterErgodic(root,keys);
        return keys;
    }

    // 后序遍历顺序:先访问左子树,再访问右子树,最后访问根节点
    // 使用后序遍历,把指定树x中的所有键放入到keys队列中
    private void afterErgodic(Node x,Queue<Key> keys) {
        if(x == null) {
            return;
        }
        // 找到当前结点的左子树,如果不为空,递归遍历左子树
        if(x.left != null) {
            afterErgodic(x.left,keys);
        }
        // 找到当前结点的右子树,如果不为空,递归遍历右子树
        if(x.right != null) {
            afterErgodic(x.right,keys);
        }
        // 把当前结点的key放入队列中
        keys.enqueue(x.key);
    }

    //使用层序遍历得到树中所有的键
    public Queue<Key> layerErgodic() {
        Queue<Key> keys = new Queue<>();
        Queue<Node> nodes = new Queue<>();
        nodes.enqueue(root);
        while (!nodes.isEmpty()) {
            Node x = nodes.dequeue();
            keys.enqueue(x.key);
            if (x.left != null) {
                nodes.enqueue(x.left);
            }
            if (x.right != null) {
                nodes.enqueue(x.right);
            }
        }
        return keys;
    }

    //计算整个树的最大深度
    public int maxDepth() {
        return maxDepth(root);
    }

    private int maxDepth(Node x) {
        if(x == null) {
            return 0;
        }else {
            int leftHeight = maxDepth(x.left);
            int rightHeight = maxDepth(x.right);
            return Math.max(leftHeight, rightHeight) + 1;
        }
    }

    // 结点类
    private class Node {
        // 存储Key
        private Key key;
        // 存储Value
        private Value value;
        // 记录子结点
        private Node left;
        // 记录右孩子
        private Node right;

        // 构造方法
        public Node(Key key, Value value, Node left, Node right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }
}

@ 以上内容属于个人笔记