最近有在leetcode上面做算法題,已經遇到了兩道回溯算法的題目,感覺一點思路都沒有,現決定將java如何實現回溯算法做一次總結。
一、什么叫做回溯算法
(摘抄於百度百科)
回溯算法實際上一個類似枚舉的搜索嘗試過程,主要是在搜索嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就“回溯”返回,嘗試別的路徑。回溯法是一種選優搜索法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術為回溯法,而滿足回溯條件的某個狀態的點稱為“回溯點”。許多復雜的,規模較大的問題都可以使用回溯法,有“通用解題方法”的美稱。
(摘抄於他人博客)
在包含問題的所有解的解空間樹中,按照深度優先搜索的策略,從根結點出發深度探索解空間樹。當探索到某一結點時,要先判斷該結點是否包含問題的解,如果包含,就從該結點出發繼續探索下去,如果該結點不包含問題的解,則逐層向其祖先結點回溯。(其實回溯法就是對隱式圖的深度優先搜索算法)。 若用回溯法求問題的所有解時,要回溯到根,且根結點的所有可行的子樹都要已被搜索遍才結束。 而若使用回溯法求任一個解時,只要搜索到問題的一個解就可以結束。
二、如何使用回溯算法
回溯我認為也就是一種遞歸,有以下四個參數,當然不一定是我所舉例的類型,要看題目而定
一個全局變量集合保存所有滿足條件的答案,舉例:List<List<Integer>> list
一個集合保存一個滿足條件的答案,舉例:List<Integer> tempList
算法問題給所給的輸入條件,這個可能是一個字符串,也可能是一個數組
附加參數(可有可無,視題目而定)
下面具體以leetcode上的一道題目為例,這樣能更好的理解
import java.util.ArrayList; import java.util.List; public class Permutations { //題目描述:Given a collection of distinct integers, return all possible permutations.(給定一組不同的整數,返回其所有的可能組合) public List<List<Integer>> permute(int[] nums) { //一個全局變量,用於保存所有集合 List<List<Integer>> list = new ArrayList<>(); //傳入三個參數,沒有附加參數 backtrack(list, new ArrayList<>(), nums); return list; } private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums){ //一個終結條件,也就是滿足條件的時候 if(tempList.size() == nums.length){ //全局變量添加一個滿足條件的集合 list.add(new ArrayList<>(tempList)); } else{ for(int i = 0; i < nums.length; i++){ if(tempList.contains(nums[i])) continue; //如果tempList沒有包含nums[i]才添加 tempList.add(nums[i]); //遞歸調用,此時的tempList一直在變化,直到滿足終結條件才結束 backtrack(list, tempList, nums); System.out.println("tempList的內容:"+tempList+"-------"+"i的值:"+i); //它移除tempList最后一個元素的作用就是返回上一次調用時的數據,也就是希望返回之前的節點再去重新搜索滿足條件。這樣才能實現回溯 tempList.remove(tempList.size() - 1); } } } public static void main(String[] args){ int[] nums={1,2,3}; (new Permutations()).permute(nums); } }
main方法測試,輸出語句的結果顯示如下,可以觀察出回溯的過程
tempList的內容:[1, 2, 3]-------i的值:2
tempList的內容:[1, 2]-------i的值:1
tempList的內容:[1, 3, 2]-------i的值:1
tempList的內容:[1, 3]-------i的值:2
tempList的內容:[1]-------i的值:0
tempList的內容:[2, 1, 3]-------i的值:2
tempList的內容:[2, 1]-------i的值:0
tempList的內容:[2, 3, 1]-------i的值:0
tempList的內容:[2, 3]-------i的值:2
tempList的內容:[2]-------i的值:1
tempList的內容:[3, 1, 2]-------i的值:1
tempList的內容:[3, 1]-------i的值:0
tempList的內容:[3, 2, 1]-------i的值:0
tempList的內容:[3, 2]-------i的值:1
tempList的內容:[3]-------i的值:2
我對這道題目的理解是從這個for循環的i=0開始就不斷遞歸,然后在不斷消除集合最末尾元素進行回溯,直到回溯完成。這樣就產生了首位元素是1的各種情況。以此類推,之后再開始for循環i=1,產生首位元素是2的各種情況。
形象點就是一個根結點有1,2,3三個元素,第一次從1出發,尋找最低端滿足條件的結點,然后再返回上一個結點尋找滿足條件的結點,直到返回1,返回1之后再從2開始,以此類推。
leetcode上面有關回溯算法的一些題目的實現可查看這個鏈接:https://leetcode.com/problems/permutations/discuss/18239/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partioning)