看動畫學演算法之:二叉搜尋樹BST

flydean發表於2021-12-08

簡介

樹是類似於連結串列的資料結構,和連結串列的線性結構不同的是,樹是具有層次結構的非線性的資料結構。

樹是由很多個節點組成的,每個節點可以指向很多個節點。

如果一個樹中的每個節點都只有0,1,2個子節點的話,這顆樹就被稱為二叉樹,如果我們對二叉樹進行一定的排序。

比如,對於二叉樹中的每個節點,如果左子樹節點的元素都小於根節點,而右子樹的節點的元素都大於根節點,那麼這樣的樹被叫做二叉搜尋樹(Binary Search Tree)簡稱BST。

今天我們來探討一下BST的性質和對BST的基本操作。

BST的基本性質

剛剛我們已經講過BST的基本特徵了,現在我們再來總結一下:

  1. BST中任意節點的左子樹一定要比該節點的值要小
  2. BST中任意節點的右子樹一定要比該節點的值要大
  3. BST中任意節點的左右子樹一定要是一個BST。

看一張圖直觀的感受一下BST:

BST的構建

怎麼用程式碼來構建一個BST呢?

首先,BST是由一個一個的節點Node組成的,Node節點除了儲存節點的資料之外,還需要指向左右兩個子節點,這樣我們的BST完全可以由Node連線而成。

另外我們還需要一個root節點來表示BST的根節點。

相應的程式碼如下:

public class BinarySearchTree {

    //根節點
    Node root;

    class Node {
        int data;
        Node left;
        Node right;

        public Node(int data) {
            this.data = data;
            left = right = null;
        }
    }
}

BST的搜尋

先看下BST的搜尋,如果是上面的BST,我們想搜尋32這個節點應該是什麼樣的步驟呢?

先上圖:

搜尋的基本步驟是:

  1. 從根節點41出發,比較根節點和搜尋值的大小
  2. 如果搜尋值小於節點值,那麼遞迴搜尋左側樹
  3. 如果搜尋值大於節點值,那麼遞迴搜尋右側樹
  4. 如果節點匹配,則直接返回即可。

相應的java程式碼如下:

//搜尋方法,預設從根節點搜尋
    public Node search(int data){
        return search(root,data);
    }

    //遞迴搜尋節點
    private Node search(Node node, int data)
    {
        // 如果節點匹配,則返回節點
        if (node==null || node.data==data)
            return node;

        // 節點資料大於要搜尋的資料,則繼續搜尋左邊節點
        if (node.data > data)
            return search(node.left, data);

        // 如果節點資料小於要搜素的資料,則繼續搜尋右邊節點
        return search(node.right, data);
    }

BST的插入

搜尋講完了,我們再講BST的插入。

先看一個動畫:

上的例子中,我們向BST中插入兩個節點30和55。

插入的邏輯是這樣的:

  1. 從根節點出發,比較節點資料和要插入的資料
  2. 如果要插入的資料小於節點資料,則遞迴左子樹插入
  3. 如果要插入的資料大於節點資料,則遞迴右子樹插入
  4. 如果根節點為空,則插入當前資料作為根節點

相應的java程式碼如下:

// 插入新節點,從根節點開始插入
    public void insert(int data) {
        root = insert(root, data);
    }

    //遞迴插入新節點
    private Node insert(Node node, int data) {

        //如果節點為空,則建立新的節點
        if (node == null) {
            node = new Node(data);
            return node;
        }

        //節點不為空,則進行比較,從而遞迴進行左側插入或者右側插入
        if (data < node.data)
            node.left = insert(node.left, data);
        else if (data > node.data)
            node.right = insert(node.right, data);

        //返回插入後的節點
        return node;
    }

BST的刪除

BST的刪除要比插入複雜一點,因為插入總是插入到葉子節點,而刪除可能刪除的是非葉子節點。

我們先看一個刪除葉子節點的例子:

上面的例子中,我們刪除了30和55這兩個節點。

可以看到,刪除葉子節點是相對簡單的,找到之後刪除即可。

我們再來看一個比較複雜的例子,比如我們要刪除65這個節點:

可以看到需要找到65這個節點的右子樹中最小的那個,替換掉65這個節點即可(當然也可以找到左子樹中最大的那個)。

所以刪除邏輯是這樣的:

  1. 從根節點開始,比較要刪除節點和根節點的大小
  2. 如果要刪除節點比根節點小,則遞迴刪除左子樹
  3. 如果要刪除節點比根節點大,則遞迴刪除右子樹
  4. 如果節點匹配,又有兩種情況
  5. 如果是單邊節點,直接返回節點的另外一邊
  6. 如果是雙邊節點,則先找出右邊最小的值,作為根節點,然後將刪除最小值過後的右邊的節點,作為根節點的右節點

看下程式碼的實現:

 // 刪除新節點,從根節點開始刪除
    void delete(int data)
    {
        root = delete(root, data);
    }

    //遞迴刪除節點
    Node delete(Node node, int data)
    {
        //如果節點為空,直接返回
        if (node == null)  return node;

        //遍歷左右兩邊的節點
        if (data < node.data)
            node.left = delete(node.left, data);
        else if (data > root.data)
            node.right = delete(node.right, data);

        //如果節點匹配
        else
        {
            //如果是單邊節點,直接返回其下面的節點
            if (node.left == null)
                return node.right;
            else if (node.right == null)
                return node.left;

            //如果是雙邊節點,則先找出右邊最小的值,作為根節點,然後將刪除最小值過後的右邊的節點,作為根節點的右節點
            node.data = minValue(node.right);

            // 從右邊刪除最小的節點
            node.right = delete(node.right, node.data);
        }
        return node;
    }

這裡我們使用遞迴來實現的刪除雙邊節點,大家可以考慮一下有沒有其他的方式來刪除呢?

本文的程式碼地址:

learn-algorithm

本文收錄於 www.flydean.com

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章