二叉樹的遞迴套路

Typing_with_toes發表於2024-08-08

二叉樹的遞迴套路

二叉樹結構

二叉樹是一個將資料組織成頭尾相連的特殊連結串列,每一個資料單元與連結串列一樣有一個指向其的指標,但與連結串列不同的是其可以有兩個指向其他單元的指標,分別是其左孩子與右孩子。採用該這種結構,最終資料的呈現形式會與“鏈”不一樣,而呈現出了一種樹的結構。

對於這種樹型結構,每一棵完整的子樹都有頭節點、左孩子與右孩子。頭節點可以從左右孩子分別收集資訊並向上傳遞,給自己的頭節點。這就是二叉樹遞迴套路的基本準則,採用這種方式去寫遞迴演算法,就可以簡單地遍歷所有的二叉樹節點並收集資訊,在整個二叉樹地頭節點進行彙集。

二叉樹的遞迴套路步驟

以最低祖先點闡述遞迴套路的具體執行。最低祖先點:給定一顆二叉樹中的兩個節點,求二者向根節點溯源路徑中第一個相同的節點。

步驟1:構建資訊類Info

對於一個我們想要解決的實際問題,當前的處理思想是子節點一直向上提交資訊,那麼這個資訊一定就要能推斷問題或者解決問題。所以第一個步驟為:假設頭節點可以獲取左右孩子的所有資訊,構建需要解決這個問題的方法類

問題分析:想要找到二者的最先祖先點,則該點下路徑搜尋中必須要能找到這兩個節點,而由於是最低的公共祖先節點,所以要返回第一個可以在孩子中搜尋到二者的節點,那麼資訊中一定要包含這個返回節點以及是否能找到兩個節點的資訊,因此有如下定義:

public static class Info {
    public boolean findA;//是否這條路徑有節點A
    public boolean findB;//是否這條路徑有節點B
    public Node ans;//最找在此節點下路徑發現節點A,B的節點
    public Info(boolean fA, boolean fB, Node an) {
        findA = fA;
        findB = fB;
        ans = an;
    }
}

步驟2:構建自身的資訊體

確定好資訊類後,我們的遞迴函式的返回型別就是資訊類,因為我們想要左右孩子直接向頭節點返回資訊類,然後頭節點處理這些資訊,並結合自己的資訊,生成自己的資訊體。因此第二個步驟為:頭節點從左右孩子獲取左右資訊體,結合自身資訊處理得到本節點的資訊體。

Info leftInfo = process(x.left, a, b);
Info rightInfo = process(x.right, a, b);
boolean findA = (x == a) || leftInfo.findA || rightInfo.findA;//此節點的findA為:左孩子下可以找到||右孩子下可以找到A||自己就是A
boolean findB = (x == b) || leftInfo.findB || rightInfo.findB;//此節點的findB為:左孩子下可以找到||右孩子下可以找到B||自己就是B
Node ans = null;
if (leftInfo.ans != null) {
    ans = leftInfo.ans;
} else if (rightInfo.ans != null) {
    ans = rightInfo.ans;
} //如果左右孩子中的返回資訊中已經設定好了答案節點那麼就直接設定為本資訊體的答案,否則就在本節點進行一次資訊處理,看看本節點是否有答案。
else {
    if (findA && findB) {
        ans = x;
    }
return new Info(findA, findB, ans);//返回本節點的資訊體

必須注意本節點收到左右孩子的資訊,結合自身資訊進行加工處理時必須要考慮所有的情況。

步驟3:邊界條件的處理

這是對頭節點下探到自己孩子,返回為空的處理,程式的入口是頭節點,會一直下探,探到底部的節點,底部的節點此時也會呼叫:

Info leftInfo = process(x.left, a, b);
Info rightInfo = process(x.right, a, b);

並得到底部節點的空的孩子的返回資訊,這時要合理設定底部的返回資訊避免其會對後續的資訊處理產生干擾,在本例中,資訊體以是否找到節點的boolean型別為核心,所以,為了不干擾結果,當本節點為空時,應該立刻返回一個不干擾的資訊體:

return new Info(false,false,null);

程式碼彙總

經歷所有的步驟就有了總的程式碼:

//1.資訊體的設定
public static class Info {
    public boolean findA;
    public boolean findB;
    public Node ans;

    public Info(boolean fA, boolean fB, Node an) {
        findA = fA;
        findB = fB;
        ans = an;
    }
}
//遞迴函式,返回型別是資訊體類
public static Info process(Node x, Node a, Node b) {
    //邊緣的返回
    if (x == null) {
        return new Info(false, false, null);
    }
    //得到左右孩子的資訊,並結合自身資訊,加工出自己的資訊體,並向上返回。
    Info leftInfo = process(x.left, a, b);
    Info rightInfo = process(x.right, a, b);
    boolean findA = (x == a) || leftInfo.findA || rightInfo.findA;
    boolean findB = (x == b) || leftInfo.findB || rightInfo.findB;
    Node ans = null;
    if (leftInfo.ans != null) {
        ans = leftInfo.ans;
    } else if (rightInfo.ans != null) {
        ans = rightInfo.ans;
    } else {
        if (findA && findB) {
            ans = x;
        }
    }
    return new Info(findA, findB, ans);
}

對於遞迴套路的理解

整個遞迴函式的關鍵是假設頭節點可以從左右孩子獲得所有想要的資訊,這一步是透過:

Info leftInfo = process(x.left, a, b);
Info rightInfo = process(x.right, a, b);

來實現的,那麼在實際的程式運轉中,會怎樣去進行這個遞迴方法呢,首先我們知道JAVA實現遞迴函式是依靠方法棧,Java執行一個程式時,會首先向棧中壓入其main函式,然後每當main函式中呼叫了其他函式則繼續在棧中壓入其他函式,其他函式產生了呼叫亦然。對於本方法亦然,所以執行到本方法時,會將該方法以棧幀的形式壓入方法棧中。

以程式執行到本函式為起點:

棧中壓入本方法f(root),開始執行本程式中傳入了二叉樹的根節點,首先會經過一個空指標判斷,如果為空則直接返回,不為空,則會向本程式中傳入根節點的左孩子;再次執行以root.left為當前節點的程式,棧中壓入本方法f(root.left),再次經歷一個空指標判斷,然後繼續向本程式中傳入當前節點的左孩子;再次執行以root.left.left為當前節點的策劃功能,棧中壓入方法f(root.left.left)……

這個過程會一直重複,直到f(root.left........leftn)進入程式後在判斷環節發現其是空,那麼就會直接返回,這樣該方法結束,方法棧中彈出該方法,執行f(root.left...leftn-1)方法,此時, Info leftInfo = process(x.left, a, b);已經有了返回值,為new Info(false, false, null),該程式就會進入下一條程式碼的執行:Info rightInfo = process(x.right, a, b);,由於其也為空,則也會返回new Info(false, false, null),這樣底部節點就收到了左右資訊,就可以開始處理自身資訊並加工自己的資訊體返回。

那麼當底部節點完成其資訊體後,這個節點其實也是其他節點的左節點,它的返回資訊會被上層程式碼接收即f(root.left...leftn-2)中的Info leftInfo = process(x.left, a, b);這樣,f(root.left...leftn-2)就可以去執行下一行,去接收其右節點資訊,這個過程與剛剛的左節點資訊上交也是類似的。

總的來說,程式會按照一個遞迴序的順序去遍歷所有的節點,然後逐級上交資訊, 最終在根節點彙總資訊後,返回整棵樹的資訊。

  1. f(1)壓入棧中,收集左資訊,f(2)壓入棧中

  2. f(2)收集左資訊,f(3)壓入棧中

  3. f(3)將f(3.left)壓入棧中,直接返回,將f(3.right)壓入棧中,也直接返回,f(3)處理完自身資訊,程式結束,方法棧中彈出f(2)。

  4. 執行f(2)的收集右資訊,將f(4)壓入棧中

  5. f(4)會執行一個跟步驟三一樣的過程。並返回作為f(2)的右資訊。

  6. f(2)已經蒐集到了左右資訊,處理完資訊後,返回資訊給f(1)中的收集左資訊環節,進入f(1)的收集右資訊環節。

  7. 首先以f(5)為f(1)右資訊的返回,壓入f(5)。f(5)以f(6)為其左資訊。f(6)執行結束,壓入f(7)作為f(5)右資訊的返回,f(7執行結束,處理f(5)資訊,f(1)中得到右資訊,最終返回f(1)資訊,得到整棵樹的資訊。

相關文章