算法与数据结构(五):树之二叉树

算法与数据结构(五):树之二叉树

  • 博主会对算法与数据结构会不断进行更新,敬请期待,如有什么建议,欢迎联系。

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

  • 树的相关术语:

    结点的度:
    一个结点含有的子树的个数称为该结点的度;
    叶结点:
    度为0的结点称为叶结点,也可以叫做终端结点
    分支结点:
    度不为0的结点称为分支结点,也可以叫做非终端结点
    结点的层次:
    从根结点开始,根结点的层次为1,根的直接后继层次为2,以此类推
    结点的层序编号:

    将树中的结点,按照从上层到下层,同层从左到右的次序排成一个线性序列,把他们编成连续的自然数。
    树的度:

    树中所有结点的度的最大值
    树的高度(深度)

    树中结点的最大层次
    森林:

    m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根结点,森林就变成一棵树

    孩子结点:
    一个结点的直接后继结点称为该结点的孩子结点
    双亲结点(父结点):
    一个结点的直接前驱称为该结点的双亲结点
    兄弟结点:
    同一双亲结点的孩子结点间互称兄弟结点

二叉树的实现细节:

  • 二叉树是树结构中特殊的一种树,二叉树的每个节点最多有2个子节点,是数据结构中使用最为普遍的一种数据结构
  • 其中二叉树的遍历,博主实现了前序遍历,中序遍历,后序遍历和层序遍历
  • 实现了树最大深度的计算,此处使用了队列方面的知识,具体实现思路感觉是比较重要的一种算法思想,值得深思。

二叉树的实现代码如下:

package com.victor.tree;

import com.victor.linear.Queue;

/**
 * @description: 二叉树
 * @author: victor
 */
public class BinaryTree<K extends Comparable<K>, V> {
    //根节点
    private Node root;
    //树中元素的个数
    private int N;

    /**
     * 内部类Node节点
     */
    private class Node {
        public K key;
        public V value;
        public Node left;
        public Node right;

        public Node(K key, V value, Node left, Node right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

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

    /**
     * 向树种添加元素
     */
    public void put(K key, V value) {
        root = put(root, key, value);
    }

    /**
     * 向指定的树中添加元素
     *
     * @param x     指定的子树
     * @param key   键
     * @param value 值
     * @return 插入后的新树
     */
    private Node put(Node x, K key, V value) {
        //根节点为null时
        if (x == null) {
            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的值,替换当前节点
            x.value = value;
        }
        //返回子树
        return x;
    }

    /**
     * 通过key获取value
     *
     * @param key 键
     * @return 值
     */
    public V get(K key) {
        return get(root, key);
    }

    /**
     * 查找子树x,通过key获取value
     *
     * @param x   子树
     * @param key 键
     * @return 值
     */
    private V get(Node x, K key) {
        //如果子树为null,则返回null
        if (x == null) {
            return null;
        }
        //key与根节点的key进行对比
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
            //key的值大于根节点key的值,获得右边子树key的值
            return get(x.right, key);
        } else if (cmp < 0) {
            //key的值小于根节点key的值,获得左边子树key的值
            return get(x.left, key);
        } else {
            //key的值等于根节点key的值,直接返回value
            return x.value;
        }

    }

    /**
     * 通过key删除
     *
     * @param key 键
     */
    public void delete(K key) {
        delete(root, key);
    }

    /**
     * 删除子树x中的键值对
     *
     * @param x   子树x
     * @param key 键
     * @return 删除后的新树
     */
    private Node delete(Node x, K key) {
        //子树为null时返回null
        if (x == null) {
            return null;
        }
        //key与根节点的key进行对比
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
            //key的值大于根节点key的值,将删除key-value后的子树变成x的右子树
            x.right = delete(x.right, key);
        } else if (cmp < 0) {
            //key的值小于根节点key的值,将删除key-value后的子树变成x的左子树
            x.left = delete(x.left, key);
        } else {
            N--; //先让树的值--,这样不用每个if都要写了
            //key的值等于根节点key的值,删除当前子树的根节点

            //子树根节点没有左子树时
            if (x.left == null) {
                return x.right;
            }
            //子树根节点没有右子树时
            if (x.right == null) {
                return x.left;
            }

            //找到右子树最小的值(或者找到左子树最大的值)
            Node minNode = x.right;
            Node fatherMinNode = x;  //最小值的父节点
            while (minNode.left != null) {
                fatherMinNode = minNode;
                minNode = minNode.left;
            }
            //让fatherMinNode的左节点为null
            fatherMinNode.left = null;
            //让x节点的左子树为minNode的左子树
            minNode.left = x.left;
            //让x节点的右子树为minNode的右子树
            minNode.right = x.right;
            //将x子树变成minNode子树
            x = minNode;
        }
        return x;
    }

    /**
     * <p>
     * 查询树中最小的key
     * </p>
     * 2*
     *
     * @return 键
     */
    public K min() {        //获取最小的key,还可以用递归
        Node minKeyNode = root;
        while (minKeyNode.left != null) {
            minKeyNode = minKeyNode.left;
        }
        return minKeyNode.key;
    }

    /**
     * 查询树中最大的key
     *
     * @return 键
     */
    public K max() {
        Node maxKeyNode = root;
        while (maxKeyNode.right != null) {
            maxKeyNode = maxKeyNode.right;
        }
        return maxKeyNode.key;
    }

    /**
     * 前序遍历
     *
     * @return 包含key的queue
     */
    public Queue<K> preErgodic() {
        Queue<K> keys = new Queue<>();
        preErgodic(root, keys);
        return keys;
    }

    /**
     * 获取指定x树所有的键,放到queue中
     *
     * @param x    x树
     * @param keys queue
     */
    private void preErgodic(Node x, Queue<K> keys) {
        //如果x为null则直接返回
        if (x == null) return;
        //把x节点放到keys中
        keys.enqueue(x.key);
        //遍历左子树
        if (x.left != null) preErgodic(x.left, keys);
        //遍历右子树
        if (x.right != null) preErgodic(x.right, keys);
    }

    /**
     * 中序遍历
     *
     * @return 包含key的queue
     */
    public Queue<K> midErgodic() {
        Queue<K> keys = new Queue<>();
        midErgodic(root, keys);
        return keys;
    }

    /**
     * 获取指定x树所有的键,放到queue中
     *
     * @param x    x树
     * @param keys queue
     */
    private void midErgodic(Node x, Queue<K> keys) {
        //如果x为null则直接返回
        if (x == null) return;
        //遍历左子树
        if (x.left != null) midErgodic(x.left, keys);
        //把x节点放到keys中
        keys.enqueue(x.key);
        //遍历右子树
        if (x.right != null) midErgodic(x.right, keys);
    }

    /**
     * 后序遍历
     *
     * @return 包含key的queue
     */
    public Queue<K> afterErgodic() {
        Queue<K> keys = new Queue<>();
        afterErgodic(root, keys);
        return keys;
    }

    /**
     * 获取指定x树所有的键,放到queue中
     *
     * @param x    x树
     * @param keys queue
     */
    private void afterErgodic(Node x, Queue<K> keys) {
        //如果x为null则直接返回
        if (x == null) return;
        //遍历左子树
        if (x.left != null) afterErgodic(x.left, keys);
        //遍历右子树
        if (x.right != null) afterErgodic(x.right, keys);
        //把x节点放到keys中
        keys.enqueue(x.key);
    }

    /**
     * 层序遍历
     *
     * @return 包含key的queue
     */
    public Queue<K> layerErgodic() {
        //声明两个队列,一个储存key,一个储存Node
        Queue<Node> nodes = new Queue<>();
        Queue<K> keys = new Queue<>();
        nodes.enqueue(root);    //先将根节点放入其中
        while (!nodes.isEmpty()) {
            Node node = nodes.dequeue();    //弹出一个Node,然后根据弹出的Node将弹出的node的左节点,有节点放入队列中
            keys.enqueue(node.key);    //将弹出的Node的key放入keys队列中
            if (node.left != null) nodes.enqueue(node.left);   //将左节点放入队列中
            if (node.right != null) nodes.enqueue(node.right);  //将右节点放入队列中
        }

        return keys;    //返回keys
    }

    /**
     * 获取树的最大深度
     *
     * @return 最大深度
     */
    public int maxDepth() {
        return maxDepth(root);
    }

    /**
     * 子树x的最大深度
     *
     * @param x 子树
     * @return 子树x的最大深度
     */
    private int maxDepth(Node x) {
        if (x == null) return 0;   //递归调用的出口

        int max = 0;    //当前子树最大深度
        int maxLeft = 0;    //当前子树左子树的最大深度
        int maxRight = 0;    //当前子树右子树的最大深度

        if (x.left != null) maxLeft = maxDepth(x.left);     //获得左子树的最大深度
        if (x.right != null) maxRight = maxDepth(x.right);     //获得右子树的最大深度
        max = Math.max(maxLeft, maxRight) + 1;  //当前子树的最大深度
        return max;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值