Java數據結構與算法分析 | 二叉查找樹(BST)

GitHub源碼分享

項目主頁: https://github.com/gozhuyinglong/blog-demos
本文源碼: https://github.com/gozhuyinglong/blog-demos/tree/main/java-data-structures

1. 二叉查找樹(Binary Search Tree)

二叉查找樹又叫二叉排序樹(Binary Sort Tree),或叫二叉搜索樹,簡稱BST。BST是一種節點值之間有次序的二叉樹。其特性是:java

  • 若任意節點的左子樹不空,則左子樹上全部節點的值均小於它的根節點的值;
  • 若任意節點的右子樹不空,則右子樹上全部節點的值均大於或等於它的根節點的值;
  • 任意節點的左、右子樹也分別爲二叉查找樹;

是否二叉查找樹

二叉查找樹相比於其餘數據結構的優點在於查找、插入的時間複雜度較低,爲$O(logN)$。用大$O$符號表示的時間複雜度:node

算法 平均 最差
空間 $O(N)$ $O(N)$
搜索 $O(logN)$ $O(N)$
插入 $O(logN)$ $O(N)$
刪除 $O(logN)$ $O(N)$

2. BST的實現

二叉查找樹要求全部的節點元素都可以排序,因此咱們的Node節點類須要實現Comparable接口,樹中的兩個元素可使用compareTo方法進行比較。
咱們節點中元素的類型爲int型,因此該接口泛型爲Comparable<Integer>,下面是具體實現:git

2.1 節點類

  • element 爲數據元素
  • left 爲左子節點
  • right 爲右子節點
class Node implements Comparable<Integer> {
    private final int element; // 數據元素
    private Node left; // 左子樹
    private Node right; // 右子樹

    private Node(Integer element) {
        this.element = element;
    }

    @Override
    public int compareTo(Integer o) {
        return o.compareTo(element);
    }
}

2.2 二叉查找樹類

  • root 爲樹根,全部的操做均始於此

後面會在該類中增長其餘方法,如添加、查找、刪除等github

class BinarySearchTree {
        private Node root; // 樹根
}

3. 插入節點

向二叉查找樹中插入的節點老是葉子節點,插入過程以下:算法

  1. root爲空,則將插入節點設爲root
  2. 當前元素與插入元素經過compareTo進行比較,若插入元素值小,而且左子節點left爲空,則插入至當前節點左子節點;不然繼續遞歸
  3. 若插入元素值大,且右子節點right爲空,則插入至當前節點右子節點;不然繼續遞歸。
  4. 若插入元素等於當前節點元素,則插入失敗。注:也能夠將其插入到右子節點,我這裏爲了方便直接放棄插入。

具體實現:
BinarySearchTree類中添加兩個方法:數據結構

  • public boolean add(int element) 爲公開方法
  • private boolean add(Node node, int element)爲私有方法,內部遞歸使用
// 添加元素
       public boolean add(int element) {
            if (root == null) {
                root = new Node(element);
                return true;
            }
            return add(root, element);
        }
        // 添加元素(遞歸)
        private boolean add(Node node, int element) {
            if (node.compareTo(element) < 0) {
                if (node.left == null) {
                    node.left = new Node(element);
                    return true;
                } else {
                    return add(node.left, element);
                }
            } else if (node.compareTo(element) > 0) {
                if (node.right == null) {
                    node.right = new Node(element);
                    return true;
                } else {
                    return add(node.right, element);
                }
            } else {
                return false;
            }
        }

4. 查找節點

經過二叉查找樹查找元素,其過程以下:ide

  1. root爲空,則查找失敗
  2. 將當前元素與目標元素對比,若相等則查找成功。
  3. 若不相等,則繼續遞歸查找:若目標值小於當前節點值,則查找左子樹;不然,查找右子樹。

具體實現:
BinarySearchTree類中添加兩個方法:this

  • public Node find(int element) 爲公開方法
  • private Node find(Node node, int element) 爲私有方法,內部遞歸使用
// 查找元素
      public Node find(int element) {
            if (root == null) {
                return null;
            }
            return find(root, element);
        }

        // 查詢元素(遞歸)
        private Node find(Node node, int element) {
            if (node == null) {
                return null;
            }
            int compareResult = node.compareTo(element);
            if (compareResult < 0) {
                return find(node.left, element);
            } else if (compareResult > 0) {
                return find(node.right, element);
            } else {
                return node;
            }
        }

5. 遍歷節點

BST是一個有序二叉樹,經過中序遍歷可順序輸出樹中節點。
中序遍歷過程以下:spa

  1. 遞歸遍歷左子節點
  2. 輸出當前節點
  3. 遞歸遍歷右子節點

具體實現:
BinarySearchTree類中添加兩個方法:code

  • public void orderPrint() 爲公開方法
  • private void orderPrint(Node node) 爲私有方法,內部遞歸使用
// 遍歷節點
      public void orderPrint() {
            orderPrint(root);
        }

        // 遍歷節點(遞歸)
        private void orderPrint(Node node) {

            if (node == null) {
                return;
            }

            // 遞歸左子節點
            if (node.left != null) {
                orderPrint(node.left);
            }

            // 輸出當前節點
            System.out.println(node.element);

            // 遞歸右子節點
            if (node.right != null) {
                orderPrint(node.right);
            }

        }

6. 刪除節點

刪除節點最爲複查,共有三種狀況:

6.1 目標元素爲葉子節點

葉子節點最容易刪除,過程以下:

  1. 找到目標節點的父節點
  2. 判斷目標節點是父節點的左子樹仍是右子樹
  3. 如果左子樹,將父節點的left設爲空;不然將父節點的right設爲空

6.2 目標元素即有左子樹,也有右子樹

該狀況刪除操做最爲複雜,過程以下:

  1. 找到目標節點的父節點
  2. 判斷目標節點是父節點的左子樹仍是右子樹
  3. 找到右子樹中最小元素(葉子節點),將其賦給臨時變量minNode,再將該元素從樹中刪除
  4. 將目標元素的屬性賦予minNode
  5. 若目標元素是父節點的左子樹,將父節點的left設爲minNode;不然將父節點的right設爲minNode

6.3 目標元素只有左子樹,或只有右子樹

刪除過程以下

  1. 找到目標節點的父節點
  2. 判斷目標節點是父節點的左子樹仍是右子樹
  3. 如果左子樹,將父節點的left設爲目標節點不爲空的子樹;不然將父節點的right設爲目標節點不爲空的子樹

具體實現
BinarySearchTree類中添加兩個方法:

  • public boolean remove(int element) 爲公開方法
  • private boolean remove(Node parentNode, Node node, int element)爲私有方法,內部遞歸使用
// 刪除節點
      public boolean remove(int element) {
            if (root == null) {
                return false;
            }
            // 若是刪除的元素是root
            if (root.compareTo(element) == 0) {
                if (root.right == null) {
                    root = root.left;
                } else {
                    root.right.left = root.left;
                    root = root.right;
                }
                return true;
            }
            return remove(null, root, element);
        }

        // 刪除節點(遞歸)
        private boolean remove(Node parentNode, Node node, int element) {
            if (node == null) {
                return false;
            }
            // 先找到目標元素
            int compareResult = node.compareTo(element);
            if (compareResult < 0) {
                return remove(node, node.left, element);
            }
            if (compareResult > 0) {
                return remove(node, node.right, element);
            }

            // 找到目標元素,判斷該節點是父節點的左子樹仍是右子樹
            boolean isLeftOfParent = false;
            if (parentNode.left != null && parentNode.left.compareTo(element) == 0) {
                isLeftOfParent = true;
            }

            // 刪除目標元素
            if (node.left == null && node.right == null) { // (1)目標元素爲葉子節點,直接刪除
                if (isLeftOfParent) {
                    parentNode.left = null;
                } else {
                    parentNode.right = null;
                }
            } else if (node.left != null && node.right != null) { // (2)目標元素即有左子樹,也有右子樹
                // 找到右子樹最小值(葉子節點),並將其刪除
                Node minNode = findMin(node.right);
                remove(minNode.element);
                // 將該最小值替換要刪除的目標節點
                minNode.left = node.left;
                minNode.right = node.right;
                if(isLeftOfParent) {
                    parentNode.left = minNode;
                } else {
                    parentNode.right = minNode;
                }

            } else { // (3)目標元素只有左子樹,或只有右子樹
                if (isLeftOfParent) {
                    parentNode.left = node.left != null ? node.left : node.right;
                } else {
                    parentNode.right = node.left != null ? node.left : node.right;
                }
            }
            return true;
        }
    }

7. 完整代碼

該代碼根據下圖二叉查找樹實現,其操做包括:添加、查找、遍歷、刪除、查詢最小值、查詢最大值。

二叉查找樹

public class BinarySearchTreeDemo {

    public static void main(String[] args) {
        BinarySearchTree tree = new BinarySearchTree();

        System.out.println("----------------------添加元素");
        Integer[] array = {5, 2, 7, 1, 4, 3, 7, 6, 9, 8};
        for (Integer element : array) {
            System.out.printf("添加元素[%s] --> %s\n", element, tree.add(element));
        }

        System.out.println("----------------------順序輸出(中序遍歷)");
        tree.orderPrint();

        System.out.println("----------------------查找元素");
        System.out.println(tree.find(7));

        System.out.println("----------------------查找最小元素");
        System.out.println(tree.findMin());

        System.out.println("----------------------查找最大元素");
        System.out.println(tree.findMax());

        System.out.println("----------------------是否包含元素");
        System.out.println("是否包含[0] --> \t" + tree.contains(0));
        System.out.println("是否包含[2] --> \t" + tree.contains(2));

        System.out.println("----------------------刪除目標元素");
        System.out.println("刪除[0] --> \t" + tree.remove(0));
        tree.orderPrint();
        System.out.println("刪除[1] --> \t" + tree.remove(1));
        tree.orderPrint();
        System.out.println("刪除[4] --> \t" + tree.remove(4));
        tree.orderPrint();
        System.out.println("刪除[7] --> \t" + tree.remove(7));
        tree.orderPrint();

    }

    private static class BinarySearchTree {
        private Node root; // 樹根

        /**
         * 添加元素
         *
         * @param element
         * @return
         */
        public boolean add(int element) {
            if (root == null) {
                root = new Node(element);
                return true;
            }
            return add(root, element);
        }

        /**
         * 添加元素(遞歸)
         *
         * @param node
         * @param element
         * @return
         */
        private boolean add(Node node, int element) {
            if (node.compareTo(element) < 0) {
                if (node.left == null) {
                    node.left = new Node(element);
                    return true;
                } else {
                    return add(node.left, element);
                }
            } else if (node.compareTo(element) > 0) {
                if (node.right == null) {
                    node.right = new Node(element);
                    return true;
                } else {
                    return add(node.right, element);
                }
            } else {
                return false;
            }
        }

        /**
         * 查詢元素
         *
         * @param element
         * @return
         */
        public Node find(int element) {
            if (root == null) {
                return null;
            }
            return find(root, element);
        }

        /**
         * 查詢元素(遞歸)
         *
         * @param node
         * @param element
         * @return
         */
        private Node find(Node node, int element) {
            if (node == null) {
                return null;
            }
            int compareResult = node.compareTo(element);
            if (compareResult < 0) {
                return find(node.left, element);
            } else if (compareResult > 0) {
                return find(node.right, element);
            } else {
                return node;
            }
        }

        /**
         * 查找最大值
         *
         * @return
         */
        public Node findMax() {
            return findMax(root);
        }

        /**
         * 查找最大值(遞歸)
         *
         * @param node
         * @return
         */
        private Node findMax(Node node) {
            if (node.right == null) {
                return node;
            }
            return findMax(node.right);
        }

        /**
         * 查找最小值
         *
         * @return
         */
        private Node findMin() {
            return findMin(root);
        }

        /**
         * 查找最小值(遞歸)
         *
         * @param node
         * @return
         */
        private Node findMin(Node node) {
            if (node.left == null) {
                return node;
            }
            return findMin(node.left);
        }

        /**
         * 順序輸出
         */
        public void orderPrint() {
            orderPrint(root);
        }


        /**
         * 順序輸出(遞歸)
         *
         * @param node
         */
        private void orderPrint(Node node) {

            if (node == null) {
                return;
            }

            // 遞歸左子節點
            if (node.left != null) {
                orderPrint(node.left);
            }

            // 輸出當前節點
            System.out.println(node.element);

            // 遞歸右子節點
            if (node.right != null) {
                orderPrint(node.right);
            }

        }

        /**
         * 是否包含某值
         *
         * @param element
         * @return
         */
        public boolean contains(int element) {
            if (find(element) == null) {
                return false;
            }
            return true;
        }

        /**
         * 刪除目標元素
         *
         * @param element
         * @return
         */
        public boolean remove(int element) {
            if (root == null) {
                return false;
            }
            // 若是刪除的元素是root
            if (root.compareTo(element) == 0) {
                if (root.right == null) {
                    root = root.left;
                } else {
                    root.right.left = root.left;
                    root = root.right;
                }
                return true;
            }
            return remove(null, root, element);
        }

        /**
         * 刪除目標元素(遞歸),有三種狀況:
         * (1)目標元素爲葉子節點
         * (2)目標元素即有左子樹,也有右子樹
         * (3)目標元素只有左子樹,或只有右子樹
         *
         * @param parentNode 當前節點的父節點
         * @param node       當前節點(若當前節點上的元素與要刪除的元素匹配,則刪除當前節點)
         * @param element    要刪除的元素
         * @return
         */
        private boolean remove(Node parentNode, Node node, int element) {
            if (node == null) {
                return false;
            }
            // 先找到目標元素
            int compareResult = node.compareTo(element);
            if (compareResult < 0) {
                return remove(node, node.left, element);
            }
            if (compareResult > 0) {
                return remove(node, node.right, element);
            }

            // 找到目標元素,判斷該節點是父節點的左子樹仍是右子樹
            boolean isLeftOfParent = false;
            if (parentNode.left != null && parentNode.left.compareTo(element) == 0) {
                isLeftOfParent = true;
            }

            // 刪除目標元素
            if (node.left == null && node.right == null) { // (1)目標元素爲葉子節點,直接刪除
                if (isLeftOfParent) {
                    parentNode.left = null;
                } else {
                    parentNode.right = null;
                }
            } else if (node.left != null && node.right != null) { // (2)目標元素即有左子樹,也有右子樹
                // 找到右子樹最小值(葉子節點),並將其刪除
                Node minNode = findMin(node.right);
                remove(minNode.element);
                // 將該最小值替換要刪除的目標節點
                minNode.left = node.left;
                minNode.right = node.right;
                if(isLeftOfParent) {
                    parentNode.left = minNode;
                } else {
                    parentNode.right = minNode;
                }

            } else { // (3)目標元素只有左子樹,或只有右子樹
                if (isLeftOfParent) {
                    parentNode.left = node.left != null ? node.left : node.right;
                } else {
                    parentNode.right = node.left != null ? node.left : node.right;
                }
            }
            return true;
        }
    }

    private static class Node implements Comparable<Integer> {
        private final Integer element; // 數據元素
        private Node left; // 左子樹
        private Node right; // 右子樹

        private Node(Integer element) {
            this.element = element;
        }

        @Override
        public int compareTo(Integer o) {
            return o.compareTo(element);
        }

        @Override
        public String toString() {
            return "Node{" +
                    "element=" + element +
                    '}';
        }
    }
}

輸出結果:

----------------------添加元素
添加元素[5] --> true
添加元素[2] --> true
添加元素[7] --> true
添加元素[1] --> true
添加元素[4] --> true
添加元素[3] --> true
添加元素[7] --> false
添加元素[6] --> true
添加元素[9] --> true
添加元素[8] --> true
----------------------順序輸出(中序遍歷)
1
2
3
4
5
6
7
8
9
----------------------查找元素
Node{element=7}
----------------------查找最小元素
Node{element=1}
----------------------查找最大元素
Node{element=9}
----------------------是否包含元素
是否包含[0] -->     false
是否包含[2] -->     true
----------------------刪除目標元素
刪除[0] -->     false
1
2
3
4
5
6
7
8
9
刪除[1] -->     true
2
3
4
5
6
7
8
9
刪除[4] -->     true
2
3
5
6
7
8
9
刪除[7] -->     true
2
3
5
6
8
9
相關文章
相關標籤/搜索