zoukankan      html  css  js  c++  java
  • 【转载】图解:二叉搜索树算法(BST)

    原文:图解:二叉搜索树算法(BST)

    摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!
    “岁月极美,在于它必然的流逝”
    “春花 秋月 夏日 冬雪”
    — 三毛

    一、树 & 二叉树

    是由节点和边构成,储存元素的集合。节点分根节点、父节点和子节点的概念。
    如图:树深=4; 5是根节点;同样8与3的关系是父子节点关系。

    1
    二叉树binary tree,则加了“二叉”(binary),意思是在树中作区分。每个节点至多有两个子(child),left child & right child。二叉树在很多例子中使用,比如二叉树表示算术表达式。
    如图:1/8是左节点;2/3是右节点;

    2

    二、二叉搜索树 BST

    顾名思义,二叉树上又加了个搜索的限制。其要求:每个节点比其左子树元素大,比其右子树元素小。
    如图:每个节点比它左子树的任意节点大,而且比它右子树的任意节点小

    3

    三、BST Java实现

    直接上代码,对应代码分享在 Github 主页
    BinarySearchTree.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    package org.algorithm.tree;
    /*
     * Copyright [2015] [Jeff Lee]
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
     
    /**
     * 二叉搜索树(BST)实现
     *
     * Created by bysocket on 16/7/7.
     */
    public class BinarySearchTree {
        /**
         * 根节点
         */
        public static TreeNode root;
     
        public BinarySearchTree() {
            this.root = null;
        }
     
        /**
         * 查找
         *      树深(N) O(lgN)
         *      1. 从root节点开始
         *      2. 比当前节点值小,则找其左节点
         *      3. 比当前节点值大,则找其右节点
         *      4. 与当前节点值相等,查找到返回TRUE
         *      5. 查找完毕未找到,
         * @param key
         * @return
         */
        public TreeNode search (int key) {
            TreeNode current = root;
            while (current != null
                    && key != current.value) {
                if (key < current.value )
                    current = current.left;
                else
                    current = current.right;
            }
            return current;
        }
     
        /**
         * 插入
         *      1. 从root节点开始
         *      2. 如果root为空,root为插入值
         *      循环:
         *      3. 如果当前节点值大于插入值,找左节点
         *      4. 如果当前节点值小于插入值,找右节点
         * @param key
         * @return
         */
        public TreeNode insert (int key) {
            // 新增节点
            TreeNode newNode = new TreeNode(key);
            // 当前节点
            TreeNode current = root;
            // 上个节点
            TreeNode parent  = null;
            // 如果根节点为空
            if (current == null) {
                root = newNode;
                return newNode;
            }
            while (true) {
                parent = current;
                if (key < current.value) {
                    current = current.left;
                    if (current == null) {
                        parent.left = newNode;
                        return newNode;
                    }
                } else {
                    current = current.right;
                    if (current == null) {
                        parent.right = newNode;
                        return newNode;
                    }
                }
            }
        }
     
        /**
         * 删除节点
         *      1.找到删除节点
         *      2.如果删除节点左节点为空 , 右节点也为空;
         *      3.如果删除节点只有一个子节点 右节点 或者 左节点
         *      4.如果删除节点左右子节点都不为空
         * @param key
         * @return
         */
        public TreeNode delete (int key) {
            TreeNode parent  = root;
            TreeNode current = root;
            boolean isLeftChild = false;
            // 找到删除节点 及 是否在左子树
            while (current.value != key) {
                parent = current;
                if (current.value > key) {
                    isLeftChild = true;
                    current = current.left;
                } else {
                    isLeftChild = false;
                    current = current.right;
                }
     
                if (current == null) {
                    return current;
                }
            }
     
            // 如果删除节点左节点为空 , 右节点也为空
            if (current.left == null && current.right == null) {
                if (current == root) {
                    root = null;
                }
                // 在左子树
                if (isLeftChild == true) {
                    parent.left = null;
                } else {
                    parent.right = null;
                }
            }
            // 如果删除节点只有一个子节点 右节点 或者 左节点
            else if (current.right == null) {
                if (current == root) {
                    root = current.left;
                } else if (isLeftChild) {
                    parent.left = current.left;
                } else {
                    parent.right = current.left;
                }
     
            }
            else if (current.left == null) {
                if (current == root) {
                    root = current.right;
                } else if (isLeftChild) {
                    parent.left = current.right;
                } else {
                    parent.right = current.right;
                }
            }
            // 如果删除节点左右子节点都不为空
            else if (current.left != null && current.right != null) {
                // 找到删除节点的后继者
                TreeNode successor = getDeleteSuccessor(current);
                if (current == root) {
                    root = successor;
                } else if (isLeftChild) {
                    parent.left = successor;
                } else {
                    parent.right = successor;
                }
                successor.left = current.left;
            }
            return current;
        }
     
        /**
         * 获取删除节点的后继者
         *      删除节点的后继者是在其右节点树种最小的节点
         * @param deleteNode
         * @return
         */
        public TreeNode getDeleteSuccessor(TreeNode deleteNode) {
            // 后继者
            TreeNode successor = null;
            TreeNode successorParent = null;
            TreeNode current = deleteNode.right;
     
            while (current != null) {
                successorParent = successor;
                successor = current;
                current = current.left;
            }
     
            // 检查后继者(不可能有左节点树)是否有右节点树
            // 如果它有右节点树,则替换后继者位置,加到后继者父亲节点的左节点.
            if (successor != deleteNode.right) {
                successorParent.left = successor.right;
                successor.right = deleteNode.right;
            }
     
            return successor;
        }
     
        public void toString(TreeNode root) {
            if (root != null) {
                toString(root.left);
                System.out.print("value = " + root.value + " -> ");
                toString(root.right);
            }
        }
    }
     
    /**
     * 节点
     */
    class TreeNode {
     
        /**
         * 节点值
         */
        int value;
     
        /**
         * 左节点
         */
        TreeNode left;
     
        /**
         * 右节点
         */
        TreeNode right;
     
        public TreeNode(int value) {
            this.value = value;
            left  = null;
            right = null;
        }
    }

    1. 节点数据结构
    首先定义了节点的数据接口,节点分左节点和右节点及本身节点值。如图

    4

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
     * 节点
     */
    class TreeNode {
     
        /**
         * 节点值
         */
        int value;
     
        /**
         * 左节点
         */
        TreeNode left;
     
        /**
         * 右节点
         */
        TreeNode right;
     
        public TreeNode(int value) {
            this.value = value;
            left  = null;
            right = null;
        }
    }

    2. 插入
    插入,和删除一样会引起二叉搜索树的动态变化。插入相对删处理逻辑相对简单些。如图插入的逻辑:

    5
    a. 从root节点开始
    b.如果root为空,root为插入值
    c.循环:
    d.如果当前节点值大于插入值,找左节点
    e.如果当前节点值小于插入值,找右节点
    代码对应:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    /**
     * 插入
     *      1. 从root节点开始
     *      2. 如果root为空,root为插入值
     *      循环:
     *      3. 如果当前节点值大于插入值,找左节点
     *      4. 如果当前节点值小于插入值,找右节点
     * @param key
     * @return
     */
    public TreeNode insert (int key) {
        // 新增节点
        TreeNode newNode = new TreeNode(key);
        // 当前节点
        TreeNode current = root;
        // 上个节点
        TreeNode parent  = null;
        // 如果根节点为空
        if (current == null) {
            root = newNode;
            return newNode;
        }
        while (true) {
            parent = current;
            if (key < current.value) {
                current = current.left;
                if (current == null) {
                    parent.left = newNode;
                    return newNode;
                }
            } else {
                current = current.right;
                if (current == null) {
                    parent.right = newNode;
                    return newNode;
                }
            }
        }
    }

    3.查找

    其算法复杂度 : O(lgN),树深(N)。如图查找逻辑:

    6
    a.从root节点开始
    b.比当前节点值小,则找其左节点
    c.比当前节点值大,则找其右节点
    d.与当前节点值相等,查找到返回TRUE
    e.查找完毕未找到
    代码对应:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
     * 查找
     *      树深(N) O(lgN)
     *      1. 从root节点开始
     *      2. 比当前节点值小,则找其左节点
     *      3. 比当前节点值大,则找其右节点
     *      4. 与当前节点值相等,查找到返回TRUE
     *      5. 查找完毕未找到,
     * @param key
     * @return
     */
    public TreeNode search (int key) {
        TreeNode current = root;
        while (current != null
                && key != current.value) {
            if (key < current.value )
                current = current.left;
            else
                current = current.right;
        }
        return current;
    }

    4. 删除
    首先找到删除节点,其寻找方法:删除节点的后继者是在其右节点树种最小的节点。如图删除对应逻辑:7

    结果为:

    8
    a.找到删除节点
    b.如果删除节点左节点为空 , 右节点也为空;
    c.如果删除节点只有一个子节点 右节点 或者 左节点
    d.如果删除节点左右子节点都不为空
    代码对应见上面完整代码。

    案例测试代码如下,BinarySearchTreeTest.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    package org.algorithm.tree;
    /*
     * Copyright [2015] [Jeff Lee]
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
     
    /**
     * 二叉搜索树(BST)测试案例 {@link BinarySearchTree}
     *
     * Created by bysocket on 16/7/10.
     */
    public class BinarySearchTreeTest {
     
        public static void main(String[] args) {
            BinarySearchTree b = new BinarySearchTree();
            b.insert(3);b.insert(8);b.insert(1);b.insert(4);b.insert(6);
            b.insert(2);b.insert(10);b.insert(9);b.insert(20);b.insert(25);
     
            // 打印二叉树
            b.toString(b.root);
            System.out.println();
     
            // 是否存在节点值10
            TreeNode node01 = b.search(10);
            System.out.println("是否存在节点值为10 => " + node01.value);
            // 是否存在节点值11
            TreeNode node02 = b.search(11);
            System.out.println("是否存在节点值为11 => " + node02);
     
            // 删除节点8
            TreeNode node03 = b.delete(8);
            System.out.println("删除节点8 => " + node03.value);
            b.toString(b.root);
     
     
        }
    }

    运行结果如下:

    1
    2
    3
    4
    5
    value = 1 -> value = 2 -> value = 3 -> value = 4 -> value = 6 -> value = 8 -> value = 9 -> value = 10 -> value = 20 -> value = 25 ->
    是否存在节点值为10 => 10
    是否存在节点值为11 => null
    删除节点8 => 8
    value = 1 -> value = 2 -> value = 3 -> value = 4 -> value = 6 -> value = 9 -> value = 10 -> value = 20 -> value = 25 ->

    四、小结

    与偶尔吃一碗“老坛酸菜牛肉面”一样的味道,品味一个算法,比如BST,的时候,总是那种说不出的味道。

    树,二叉树的概念

    BST算法

    相关代码分享在 Github 主页

  • 相关阅读:
    [Javascript] Data ownership, avoid accidently mutation
    [Next.js] Consume Next.js API routes with the SWR library on the client-side
    [ARIA] Read error message for the focused form field by using aria-describedby
    [ARIA] Group different DOM element in screen reader by using aria-labelledby
    [React] Handle HTTP Errors with React
    [Angular] Modify :before / :after value from Javascirpt
    [Javascript] Finding Sibling Elements
    [Javascript] Finding Parent Elements
    MAC之find与grep
    Android只播放gif动画
  • 原文地址:https://www.cnblogs.com/zhehan54/p/5682712.html
Copyright © 2011-2022 走看看