豁出去了,二叉搜索樹最強學習攻略!

二叉搜索樹最強學習攻略

前言

目前咱們公衆號的核心學習目標是:數據結構與算法,關於這塊一直是個重點,這方面的文章也有很多,我爲什麼還要再來一遍呢?兩個目的:

1、我想深入的學習

2、想給大家講明白,讓大家也深入學習

在實際的學習與文章寫作中,我發現這並非易事,所以需要提前告知大家“俺做不到日更😂”,我的準則就是保質不保量🤣,但是會持續更新,這個是必須的!

二叉搜索樹

爲啥要學習二叉搜索樹

今天咱們要說的是二叉搜索樹,在學習這個之前,建議之前還沒有看過我寫的樹和二叉樹的朋友一定要去看看再來!否則你會有點暈乎。

這裏先給大傢伙普及一下,想必有些人不太清楚,我們之前講了什麼是樹,然後接着說了二叉樹,這裏爲啥開始說二叉搜索樹了?

說到樹這個數據結構,其實蠻重要的,你肯定聽說過紅黑樹,因爲jdk1.8中的hashmap就是數組加鏈表加紅黑樹,然後你肯定還聽過AVL樹,是不是聽着就感覺高大上,然後學數據庫MySQL索引的時候,你一定會接觸B樹,對還有B+樹,同樣的高大上(其實還有B-樹)

如果你之前不瞭解,看到這些有種被勸退的感覺,我這裏簡單先給梳理一下,以後咱們都會詳細講解的:

“其實很多高深的東西都是在原有基礎之上發展而來,我們最開始學習樹,瞭解了啥是樹,然後開始對樹做一些規定,然後產生了二叉樹來應用於某些特定的場景,然後我們對二叉樹再給定一些要求,然後產生了二叉搜索樹,在使用二叉搜索樹的過程中發現了一些問題,然後優化優化,再加一些新規定,就產生了AVL樹,AVL樹主要解決的就是二叉搜索樹的平衡問題,然後在AVL樹的基礎上再優化設計,又出來了紅黑樹,也就是說,紅黑樹也是一種平衡二叉搜索樹,主語B樹也是在原有基礎的樹之上發展而來的一種結構,進而有B+和B-”

不知道上面說的那些你明白了不,總結一下就是:

1、這些看起來各式各樣的樹都是一步步發展而來的,我們應該尋根溯源,從最開始的一步步來學習

2、這些都屬於高級數據結構,都有一些特定的應用場景,是我們需要格外注意的

也就是說,我們想要學習AVL樹和紅黑樹這些就要先知道啥是二叉搜索樹,然後你就會接觸二叉搜索樹的平衡問題,自然過渡到紅黑樹這種。

二叉搜索樹是個啥

首先,我們必須搞清楚二叉搜索樹是個啥?第一點,肯定的那就是它一定是樹結構,也就是說二叉搜索樹是基本的基礎數據結構,看清楚了:

二叉搜索樹是基礎數據結構,不是什麼算法

它是在原有二叉樹上增加一些規則從而形成的,也就是本來人家是個二叉樹,然後再給它指定一些規則,加了這些規則之後,它就不同於一般的二叉樹了,人家有了新名字,就叫做二叉搜索樹

那加了人什麼規則呢?二叉搜索樹有如下規定:

  1. 若任意節點的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值;
  2. 若任意節點的右子樹不空,則右子樹上所有節點的值均大於或等於它的根節點的值;
  3. 任意節點的左、右子樹也分別爲二叉查找樹;

不知道你是否看明白了?我來結合圖看看:

在這裏插入圖片描述

這就是一棵二叉搜索樹,然後我們來一次解讀它的三條規則:

1、比如8是根節點,左子樹不爲空,左子樹上所有節點的值均小於8,你看看是不是,也就是說這個8左邊的樹都比它小

2、同樣的,這個8的右子樹上的所有節點的值都比8大

3、然後每個節點的左右子樹也滿足上述1和2兩條

我一般是這樣理解的:

你看啊,我們這裏以每一個節點來敘述對象來說,也就是比左邊的都大,比右邊的都小,這裏要記住我們針對的是某一個節點來說,比如我們拿8這個根節點來說,這個8是不是比它左邊的數都要大,但是比它右邊的數都小

不知道大家明白嗎?這裏還有注意的就是:

這裏說的左邊指的是左子樹上的節點的值

比如這裏的7,千萬別擡槓說“慶哥,慶哥,這個7不是在8的右邊嗎?不信你看”

image-20200323213530703

哈哈,千萬不要這樣想哦。

這就是二叉搜索樹了,看定義,我們覺得其實挺好理解的,另外啊,關於二叉搜索樹的基本概念,你還需要知道這些:

二叉搜索樹,英文名稱是:Binary Search Tree,我們一般就簡稱BST,另外它還有其他的名字,比如二叉排序樹,二叉查找樹,記住這指的都是一樣的,我這裏習慣說成二叉搜索樹。

最後再總結一下,啥是二叉搜索樹:

二叉搜索樹是基本的數據結構,是具有以下性質的二叉樹,當然也可以是空樹:

  1. 若任意節點的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值;
  2. 若任意節點的右子樹不空,則右子樹上所有節點的值均大於或等於它的根節點的值;
  3. 任意節點的左、右子樹也分別爲二叉查找樹;

以上說了這麼多,其實目的就一個,讓你明白什麼是二叉搜索樹,它的概念本身比較簡單,可能剛接觸的話會感覺比較迷糊,如果經過上面的講解,你還是不怎麼明白二叉搜索樹是個什麼玩意,那我建議你再回過頭看一遍,當然,如果你又看了一遍,但是依舊有點迷糊,那我就要承認是我寫的不行,那麼你可以加我的微信找我探討一下,

ps:公衆號底部可以獲取到我的微信。

假如說現在你已經掌握了什麼是二叉搜索樹,那我們接下來看看下一個問題:二叉搜索樹的優勢在哪?

二叉搜索樹的優勢

數據的邏輯機構和物理結構

我們在說二叉搜索樹的時候,需要先來回顧一下之前學習關於數據的邏輯結構和物理結構,我們知道,對於數據的存儲該選擇什麼樣的數據結構,那就要取決於數據的邏輯結構和物理結構

所謂數據的邏輯結構指的就是數據之間具有什麼樣的關係,一般如下:

  1. 一對一(採用線性數據結構)
  2. 一對多(採用樹結構)
  3. 多對多(採用圖結構)

然後我們還需要知道的是,對於某種數據結構來說,我們實現的方式有兩種:

  1. 順序存儲(數組)
  2. 鏈式存儲(鏈表)

也就是說啊,當我們確定了要採用樹結構了,我們下一個要考慮的問題就是看看這個樹結構要採取順序存儲還是鏈式存儲,那這個就要有數據的物理結構來決定了。

所謂的數據的物理結構一般就是指的數據在內存中的存放形式:

  1. 集中存放(順序存儲)
  2. 分散存放(鏈式存儲)

這些知識,你是需要提前知道的。

二叉搜索樹的存儲結構

首先既然是樹,那肯定存放的是具有一對多關係的數據,這個毋庸置疑的,我們就不需要再去考慮數據的邏輯結構這一塊了,也就是說剩下的重點就是要考慮數據結構的存儲結構了,也就是看看是採取順序存儲還是鏈式存儲了,當然了,我們這裏根據數據的物理結構來判斷,可能採取順序存儲,也有可能採取鏈式存儲。

不過這裏你要記住了,二叉搜索樹通常情況下采用的鏈式存儲,也就是使用鏈表的形式,那我們就知道了,鏈表的的話對於更新的操作效率比較高,因爲只需要改動相應指針指向即可,不用進行挪動什麼的(數組),那麼二叉搜索樹採用鏈式存儲自然有這個優點:

所以,對於二叉搜索樹而言,它相比較其他數據結構來說在查找,插入和刪除等效率比較高,時間複雜度爲image-20200324003725959

這裏可能有人會有疑問了,鏈表是在更新操作效率比較高,查找的話不應該是數組更快嗎?鏈表的查找需要依次遍歷,時間複雜度不是O(n)嗎?

確實如此,但是這裏在二叉搜索樹這塊就變了,我們應該還記得二叉搜索樹上的節點數據的特點吧,也就是我們上面方分析的,比左邊的大,比右邊的小,我們查找的時候根節點開始,進行的就是二分查找啊,因爲比當前節點小的數都在左邊,大的都在右邊,而二分查找的時間複雜度爲image-20200324003725959

所以在查找這塊,二叉搜索樹的效率還是可以的。

二叉搜索樹相關操作的時間複雜度

然後我們來看下關於二叉搜搜索樹的相關操作的時間複雜度,這裏有一張圖片挺好的,來自維基百科:

image-20200323224946856

可以看到,對於二叉搜索樹的搜索,插入和刪除來說,時間複雜度都是image-20200324003725959

不過這個都是平均,這裏還有個最差的情況降低到了O(n)的情況,這是怎麼回事呢?我們看下,對於二叉搜索樹來說可能有這樣的情況:

在這裏插入圖片描述

就是比如有個有序的序列,數據大小一次增大或者增小,就比如上面的數據一次變大,這樣實際上就會退化成鏈表了,這個時候搜索,插入和刪除的時間複雜度最差就退化到O(n)了。

那這個時候怎麼辦呢?這就需要進行優化,也就是需要旋轉,像AVL和紅黑樹就解決了這個問題,就是二叉搜索樹的自平衡,這樣就可以把最差的時間複雜度也優化到image-20200324003725959

簡單小結

到了這裏,我們基本上把二叉搜索樹一些基本的概念知識點都介紹的差不多了,當然,可能有遺漏,大家可以留言說一下,我後續還會發文進行補充的。

其實吧,我們再講二叉搜索樹的話,接下來就是講關於二叉搜索樹的實現了,也就是二叉搜索樹如何實現查找啊,增加和刪除啊,重要還有二叉搜索樹的遍歷等等,這些其實就要涉及代碼了,上面說的那些都是停留在二叉搜索樹的概念性認識,就是讓你瞭解二叉搜索樹,接下的重點就是我們需要自己手動實現一下二叉搜索樹,這樣你才能真正的理解這個數據結構。

今天這篇文章我不打算繼續去實現一個完整的二叉搜索樹,不然篇幅太長,我準備後續再寫一篇,主要就是關於二叉樹的實現,還會詳細介紹它的遍歷,另外可能後續還會補充一些內容,但是爲了保證本篇文章的深入性,我會繼續介紹二叉搜索的前期實現,也就是插入這塊的實現。

二叉搜索樹該如何進行插入操作

就是不會寫代碼

很多人剛開始學習數據結構的時候,都有這種感覺,我看概念的話覺得都懂了,但是真的讓我去實現,自己寫代碼的話就會感到無從下手。

這其實也是我們學習編程都會遇到的一個問題,就是我看視頻或者看書覺得自己都可以看得懂啊,但是讓自己寫代碼的話卻寫不出來,所以啊,你看的懂和能寫出來完全是兩碼事。

所以啊,平常學習編程一定要多敲代碼

很多人在接觸鏈表也就是鏈式存儲這塊的時候,會迷惑這個節點咋表示啊,有點抽象啊,還有指針指向,這些看起來比較抽象,用代碼?怎麼搞啊?

就拿今天說的二叉搜索樹來說吧,我們要實現它的很重要的一步就是要確定這個節點怎麼表示啊,這個怎麼搞啊,有點懵啊,我們先來看畫圖怎麼表示的,比如這裏有一個二叉搜索樹:

image-20200323231826319

你就比如這個,我們該怎麼用代碼表示呢?

節點該怎麼表示

首先啊,你看,我們直觀來看,是不是每個節點是一個數據,然後還有指針指向,就是那些箭頭,所以最基本的一個節點包括:

  1. 數據元素
  2. 箭頭(也就是指針,一個節點有兩個)

簡單來看是不是就是這些,要記住,這是節點包含這些內容,那麼這個節點是一個整塊的內容,該怎麼表示,在java中不就可以使用一個類來表示嘛,也即是這樣:

class Node {
        

    }

然後就是包含的裏面的東西,首先是數據,這個數據這裏暫定爲整型類型,然後我們在類裏面定義這個數據元素:

class Node {
        int element;
    }

接下來就是數據指針的表示了,很多人會疑惑這個該怎麼表示,你想啊,這個指針指向的不也是個節點嘛,這裏的節點已經是個Node對象了,那麼一個節點裏面保存的指針不就是指向另外一個節點嘛,這不就是保存的另一個對象的引用地址嘛,所以我們可以直接在類裏面聲明節點對象,也即是這樣:

class Node {
        int element;
        Node left;
        Node right;
        Node parent;
    }

咋樣,是不是看明白了,然後這裏還需要記錄一個父節點,因爲後續的插入啥的要根據父節點來操作,然後我們還需要添加構造函數:

class Node {
        int element;
        Node left;
        Node right;
        Node parent;
        public Node(int element,Node parent) {
            this.element = element;
            this.parent = parent;
        }
    }

如此一來,我們就表示了二叉搜索樹的節點,接下來就是添加節點的操作。

二叉搜索樹添加操作

在此之前,我們需要設定兩個參數,一個是代表節點個數,一個是代表根節點:

 private int size;
 private Node root;

這個應該好理解吧,然後就是添加操作:

    /**
     * 添加節點
     */
    public void add(int element) {
        //添加第一個節點
        if (root == null) {
            root = new Node(element, null);
            size++;
            return;

        }
     }

這段代碼的邏輯也很簡單,你想啊,我們剛開始肯定是個空樹,那麼你添加的第一個節點理所應當的稱爲根節點,然後節點個數加一,這個不難理解啊,接下來我們看後續的添加操作:

//添加的不是第一個節點
        //找到父節點
        Node parent = null;
        Node node = root;
        int cmp = 0;

        //插入的節點與父節點進行比較
        while (node != null) {
             cmp = compare(element, node.element);

            //向左向右之前保存父節點
            parent = node;

            if (cmp > 0) {
                node = node.right;
            } else if (cmp < 0) {
                node = node.left;
            } else { //相等
                node.element = element;
                return;
            }
        }

這些代碼就需要好好說道說道了,不然我真怕有些夥伴看不懂,首先我們先看這個圖:

假設這裏的根節點79已經添加上去,我們添加第二個節點的時候,是不是需要與根節點的79進行比較,看看是比79大還是小,從而決定該把新插入的第二個節點放在79的左邊還是右邊,然後目前這個樹只有一個根節點79,它是沒有父節點的,對應的就是這些代碼:

//添加的不是第一個節點
        //找到父節點
        Node parent = null;
        Node node = root;
        int cmp = 0;

這個node就代表當前節點,此時就是根節點啊,這裏還定義一個整型變量cmp,主要是後續爲了比較兩個節點根絕cmp的值來確定兩個值的大小。

接下來就是核心代碼:(目的就是找到新插入節點的父節點

//插入的節點與父節點進行比較
        while (node != null) {
             cmp = compare(element, node.element);

            //向左向右之前保存父節點
            parent = node;

            if (cmp > 0) {
                node = node.right;
            } else if (cmp < 0) {
                node = node.left;
            } else { //相等
                node.element = element;
                return;
            }
        }

繼續看這個圖:

假如我們要插入一個新的節點,是不是需要先與根節點78比較,看看是大還是小,比較有三種結果,如果是等於的情況,就直接覆蓋當前節點的數據值,如果是大於或者小於,那都需要下移,將新插入的節點數據與33或者88比較,而此時的節點(需要與新插入節點比較的那個節點)node就變成了33或者88了,而無論是33還是88,79都是他們的父節點(其實是父節點的數據值,我這裏爲了敘述方便)。

所以需要把33或者88所在的節點編程當前節點node好與當前節點比較,在此之前需要先記錄着父節點,比如這裏新增加的節點的數據值比79大,那需要與79的右節點的數據值比較,圖上是88,假如這裏是空呢?我們是不是就需要把新插入的節點放在88的位置。自然而然,79就是我們插入新的節點的父節點,這就達到了我們的目的。

ps:這裏可能沒那麼好理解,記住核心,你插入新節點,就需要需要當前節點比較,比當前節點大就去與當前節點的右子節點比較,小就去與當前節點的左子節點比較,直到找到左右子節點爲空,就是新節點的位置,在此之前也記錄了父節點的位置,後續就可以根絕父節點插入新節點了

怕你們不理解😂

然後這句代碼: node = node.right;執行結果node肯定是空,因爲我們默認79的右節點是空的,那麼這段代碼就循環結束,也就進入以下代碼:

// 看看插入到父節點的哪個位置
        Node newNode = new Node(element, parent);
        if (cmp > 0) {
            parent.right = newNode;
        } else {
            parent.left = newNode;
        }
        size++;

經過上面的步驟,我們找到了新插入節點的父節點,剩下的就是根據是比父節點數據大還是小,然後存放到父節點的左子節點還是右子節點,這個好理解!

然後就是那個比較方法了:

   /**
     * @param e1
     * @param e2
     * @return 0代表相同,大於0,e1大,否則e2大
     */
    private int compare(int e1, int e2) {
        return e1-e2;
    }

至此,關於二叉搜索樹的簡單插入就實現了,當然,上述實現肯定有不足之處,比如有些地方需要判空等等,這裏只是提供一個思路和簡單實現,後續我們會慢慢完善的。

好了,今天的文章就到這裏,後續我們會繼續探討二叉搜索樹的其他知識,歡迎持續關注!

感謝閱讀

大學的時候選擇了自學Java,工作了發現吃了計算機基礎不好的虧,學歷不行這是沒辦法的事,只能後天彌補,於是在編碼之外開啓了自己的逆襲之路,不斷的學習Java核心知識,深入的研習計算機基礎知識,所有心得全部書寫成文,整理成有目錄的PDF,持續原創,PDF在公衆號持續更新,如果你也不甘平庸,那就與我一起在編碼之外,不斷成長吧!

其實這裏不僅有技術,更有那些技術之外的東西,比如,如何做一個精緻的程序員,而不是“屌絲”,程序員本身就是高貴的一種存在啊,難道不是嗎?

非常歡迎你的加入,未來的日子,編碼之外,有你有我,一起做一個人不傻,錢很多,活得久的快樂的程序員吧!

回覆關鍵字“PDF”,獲取技術文章合集,已整理好,帶有目錄,歡迎一起交流技術!

另外回覆“慶哥”,看慶哥給你準備的驚喜大禮包,只給首次關注的你哦!

任何問題,可以加慶哥微信:H653836923,另外,我有個交流羣,我會***不定期在羣裏分享學習資源,不定時福利***,感興趣的可以說下我邀請你!

對了,如果你是個Java小白的話,也可以加我微信,我相信你在學習的過程中一定遇到不少問題,或許我可以幫助你,畢竟我也是過來人了!

在這裏插入圖片描述

感謝各位大大的閱讀🥰

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章