回溯演算法介紹以及模板

Tomorrowland_D發表於2024-08-14

回溯演算法的理解:

  • 回溯演算法可以理解為一顆樹形結構,即一顆n叉樹,當遍歷到葉子節點的時候,我們就到達了遞迴的終點,此時我們應該往上走。
  • 回溯法解決的問題都可以抽象為樹形結構,是的,我指的是所有回溯法的問題都可以抽象為樹形結構!因為回溯法解決的都是在集合中遞迴查詢子集,集合的大小就構成了樹的寬度,遞迴的深度就構成了樹的深度

回溯法的效率

回溯法的效能如何呢,回溯並不是什麼高效的演算法,雖然它很難理解,但是它的本質是列舉出所有情況。

因為回溯的本質是窮舉,窮舉所有可能,然後選出我們想要的答案,如果想讓回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是窮舉的本質。

那麼既然回溯法並不高效為什麼還要用它呢?

因為沒得選,一些問題能暴力搜出來就不錯了,撐死了再剪枝一下,還沒有更高效的解法。

回溯演算法的模板

  • 回溯函式模板返回值以及引數

回溯演算法中函式返回值一般為void。

再來看一下引數,因為回溯演算法需要的引數可不像二叉樹遞迴的時候那麼容易一次性確定下來,所以一般是先寫邏輯,然後需要什麼引數,就填什麼引數。

但後面的回溯題目的講解中,為了方便大家理解,我在一開始就幫大家把引數確定下來。

回溯函式虛擬碼如下:

void backtracking(引數)
  • 回溯函式終止條件

回溯要有中止條件

什麼時候達到了終止條件,樹中就可以看出,一般來說搜到葉子節點了,也就找到了滿足條件的一條答案,把這個答案存放起來,並結束本層遞迴。

所以回溯函式終止條件虛擬碼如下:

if (終止條件) {
    存放結果;
    return;
}
  • 回溯搜尋的遍歷過程

在上面我們提到了,回溯法一般是在集合中遞迴搜尋,集合的大小構成了樹的寬度,遞迴的深度構成的樹的深度。

注意圖中,我特意舉例集合大小和孩子的數量是相等的!

回溯函式遍歷過程虛擬碼如下:

for (選擇:本層集合中元素(樹中節點孩子的數量就是集合的大小)) {
    處理節點;
    backtracking(路徑,選擇列表); // 遞迴
    回溯,撤銷處理結果
}

for迴圈就是遍歷集合區間,可以理解一個節點有多少個孩子,這個for迴圈就執行多少次。

backtracking這裡自己呼叫自己,實現遞迴。

大家可以從圖中看出for迴圈可以理解是橫向遍歷,backtracking(遞迴)就是縱向遍歷,這樣就把這棵樹全遍歷完了,一般來說,搜尋葉子節點就是找的其中一個結果了。

分析完過程,回溯演算法模板框架如下:

模板的程式碼:

void backtracking(引數) {
    if (終止條件) {
        存放結果;
        return;
    }

    for (選擇:本層集合中元素(樹中節點孩子的數量就是集合的大小)) {
        處理節點;
        backtracking(路徑,選擇列表); // 遞迴
        回溯,撤銷處理結果
    }
}

相關文章