一、什么是回溯算法
回溯算法實際上一個類似枚舉的搜索嘗試過程,主要是在搜索嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就“回溯”返回,嘗試別的路徑。許多復雜的,規模較大的問題都可以使用回溯法,有“通用解題方法”的美稱。
回溯算法實際上一個類似枚舉的深度優先搜索嘗試過程,主要是在搜索嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就“回溯”返回(也就是遞歸返回),嘗試別的路徑。
二、回溯算法思想
回溯法一般都用在要給出多個可以實現最終條件的解的最終形式。回溯法要求對解要添加一些約束條件。總的來說,如果要解決一個回溯法的問題,通常要確定三個元素:
1、選擇。對於每個特定的解,肯定是由一步步構建而來的,而每一步怎么構建,肯定都是有限個選擇,要怎么選擇,這個要知道;同時,在編程時候要定下,優先或合法的每一步選擇的順序,一般是通過多個if或者for循環來排列。
2、條件。對於每個特定的解的某一步,他必然要符合某個解要求符合的條件,如果不符合條件,就要回溯,其實回溯也就是遞歸調用的返回。
3、結束。當到達一個特定結束條件時候,就認為這個一步步構建的解是符合要求的解了。把解存下來或者打印出來。對於這一步來說,有時候也可以另外寫一個issolution函數來進行判斷。注意,當到達第三步后,有時候還需要構建一個數據結構,把符合要求的解存起來,便於當得到所有解后,把解空間輸出來。這個數據結構必須是全局的,作為參數之一傳遞給遞歸函數。
三、遞歸函數的參數的選擇,要遵循四個原則
1、必須要有一個臨時變量(可以就直接傳遞一個字面量或者常量進去)傳遞不完整的解,因為每一步選擇后,暫時還沒構成完整的解,這個時候這個選擇的不完整解,也要想辦法傳遞給遞歸函數。也就是,把每次遞歸的不同情況傳遞給遞歸調用的函數。
2、可以有一個全局變量,用來存儲完整的每個解,一般是個集合容器(也不一定要有這樣一個變量,因為每次符合結束條件,不完整解就是完整解了,直接打印即可)。
3、最重要的一點,一定要在參數設計中,可以得到結束條件。一個選擇是可以傳遞一個量n,也許是數組的長度,也許是數量,等等。
4、要保證遞歸函數返回后,狀態可以恢復到遞歸前,以此達到真正回溯。
四、學習例題
1.給出 n 代表生成括號的對數,請你寫出一個函數,使其能夠生成所有可能的並且有效的括號組合。
例如,給出 n = 3,生成結果為:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
2.思路
首先利用回溯枚舉出所有括號的可能性,然后進行判斷是否符合要求(n對並且是有效的括號組合),就添加到list表格中。
對於遞歸函數變量需要全局變量列表list:用來存儲符合要求的括號組合。
局部變量temp:表示當前函數的括號組成樣式。
計數器x:判斷遞歸次數,限制其底界。
總的形成括號對數n。
3.代碼(力扣中國第22題)
public List<String> generateParenthesis(int n) { List<String> list=new ArrayList<String>(); add_list(list,"(", 1, n*2); return list; } //書寫遞歸函數 public void add_list(List<String> list,String temp,int x,int n) { x++; if(x<=n) { add_list(list,temp+"(",x,n); add_list(list,temp+")",x,n); } if(x>n) { //在這里寫判斷條件是否負荷有效的括號組合 char[] k=temp.toCharArray(); //計數器 int timer=0; for(int i=0;i<k.length;i++) { if(timer<0||timer>n/2) { return; } else { if(k[i]=='(') { timer++; }else { timer--; } } } if(timer==0) list.add(temp); } }