数据结构 - 二叉树

120 阅读4分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

树(Tree)是一种很有趣的数据结构,它既能像链表那样快速的插入和删除,又能像有序数组那样快速查找。树的种类很多,本节将记录一种特殊的树————二叉树(Binary Tree)。二叉树的每个节点最多只能有两个子节点,通常称为左子节点和右子节点。如果一个二叉树的每个节点的左子节点的关键字值小于该节点,右子节点的关键字值大于等于该节点,那么这种二叉树也称为二叉搜索树(Binary Search Tree,BST),本节主要关注BST。

相关术语

查看一个BST例子:

2020年12月30日10-01-34

  • 路径:从一个节点走到另一个节点,经过的节点顺序就称为路径;
  • 根:树的顶端节点称为根,一个数只能有一个根节点,并且从根节点到任意子节点只能有一条路径;
  • 父节点:每个节点(除了根)都有一条边向上连接到另一个节点,这个节点就是下面节点的父节点;
  • 子节点:每个节点(除了叶子节点)都有一条或两条边向下连接其他节点,下面这些节点就是当前节点的子节点。子节点分为左子节点和右子节点;
  • 叶节点:没有子节点的节点称为叶子节点,或叶节点;
  • 关键字:节点中的数据,比如上图中的数值。

操作BST

在操作BST前,我们先用代码定义一个BST的骨架:

/** BST */
public class BinarySearchTree {

   /** 根节点 */
    private Node root;
    /** 节点 */
    static class Node {
        /** 关键字 */
        int key;
        /** 额外携带的数据 */
        String value;
        /** 左子节点 */
        Node leftChild;
        /** 右子节点 */
        Node rightChild;

        public Node(int key, String value) {
            this.key = key;
            this.value = value;
        }
    }
}

下面的这些操作都以这个BST为例:

QQ20201230-102608@2x

插入

假如我们需要插入一个key为88的节点,需要经过如下步骤:

  1. 从根节点出发,88比72大,所以走右子节点82路径;
  2. 88比82大,所以走右子节点90路径;
  3. 88比90小,所以走左子节点87路径;
  4. 88比87大,并且87的右子节点为空,所以我们最终把88作为87的右子节点插入树中。

当key重复时,可以选择覆盖或者忽略,这由业务决定。

上述过程动态图如下所示:

2020-12-30 13.53.58.gif

Java代码实现如下:

/** BST */
public class BinarySearchTree {

   /** 根节点 */
    private Node root;
    /** 节点 */
    static class Node {
        /** 关键字 */
        int key;
        /** 额外携带的数据 */
        String value;
        /** 左子节点 */
        Node leftChild;
        /** 右子节点 */
        Node rightChild;

        public Node(int key, String value) {
            this.key = key;
            this.value = value;
        }
    }

    /** 插入 */
    public void insert(int key, String value) {
        // 创建一个新节点
        Node newNode = new Node(key, value);
        if (this.root == null) {
            // 如果根为null,则这个新节点就是根
            root = newNode;
        } else {
            // 如果跟不为null,则从根开始搜索插入位置
            Node currentNode = root;
            // 用于暂存父节点
            Node parentNode;
            while (true) {
                // 父节点设置为当前节点
                parentNode = currentNode;
                if (key < currentNode.key) {
                    currentNode = currentNode.leftChild;
                    if (currentNode == null) {
                        // 如果key小于当前节点key,并且当前节点的左子节点为空,则将新节点
                        // 设置为当前节点(父节点暂存对象)的左子节点,退出循环
                        parentNode.leftChild = newNode;
                        return;
                    }
                } else if (key > currentNode.key) {
                    currentNode = currentNode.rightChild;
                    if (currentNode == null) {
                        // 如果key大于当前节点key,并且当前节点的右子节点为空,则将新节点
                        // 设置为当前节点(父节点暂存对象)的又子节点,退出循环
                        parentNode.rightChild = newNode;
                        return;
                    }
                } else {
                    // 如果key等于当前节点key,则将value覆盖当前节点value
                    currentNode.value = newNode.value;
                    return;
                }
            }
        }
    }
}

编写测试程序:

public class BinarySearchTreeTest {

    public static void main(String[] args) {
        BinarySearchTree bst = new BinarySearchTree();
        Arrays.asList(72, 57, 82, 30, 63, 79, 90, 27, 40, 62, 67, 80, 87, 48).forEach(key -> {
            String value = "我是key为" + key + "的value";
            bst.insert(key, value);
        });
    }
}

以debug的方式运行程序,查看bst结构:

QQ20201230-140411@2x bst结构和上图一致,有兴趣可以自己验证。

查找

假如我们需要查找key为67的节点,需要经过如下步骤:

  1. 从根节点出发,67比72小,所以走左子节点57路径;
  2. 67比57大,所以走右子节点63路径;
  3. 67比63大,所以走右子节点67路径;
  4. 67等于67,找到目标节点,退出;
  5. 如果搜索直到叶子节点都没找到,则返回空。

上述过程动态图如下所示:

2020-12-30 10.39.41.gif

Java代码实现如下:

/** BST */
public class BinarySearchTree {

   /** 根节点 */
    private Node root;
    /** 节点 */
    static class Node {
        /** 关键字 */
        int key;
        /** 额外携带的数据 */
        String value;
        /** 左子节点 */
        Node leftChild;
        /** 右子节点 */
        Node rightChild;

        public Node(int key, String value) {
            this.key = key;
            this.value = value;
        }
    }

    /** 查找 */
    public Node find(int key) {
        // 从根节点开始查找
        Node currentNode = root;
        // 当前节点的key不等于被查找的key时
        while (currentNode.key != key) {
            if (key < currentNode.key) {
                // 如果key值小于当前节点key,则查找左子节点
                currentNode = currentNode.leftChild;
            } else {
                // 如果key值大于等于当前节点key,则查找右子节点
                currentNode = currentNode.rightChild;
            }
            // 如果当前节点为null,说明查到叶子节点了,仍没查到目标key,则直接返回null
            if (currentNode == null) {
                return null;
            }
        }
        // 返回当前节点(退出while循环要么key相等,要么没找到,结果为null)
        return currentNode;
    }
}

编写测试程序:

public class BinarySearchTreeTest {

    public static void main(String[] args) {
        BinarySearchTree bst = new BinarySearchTree();
        Arrays.asList(72, 57, 82, 30, 63, 79, 90, 27, 40, 62, 67, 80, 87, 48).forEach(key -> {
            String value = "我是key为" + key + "的value";
            bst.insert(key, value);
        });
        System.out.println(bst.find(87).value);
        bst.insert(87, "hello world");
        System.out.println(bst.find(87).value);
    }
}

输出如下: 我是key为87的value hello world