回溯法(英语:backtracking)是暴力搜寻法中的一种。
回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
(1) 找到一个可能存在的正确的答案。
(2)在尝试了所有可能的分步方法后宣告该问题没有答案。
在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。(以上定义摘自wiki百科)
但是只看定义对于了解和使用的效果是比较小的,我找了几道题目进行补充理解和练习。
#include<iostream> using namespace std; int ans = 0; int m, n; void dfs(int x, int y) { if(x == n && y == m) { ans++; return; } if(x + 1 <= n) dfs(x + 1, y); if(y + 1 <= m) dfs(x, y + 1); } int main() { cin>>n>>m; dfs(1, 1); cout<<ans; return 0; }
#include<iostream> using namespace std; int n; int a[50]; void dfs(int deep) { if(deep > n) { for(int i = 1; i <= n; i++) { cout<<a[i]<<" "; } cout<<endl; return; } for(int i = 1; i <= n; i++) { a[deep] = i; dfs(deep + 1); } } int main() { cin>>n; dfs(1); return 0; }
#include<iostream> using namespace std; int n; int a[50], flag[50] = { 0 }; void dfs(int deep) { if(deep > n) { for(int i = 1; i <= n; i++) { cout<<a[i]<<" "; } cout<<endl; return; } for(int i = 1; i <= n; i++) { if( !flag[i] ) { flag[i] = 1; //标记表示 i 已经被用过 a[deep] = i; dfs(deep + 1); flag[i] = 0; //回溯 i 用过之后重新标记,表示可以再次使用 } } } int main() { cin>>n; dfs(1); return 0; }
#include<iostream> using namespace std; //由上一个程序简单改进 //n里面选m个数 便在1~n中选数 递归到m层 int n, m; int a[50], flag[50] = { 0 }; void dfs(int deep) { if(deep > m) { for(int i = 1; i <= m; i++) { cout<<a[i]<<" "; } cout<<endl; return; } for(int i = 1; i <= n; i++) { if( !flag[i] ) { flag[i] = 1; //标记表示 i 已经被用过 a[deep] = i; dfs(deep + 1); flag[i] = 0; //回溯 i 用过之后重新标记,表示可以再次使用 } } } int main() { cin>>n>>m; dfs(1); return 0; }
但是这个代码并不难达到所示图片的样例,于是就有如下的代码。
#include<iostream> using namespace std; //由上一个程序简单改进 //n里面选m个数 便在1~n中选数 递归到m层 int n, m; int a[50], flag[50] = { 0 }; void dfs(int deep) { int m_flag = 1; if(deep > m) { for(int i = 1; i < m; i++) { if(a[i] > a[i+1]) { m_flag = 0; break; } }//如果不是升序排列的话,就跳出循环,把m_flag赋值为0,不打印 if(m_flag) { for(int i = 1; i <= m; i++) { cout<<a[i]<<" "; } cout<<endl; } return; } for(int i = 1; i <= n; i++) { if( !flag[i] ) { flag[i] = 1; //标记表示 i 已经被用过 a[deep] = i; dfs(deep + 1); flag[i] = 0; //回溯 i 用过之后重新标记,表示可以再次使用 } } } int main() { cin>>n>>m; dfs(1); return 0; }
算法可以进一步优化
#include<iostream> using namespace std; //由上一个程序简单改进 //n里面选m个数 便在1~n中选数 递归到m层 int n, m; int a[50] = { 0 }; void dfs(int deep) { int flag = 1; if(deep > m) { for(int i = 1; i <= m; i++) { cout<<a[i]<<" "; } cout<<endl; return; } for(int i = a[deep - 1] + 1; i <= n; i++)//递归之后直接从已选数字的后面找数 { a[deep] = i; dfs(deep + 1); } } int main() { cin>>n>>m; dfs(1); return 0; }
题目标题: 第39级台阶
小明刚刚看完电影《第39级台阶》,离开电影院的时候,他数了数礼堂前的台阶数,恰好是39级!
站在台阶前,他突然又想着一个问题:
如果我每一步只能迈上1个或2个台阶。先迈左脚,然后左右交替,最后一步是迈右脚,也就是说一共要走偶数步。那么,上完39级台阶,有多少种不同的上法呢?
#include<iostream> using namespace std ; int sum = 0 ; void f (int n, int state) ; int main() { f (39, 1) ; cout<<sum<<endl ; return 0 ; } void f (int n, int state) { if (n == 0 && state == 1) sum++ ; else if (n > 0) { f (n-1, !state) ; f (n-2, !state) ; } }
#include<iostream> using namespace std; int n, number = 0, a[17] = { 0 }; void dfs(int deep, int nextState, int sum) { a[deep] = nextState; //赋值 if(deep >= 2 * n && sum == 0) //满足条件打印 { for(int i = 1; i <= 2 * n; i++) { cout<<a[i]<<" "; } cout<<endl; number++; return; } if(sum < 0 || deep > 2 * n) return; // 不满足条件递归返回 dfs(deep + 1, 0, sum + 1); //继续向下一层递归 ,假设下一个人是持有5元 dfs(deep + 1, 1, sum - 1); //继续向下一层递归,假设下一个人持有10元 } int main() { int sum = 0; cin>>n; switch(n)//因为不知道应该怎么先打印数目,在打印具体方案,所以只能投机取巧了 { case 1: cout<<1<<endl; break; case 2: cout<<2<<endl; break; case 3: cout<<5<<endl; break; case 4: cout<<14<<endl; break; case 5: cout<<42<<endl; break; case 6: cout<<132<<endl; break; case 7: cout<<429<<endl; break; case 8: cout<<1430<<endl; break; } dfs(1, 0, sum + 1); return 0; }