1. 程式人生 > >程式設計師,你心裡需要有點樹

程式設計師,你心裡需要有點樹

看官,不要生氣,我沒有罵你也沒有鄙視你的意思,今天就是想單純的給大夥分享一下樹的相關知識,但是我還是想說作為一名程式設計師,自己心裡有沒有點樹?你會沒點數嗎?言歸正傳,樹是我們常用的資料結構之一,樹的種類很多有二叉樹、二叉查詢樹、平衡二叉樹、紅黑樹、B樹、B+樹等等,我們今天就來聊聊二叉樹相關的樹。

什麼是樹?

首先我們要知道什麼是樹?我們平常中的樹是往上長有分支的而卻不會形成閉環,資料結構中的樹跟我們我們平時看到的樹類似,確切的說是跟樹根長得類似,我畫了一幅圖,讓大家更好的理解樹。


圖1、圖2都是樹,圖3不是樹。每個紅色的圓圈我們稱之為元素也叫節點,用線將兩個節點連線起來,這兩個節點就形成了父子關係,同一個父節點的子節點成為兄弟節點,這跟我們家族關係一樣,同一個父親的叫做兄弟姐妹,在家族裡面最大的稱為老子,樹裡面也是一樣的,只是不叫老子,叫做跟節點,沒有子節點的叫做葉子節點。我們拿圖1來做示例,A為根節點,B、C為兄弟節點,E、F為葉子節點。

一顆樹還會涉及到三個概念高度深度,我們先來看看這三個名詞的定義:

高度:節點到葉子節點的最長路徑,從0開始計數

深度:跟節點到這個節點所經歷的邊數,從0開始計數

層:節點距離根節點的距離,從1開始計數

知道了三個名詞的概念之後,我們用一張圖來更加形象的表示這三個概念。

以上就是樹的基本概念,樹的種類很多,我們主要來學學二叉樹。

二叉樹

二叉樹就像它的名字一樣,每個元素最多有兩個節點,分別稱為左節點和右節點。當然並不是每個元素都需要有兩個節點,有的可能只有左節點,有的可能只有右節點。就像國家開放二胎一樣,也不是每個人都需要生兩個孩子。下面我們來看看一顆典型的二叉樹。

基於樹的儲存模式的不同,為了更好的利用儲存空間,二叉樹又分為完全二叉樹和非完全二叉樹,我們先來看看什麼是完全二叉樹、非完全二叉樹?

完全二叉樹的定義:葉子節點都在最底下兩層,最後一層的葉子節點都靠左排列,並且除了最後一層,其他層的節點個數都要達到最大

也許單看定義會看不明白,我們來看幾張圖,你就能夠明白什麼是完全二叉樹、非完全二叉樹。

1、完全二叉樹

2、非完全二叉樹

上面我們說了基於樹的儲存模式不同,而分為完全二叉樹和非完全二叉樹,那我們接下來來看看樹的儲存模式。

二叉樹的儲存模式

二叉樹的儲存模式有兩種,一種是基於指標或者引用的二叉鏈式儲存法,一種是基於陣列的順序儲存法

二叉鏈式儲存法

鏈式儲存法相對比較簡單,理解起來也非常容易,每一個節點都有三個欄位,一個欄位儲存著該節點的值,另外兩個欄位儲存著左右節點的引用。我們順著跟位元組就可以很輕鬆的把整棵樹串起來,鏈式儲存法的結構大概長成這樣。

順序儲存法

順序儲存法是基於陣列實現的,陣列是一段有序的記憶體空間,如果我們把跟節點的座標定位i=1,左節點就是 2 * i = 2,右節點 2 * i+ 1 = 3,以此類推,每個節點都這麼算,然後就將樹轉化成陣列了,反過來,按照這種規則我們也能將陣列轉化成一棵樹。看到這裡我想你一定看出了一些弊端, 如果這是一顆不平衡的二叉樹是不是會造成大量的空間浪費呢?沒錯,這就是為什麼需要分完全二叉樹和非完全二叉樹。分別來看看這兩種樹基於陣列的儲存模式。

完全二叉樹順序儲存法

非完全二叉樹順序儲存法

從圖中將樹轉化成陣列之後可以看出,完全二叉樹用陣列來儲存只浪費了一個下標為0的儲存空間,二非完全二叉樹則浪費了大量的空間。如果樹為完全二叉樹,用陣列儲存比鏈式儲存節約空間,因為陣列儲存不需要儲存左右節點的資訊

上面我們瞭解了二叉樹的定義、型別、儲存方式,接下來我們一起了解一下二叉樹的遍歷,二叉樹的遍歷也是面試中經常遇到的問題。

二叉樹遍歷

要了解二叉樹的遍歷,我們首先需要例項化出一顆二叉樹,我們採用鏈式儲存的方式來定義樹,例項化樹需要樹的節點資訊,用來存放該節點的資訊,因為我們才用的是鏈式儲存,所以我們的節點資訊如下。

/**
 * 定義一棵樹
 */
public class TreeNode {
    // 儲存值
    public int data;
    // 儲存左節點
    public TreeNode left;
    // 儲存右節點
    public TreeNode right;

    public TreeNode(int data) {
        this.data = data;
    }
}

定義完節點資訊之後,我們就可以初始化一顆樹啦,下面是初始化樹的過程:

public static TreeNode buildTree() {
    // 建立測試用的二叉樹
    TreeNode t1 = new TreeNode(1);
    TreeNode t2 = new TreeNode(2);
    TreeNode t3 = new TreeNode(3);
    TreeNode t4 = new TreeNode(4);
    TreeNode t5 = new TreeNode(5);
    TreeNode t6 = new TreeNode(6);
    TreeNode t7 = new TreeNode(7);
    TreeNode t8 = new TreeNode(8);

    t1.left = t2;
    t1.right = t3;
    t2.left = t4;
    t4.right = t7;
    t3.left = t5;
    t3.right = t6;
    t6.left = t8;

    return t1;
}

經過上面步驟之後,我們的樹就長成下圖所示的樣子,數字代表該節點的值。

有了樹之後,我們就可以對樹進行遍歷啦,二叉樹的遍歷有三種方式,前序遍歷、中序遍歷、後續遍歷三種遍歷方式,三種遍歷方式與節點輸出的順序有關係。下面我們分別來看看這三種遍歷方式。

前序遍歷

前序遍歷:對於樹中的任意節點來說,先列印這個節點,然後再列印它的左子樹,最後列印它的右子樹。

為了方便大家的理解,我基於上面我們定義的二叉樹,對三種遍歷方式的執行流程都製作了動態圖,希望對你的閱讀有所幫助,我們先來看看前序遍歷的執行流程動態圖。

理解了前序遍歷的概念和看完前序遍歷執行流程動態圖之後,你心裡一定很想知道,在程式碼中如何怎麼實現樹的前序遍歷?二叉樹的遍歷非常簡單,一般都是採用遞迴的方式進行遍歷,我們來看看前序遍歷的程式碼:

// 先序遍歷,遞迴實現 先列印本身,再列印左節點,在列印右節點
public static void preOrder(TreeNode root) {

    if (root == null) {
        return;
    }
    // 輸出本身
    System.out.print(root.data + " ");
    // 遍歷左節點
    preOrder(root.left);
    // 遍歷右節點
    preOrder(root.right);
}

中序遍歷

中序遍歷:對於樹中的任意節點來說,先列印它的左子樹,然後再列印它本身,最後列印它的右子樹。

跟前序遍歷一樣,我們來看看中序遍歷的執行流程動態圖。

中序遍歷的程式碼:

// 中序遍歷 先列印左節點,再輸出本身,最後輸出右節點
public static void inOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    inOrder(root.left);
    System.out.print(root.data + " ");
    inOrder(root.right);
}

後序遍歷

後序遍歷:對於樹中的任意節點來說,先列印它的左子樹,然後再列印它的右子樹,最後列印這個節點本身。

跟前兩種遍歷一樣,理解概念之後,我們還是先來看張圖。

後序遍歷的實現程式碼:

// 後序遍歷 先列印左節點,再輸出右節點,最後才輸出本身
public static void postOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    postOrder(root.left);
    postOrder(root.right);
    System.out.print(root.data + " ");
}

二叉樹的遍歷還是非常簡單的,雖然有三種遍歷方式,但都是一樣的,只是輸出的順序不一樣而已,經過了上面這麼多的學習,我相信你一定對二叉樹有不少的認識,接下來我們來了解一種常用而且比較特殊的二叉樹:二叉查詢樹

二叉查詢樹

二叉查詢樹又叫二叉搜尋樹,從名字中我們就能夠知道,這種樹在查詢方面一定有過人的優勢,事實確實如此,二叉查詢樹確實是為查詢而生的樹,但是它不僅僅支援快速查詢資料,還支援快速插入、刪除一個數據。那它是怎麼做到這些的呢?我們先從二叉查詢樹的概念開始瞭解。

二叉查詢樹:在樹中的任意一個節點,其左子樹中的每個節點的值,都要小於這個節點的值,而右子樹節點的值都大於這個節點的值。

難以理解?記不住?沒關係的,下面我定義了一顆二叉查詢樹,我們對著樹,來慢慢理解。


根據二叉查詢樹的定義,每棵樹的左節點的值要小於這父節點,右節點的值要大於父節點。62節點的 所有左節點的值都要小於 62 ,所有右節點 的值都要大於 62 。對於這顆樹上的每一個節點都要滿足這個條件,我們拿我們樹上的另一個節點 35 來說,它的右子樹上的節點值最大不能超過 47 ,因為 35 是 47 的左子樹,根據二叉搜尋樹的規則,左子樹的值要小於節點值。

二叉查詢樹既然名字中帶有查詢兩字,那我們就從二叉查詢樹的查詢開始學習二叉查詢樹吧。

二叉查詢樹的查詢操作

由於二叉查詢樹的特性,我們需要查詢一個數據,先跟跟節點比較,如果值等於跟節點,則返回根節點,如果小於根節點,則必然在左子樹這邊,只要遞迴查詢左子樹就行,如果大於,這在右子樹這邊,遞迴右子樹即可。這樣就能夠實現快速查詢,因為每次查詢都減少了一半的資料,跟二分查詢有點相似,快速插入、刪除都是居於這個特性實現的。

下面我們用一幅動態圖來加強對二叉查詢樹查詢流程的理解,我們需要在上面的這顆二叉查詢樹中找出值等於 37 的節點,我們一起來看看流程圖是怎麼實現的。

  • 1、先用 37 跟 62 比較,37 < 62 ,在左子樹中繼續查詢
  • 2、左子樹的節點值為 58,37 < 58 ,繼續在左子樹中查詢
  • 3、左子樹的節點值為 47,37 < 47,繼續在左子樹中查詢
  • 4、左子樹的節點值為 35,37 > 35,在右子樹中查詢
  • 5、右子樹中的節點值為 37,37 = 37 ,返回該節點

講完了查詢的概念之後,我們一起來看看二叉查詢樹的查詢操作的程式碼實現

/**
 * 根據值查詢樹
 * @param data 值
 * @return
 */
public TreeNode find(int data) {
    TreeNode p = tree;
    while (p != null) {
        if (data < p.data) p = p.left;
        else if (data > p.data) p = p.right;
        else return p;
    }
    return null;
}

二叉查詢樹的插入操作

插入跟查詢差不多,也是從根節點開始找,如果要插入的資料比節點的資料大,並且節點的右子樹為空,就將新資料直接插到右子節點的位置;如果不為空,就再遞迴遍歷右子樹,查詢插入位置。同理,如果要插入的資料比節點數值小,並且節點的左子樹為空,就將新資料插入到左子節點的位置;如果不為空,就再遞迴遍歷左子樹,查詢插入位置。

假設我們要插入 63 ,我們用一張動態圖來看看插入的流程。

  • 1、63 > 62 ,在樹的右子樹繼續查詢.
  • 2、63 < 88 ,在樹的左子樹繼續查詢
  • 3、63 < 73 ,因為 73 是葉子節點,所以 63 就成為了 73 的左子樹。

我們來看看二叉查詢樹的插入操作實現程式碼

/**
 * 插入樹
 * @param data
 */
public void insert(int data) {
    if (tree == null) {
        tree = new TreeNode(data);
        return;
    }

    TreeNode p = tree;

    while (p != null) {
        // 如果值大於節點的值,則新樹為節點的右子樹
        if (data > p.data) {
            if (p.right == null) {
                p.right = new TreeNode(data);
                return;
            }
            p = p.right;
        } else { // data < p.data
            if (p.left == null) {
                p.left = new TreeNode(data);
                return;
            }
            p = p.left;
        }
    }
}

二叉查詢樹的刪除操作

刪除的邏輯要比查詢和插入複雜一些,刪除分一下三種情況:

第一種情況:如果要刪除的節點沒有子節點,我們只需要直接將父節點中,指向要刪除節點的指標置為 null。比如圖中的刪除節點 51。

第二種情況:如果要刪除的節點只有一個子節點(只有左子節點或者右子節點),我們只需要更新父節點中,指向要刪除節點的指標,讓它指向要刪除節點的子節點就可以了。比如圖中的刪除節點 35。

第三種情況:如果要刪除的節點有兩個子節點,這就比較複雜了。我們需要找到這個節點的右子樹中的最小節點,把它替換到要刪除的節點上。然後再刪除掉這個最小節點,因為最小節點肯定沒有左子節點(如果有左子結點,那就不是最小節點了),所以,我們可以應用上面兩條規則來刪除這個最小節點。比如圖中的刪除節點 88

前面兩種情況稍微簡單一些,第三種情況,我製作了一張動態圖,希望能對你有所幫助。

我們來看看二叉查詢樹的刪除操作實現程式碼

public void delete(int data) {
    TreeNode p = tree; // p指向要刪除的節點,初始化指向根節點
    TreeNode pp = null; // pp記錄的是p的父節點
    while (p != null && p.data != data) {
        pp = p;
        if (data > p.data) p = p.right;
        else p = p.left;
    }
    if (p == null) return; // 沒有找到

    // 要刪除的節點有兩個子節點
    if (p.left != null && p.right != null) { // 查詢右子樹中最小節點
        TreeNode minP = p.right;
        TreeNode minPP = p; // minPP表示minP的父節點
        while (minP.left != null) {
            minPP = minP;
            minP = minP.left;
        }
        p.data = minP.data; // 將minP的資料替換到p中
        p = minP; // 下面就變成了刪除minP了
        pp = minPP;
    }

    // 刪除節點是葉子節點或者僅有一個子節點
    TreeNode child; // p的子節點
    if (p.left != null) child = p.left;
    else if (p.right != null) child = p.right;
    else child = null;

    if (pp == null) tree = child; // 刪除的是根節點
    else if (pp.left == p) pp.left = child;
    else pp.right = child;
}

我們上面瞭解了一些二叉查詢樹的相關知識,由於二叉查詢樹在極端情況下會退化成連結串列,例如每個節點都只有一個左節點,這是時間複雜度就變成了O(n),為了避免這種情況,又出現了一種新的樹叫平衡二叉查詢樹,由於本文篇幅有點長了,相信看到這裡的各位小夥伴已經有點疲憊了,關於平衡二叉查詢樹的相關知識我就不在這裡介紹了。

最後

打個小廣告,金九銀十跳槽季,平頭哥給大家整理了一份較全面的 Java 學習資料,歡迎掃碼關注微信公眾號:「平頭哥的技術博文」領取,祝各位升職加薪。

相關推薦

程式設計師心裡需要有點

看官,不要生氣,我沒有罵你也沒有鄙視你的意思,今天就是想單純的給大夥分享一下樹的相關知識,但是我還是想說作為一名程式設計師,自己心裡有沒有點樹?你會沒點數嗎?言歸正傳,樹是我們常用的資料結構之一,樹的種類很多有二叉樹、二叉查詢樹、平衡二叉樹、紅黑樹、B樹、B+樹等等,我們今天就來聊聊二叉樹相關的樹。 什麼是樹

程式設計師為什麼需要一臺mac?

用了Mac ,我再也回不去Windows。 A:帥哥,我電腦壞了。 B:重灌系統吧,包好! 重灌系統 windows系統解決所有系統問題的一劑神藥。Mac 時代再也不需要做這種勞命傷財的事情了,沒有什麼工具比一個穩定可靠,可以持續執行的作業系統更好

一個優秀的java程式設計師需要知道的10個程式碼優化方式!

程式碼優化不息以來都是一個軌範員經常要掛在嘴邊的一個詞,特別是對付如今軌範員越來越普及,網上教程一大把的時代,良多軌範員寫出的程式碼都是為了了局而寫程式碼,從來不去考慮程式碼的優化問題,如許的程式碼拿去應聘也是非常虧損的,程式碼的優化可以直接浮現出來一個軌範員的根基功以及可塑性. 而程式碼

做為一個Java程式設計師需要哪些傍身的技能?

最近總有些斷斷續續的思考,想想從我入行以來,我到底學會了什麼,做成過什麼,以後要做什麼,如何提升自己······· 工作3年了,常聽人說3年,5年,10年是程式設計師的坎,每過一個都會有新的想法,新的改變。 最近剛失業,原公司要解散開發團隊,轉做其他業務,這個訊息對我

@程式設計師需要點財商

01、財商 很長一段時間內,我都保持著一種崇高的品格——視金錢如糞土,自以為是地認為金錢是生不帶來死不帶去的玩意兒。 所以我每個月 20 號領到工資,25 號就還給了信用卡,說得再明白點,我所有的積蓄僅限於這 5 天的利息總和。說得再直接點,就是以前的我總認為“掙多少,

@程式設計師需要點金融常識

01、對金融的偏見 常常聽見一句俗話:“越靠近錢的地方越能掙錢。” 很遺憾,以前的我總是對這句充滿銅臭味的話視若罔聞——我是一名程式設計師,所以我的核心工作就是鑽進程式碼的世界裡,把程式碼寫得儘可能的完善,少出 bug。 這種思維的侷限性,一直把我封閉在一

想要成為一名優秀的Java程式設計師需要這8個錦囊

私底下,隔三差五就有讀者問我:“二哥,怎麼樣才能像你一樣,成為一名優秀的 Java 開發者呢?”假如把“怎麼才能像你一樣”去掉的話,這個問題就是一個好問題,否則的話,總有點彩虹屁的嫌疑。但話說回來,我喜歡這種提問的藝術。 從上大學那會學 Java 到現在,我已經積攢了 10 多年的程式設計經驗,雖然離優秀

科普想成為厲害的 Java 後端程式設計師需要懂這 13 個知識點

老讀者就請肆無忌憚地點贊吧,微信搜尋【沉默王二】關注這個在九朝古都洛陽苟且偷生的程式設計師。本文 GitHub github.com/itwanger 已收錄,裡面還有我精心為你準備的一線大廠面試題。 站在運籌帷幄的角度來看,一名厲害的 Java 後端程式設計師都需要懂得哪些知識呢?我想,這也是很多讀

程式設計師焦慮嗎?

我很焦慮,請問程式設計師們,你焦慮嗎? 我是一名年過30的北漂程式設計師,我身邊的朋友大多也是程式設計師。我總感覺到30歲以上的程式設計師充滿了焦慮。“華為清退35歲以上老員工”、“中興程式設計師墜樓”這些事件,更加重了程式設計師的焦慮。 最近又有朋友都跟我抱怨他們的焦慮的事情,我仔細聽完後發現,他們焦慮

程式設計師焦慮嗎有感。

話說在部落格看到一篇文章,說三十出頭的程式設計師很焦慮。是的,很焦慮啊,誰不焦慮啊,難道只有程式設計師這行才焦慮嗎?其實,並不是,而是窮人在一線城市都焦慮,特別是拖家帶口的,家裡頂樑柱的,這世界只有一種病,就是窮病嘛。 話說回來,這程式設計師怎麼就那麼窮呢?我的看法有下幾點。 1,上班再忙,也要按時吃飯和

五種型別的程式設計師屬於哪一種?

在我的程式設計生涯中,我碰到過很多奇奇怪怪的對手和同盟。我把這些編碼戰士們分成五類,有些人是你隊伍中的好夥伴,有些人則是搗蛋者,讓你的每一個計劃都完不成。 不管怎麼說,他們在軟體開發的諸神殿上都佔有一席之地。如果你的團隊中沒有一個合適健康的比例,混合這些不同型別的程式設計師,要麼你會發現你的專案跌跌

Python程式設計師必須知道的面試題

Python越來越火之後,把python作為自己的終生事業來做的話,是很多的終極目標,可是要做到知己知彼,百戰不殆,那麼你需要了解面試官出什麼題, 這些面試問題大致可以分為四類:什麼(what)?如何做(how)?說區別/談優勢(difference)以及實踐操作(practice)。 &n

要當程式設計師首先得了解的大腦!戒掉遊戲!

把大腦當做第三個人,他會不聽話,分泌多巴胺去玩遊戲。 讓你根本沒有心思去學習! 所以,瞭解和控制你的大腦是必須的! 幾年前,我在一次學術會議上碰到一位漂亮的女孩。交流之後,發現我們有著相似的興趣、相近的背景、隔得不遠。我們在會議期間聊的非常投機,會議之後也保持著聯絡,交往越來越多。彼時的我,

程式設計師碰到過的最難調的Bug是什麼樣的?

Bug無論是對於測試還是開發來說,應該都不陌生,雖然我們經歷的大部分bug有的被其他人修復了並且在網際網路分享出來了,這時候我們通過Stackoverflow、Baidu、Google等搜尋引擎找到答案了。   但是我們在工作中也可能會遇到一些疑難的bug,這裡bug我們在搜素引擎

作為程式設計師“怕老”嗎?

行文之前,先問大家幾個問題: 1,你開始逐漸擔憂自己的年紀? 2,你開始覺得加班熬不過小年輕? 3,你開始因未知新興技術而慌張? 4,你開始跳不動槽? 如果以上的回答都“是”,那麼你確實陷入“怕老”行列了。 “青春飯”這個詞,程式設計師肯定不陌生,因為上了幾年班,做到一

【1024程式設計師節】程式設計師學程式設計的初衷是什麼?

前言 今天是1024程式設計師節,中國500w+的程式設計師今天可以享受一天專屬的節日,網路上也有各種慶祝方式: 一些公司祭出了“程式設計師鼓勵師” 一些公司給程式設計師放了個“假” 還有公司出了張“海報” 都是玩

精準鑑別初級、中級、高階程式設計師是哪一種?

@jonde 初級:產品是大爺 中級:懟過產品 高階:打過產品   @PureWhiteWu  初級:加班 中級:不加班 高階:你們加班   @sunsulei 初級:嗯? 中級:嗯。 高階:嗯?

作為程式設計師在程式設計時吃了哪些數學的虧?

“如果,你只想當個普通程式設計師,數學對你來說並不重要;但你要想做頂級程式設計師,數學對你來說就相當重要了。”這是幾年前我在矽谷技術交流 Meetup 上聽到的一個分享,而在聽到這番話之前,我很少思考數學和計算機程式設計之間的關係。 這二者之間的關係,到底有多緊密呢?我們可以從 Goo

幼兒園小班都在學 AI程式設計師還有什麼資格不進階

近日,網上流傳一組《人工智慧實驗教材》的圖片,教材是為幼兒園小班的小朋友們設計的,還只是上冊。 根據公開資訊,《人工智慧實驗教材》紙質教材合計33本,覆蓋幼兒到青少年全年齡段。 也就是說,從幼兒園到高中,同桌可能會換無數個,但這套AI教材會一直陪你考大學! 事實上,程式設計學

程式設計師為什麼值這麼多錢?

聽說一段時間不加薪,人就會開始思考起和工資有關的問題。消費水平又提升了,能力也進步了,經驗也更多了,怎麼還沒漲工資呢? 近兩年,有了點餘錢就開始考慮起投資來,比如:投資股票首先需要判斷的就是關於公司價值和價格的關係。回到個人身上,似乎工資也就是個人價值在市場上的一個價格