1. 程式人生 > 實用技巧 >演算法 回溯演算法套路模板

演算法 回溯演算法套路模板

回溯演算法應用

經典的全排列和N皇后

怎麼窮舉全排列的呢?比方說給三個不重複數 [1,2,3],你肯定不會無規律地亂窮舉,一般是這樣:

先固定第一位為 1,然後第二位可以是 2,那麼第三位只能是 3;然後可以把第二位變成 3,第三位就只能是 2 了;然後就只能變化第一位,變成 2,然後再窮舉後兩位……

其實這就是回溯演算法,只要從根遍歷這棵樹,記錄路徑上的數字,其實就是所有的全排列。我們不妨把這棵樹稱為回溯演算法的「決策樹」

為啥說這是決策樹呢,因為你在每個節點上其實都在做決策,比如當前在樹的第二層深度,站在數字2的位置做決策,可以選擇 1 那條樹枝,也可以選擇 3 那條樹枝。為啥只能在 1 和 3 之中選擇呢?因為 2 這個樹枝在你身後,這個選擇你之前做過了,而全排列是不允許重複使用數字的

「路徑」,記錄你已經做過的選擇;

「選擇列表」就是[1,3],表示你當前可以做出的選擇;可以通過狀態陣列來維護每一個深度下的狀態

「結束條件」就是遍歷到樹的底層(或者判斷所有的數都進入了路徑中)

盜圖顯示(多謝原作者):

程式碼塊:

/// <summary>
/// 回溯法 遞迴 樹形結構
/// 每次做選擇,遞迴,然後回溯到上一層修改選擇,滿足條件後新增到結果列表(注意這裡不同語法可能需要拷貝操作)
/// 其全排列的形成靠的是遞迴函式遍歷到對應深度層, 另外的排列可能性在於回溯原來的狀態以新的值作為遞迴的根
///

/// </summary>
/// <param name="nums"></param>
/// <returns></returns>
public IList<IList<int>> Permute(int[] nums)
{
IList<IList<int>> ans = new List<IList<int>>();
if (null == nums || nums.Length == 0) return ans;

bool[] marked = new bool[nums.Length];
backtrack_state(nums, marked, new List<int>(), ans);

return ans;
}

/// <summary>
/// 使用狀態標記,空間換時間
/// </summary>
/// <param name="nums"></param>
/// <param name="path"></param>
/// <param name="ans"></param>
public void backtrack_state(int[] nums, bool[] marked, List<int> path, IList<IList<int>> ans)
{
// 有效路徑
if (path.Count == nums.Length)
{
ans.Add(new List<int>(path)); // 拷貝操作,因為path再回溯上層會做修改
return;
}

for (int i = 0; i < nums.Length; i++)
{
// 其實可以用狀態來標記的,但是會佔用空間複雜度
if (marked[i]) continue;

marked[i] = true;
// 做選擇
path.Add(nums[i]);

// 進入下一層決策樹,遞迴下去
backtrack_state(nums, path, ans);

// 取消選擇,,移除最後選擇的路點,本層撤銷後進行下一個路徑選擇
marked[i] = false; // 狀態重置
path.RemoveAt(path.Count - 1);
}
}