首先介紹「回溯」演算法的應用。
「回溯」演算法主要用於搜尋,有時「回溯演算法」也叫「回溯搜尋」。這裡「搜尋」的意思是「查詢所需要的解」。我們每天使用的「搜尋引擎」就是幫助我們在龐大的網際網路上搜尋我們需要的資訊。而這裡的「回溯」指的是「狀態重置」,可以理解為「回到過去」、「恢復現場」,是在編碼的過程中,是為了節約空間而使用的一種技巧。而回溯其實是「深度優先遍歷」特有的一種現象。之所以是「深度優先遍歷」,是因為我們要解決的問題通常是在一棵隱式的樹上完成的。
「全排列」就是乙個非常經典的「回溯」演算法的應用。我們知道,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。問應如何選擇裝入揹包的物品,使得裝入揹包中物品的總價值最大?在選擇物品裝入揹包 時,對每種物品只有兩種...