從零開始學演算法 二 回溯

2022-07-13 18:21:08 字數 2559 閱讀 1005

首先介紹「回溯」演算法的應用。

「回溯」演算法主要用於搜尋,有時「回溯演算法」也叫「回溯搜尋」。這裡「搜尋」的意思是「查詢所需要的解」。我們每天使用的「搜尋引擎」就是幫助我們在龐大的網際網路上搜尋我們需要的資訊。而這裡的「回溯」指的是「狀態重置」,可以理解為「回到過去」、「恢復現場」,是在編碼的過程中,是為了節約空間而使用的一種技巧。而回溯其實是「深度優先遍歷」特有的一種現象。之所以是「深度優先遍歷」,是因為我們要解決的問題通常是在一棵隱式的樹上完成的。

「全排列」就是乙個非常經典的「回溯」演算法的應用。我們知道,n 個數字的全排列一共有 n!n! 這麼多個。

大家可以嘗試一下在紙上寫 3 個數字、4 個數字、5 個數字的全排列,相信不難找到這樣的方法。

例如陣列 [1, 2, 3] 的全排列。

我們先寫以 1 開頭的全排列,它們是:[1, 2, 3], [1, 3, 2];

再寫以 2 開頭的全排列,它們是:[2, 1, 3], [2, 3, 1];

最後寫以 3 開頭的全排列,它們是:[3, 1, 2], [3, 2, 1]。

我們只需要按順序列舉每一位可能出現的情況,已經選擇的數字在接下來要確定的數字中不能出現。按照這種策略選取就能夠做到不重不漏,把可能的全排列都列舉出來。

在列舉第一位的時候,有 3 種情況。

在列舉第二位的時候,前面已經出現過的數字就不能再被選取了;

在列舉第三位的時候,前面 2 個已經選擇過的數字就不能再被選取了。

這樣的思路,我們可以用乙個樹形結構來表示。

使用程式設計的方法得到全排列,就是在這樣的乙個樹形結構中進行程式設計,具體來說,就是執行一次深度優先遍歷,從樹的根結點到葉子結點形成的路徑就是乙個全排列。

同樣是遍歷,我們很自然提出疑問,廣度優先遍歷是否可以。答案是:當然可以,有些搜尋問題的確是使用廣度優先遍歷解決的的。

這裡使用深度優先遍歷的原因是:1、編碼方便;2、全域性使用同乙份狀態變數,避免資源浪費;3、深度優先遍歷在執行的過程中,狀態不會發生跳躍,狀態轉移非常容易。

下面我們解釋如何編碼:

1、首先這棵樹除了根結點和葉子結點以外,每乙個結點做的事情其實是一樣的,即在已經選了一些數的前提,我們需要在剩下還沒有選擇的數中按照順序依次選擇乙個數,這顯然是乙個遞迴結構;

2、遞迴的終止條件是,數已經選夠了,因此我們需要乙個變數來表示當前遞迴到第幾層,我們把這個變數叫做 depth;

3、這些結點實際上表示了搜尋(查詢)全排列問題的不同階段,為了區分這些不同階段,我們就需要一些變數來記錄為了得到乙個全排列,我們進行到那一步了,在這裡我們需要兩個變數:

(1)已經選了哪些數,到葉子結點時候,這些已經選擇的數就構成了乙個全排列;

(2)乙個布林陣列 used,初始化的時候都為 false 表示這些數還沒有被選擇,當我們選定乙個數的時候,就將這個陣列的相應位置設定為 true ,這樣在考慮下乙個位置的時候,就能夠以 o(1)o(1) 的時間複雜度判斷這個數是否被選擇過,這是一種「以空間換時間」的思想。

我們把這兩個變數稱之為「狀態變數」,它們表示了我們在求解乙個問題的時候所處的階段。

5、另外,因為是執行深度優先遍歷,從較深層的結點返回到較淺層結點的時候,需要做「狀態重置」,即「回到過去」、「恢復現場」,我們舉乙個例子。

從 [1, 2, 3] 到 [1, 3, 2] ,深度優先遍歷是這樣做的,從 [1, 2, 3] 回到 [1, 2] 的時候,需要撤銷剛剛已經選擇的數 3,因為在這一層只有乙個數 3 我們已經嘗試過了,因此程式回到上一層,需要撤銷對 2 的選擇,好讓後面的程式知道,選擇 3 了以後還能夠選擇 2。

這種在遍歷的過程中,從深層結點回到淺層結點的過程中所做的操作就叫「回溯」

下面我們就來看看**應該如何編寫:

public

class

solution

boolean used = new

boolean[len];

list

path = new arraylist<>();

dfs(nums, len,

0, path, used, res);

return

res;

}private

void dfs(int nums, int len, int

depth,

list

path, boolean used,

list

>res)

for (int i = 0; i < len; i++) }}

public

static

void

main(string args) ;

solution solution = new

solution();

list

> lists =solution.permute(nums);

system.

out.println(lists);}}

其他一些適合回溯演算法的題目

演算法實驗二 回溯法 堡壘問題

時限 1000ms 記憶體限制 10000k 總時限 3000ms 描述城堡是乙個4 4的方格,為了保衛城堡,現需要在某些格仔裡修建一些堡壘。城堡中的某些格仔是牆,其餘格仔都是空格,堡壘只能建在空格裡,每個堡壘都可以向上下左右四個方向射擊,如果兩個堡壘在同一行或同一列,且中間沒有牆相隔,則兩個堡壘都...

演算法實驗二 回溯法 0 1揹包問題

時限 1000ms 記憶體限制 10000k 總時限 3000ms 描述需對容量為c 的揹包進行裝載。從n 個物品中選取裝入揹包的物品,每件物品i 的重量為wi 價值為pi 對於可行的揹包裝載,揹包中物品的總重量不能超過揹包的容量,最佳裝載是指所裝入的物品價值最高。輸入多個測例,每個測例的輸入佔三行...

實驗六 回溯演算法 二)

一 實驗目的與要求 1 熟悉0 1問題 2 掌握回溯演算法 3 能對設計的演算法進行複雜度分析。二 實驗題目 0 1揹包問題。給定n種物品和一揹包。物品i的重量是wi,其價值為vi,揹包的容量為c。問應如何選擇裝入揹包的物品,使得裝入揹包中物品的總價值最大?在選擇物品裝入揹包 時,對每種物品只有兩種...