回溯算法理解
深度优先搜索
基本思想:将解的空间,转化为了树或者图的表示,进行深度优先搜索,并加以剪枝,在遍历过程中找到所有最优解或者可行解
回溯算法的求解过程实质上是一个先序遍历一棵"状态树"的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中
当遍历某分支的时候,若发现条件不满足,则退回到根节点进入下一个分支的遍历。这就是“回溯”这个词的来源。而根据条件有选择的遍历,叫做剪枝或分枝定界
伪代码
总结一下,伪代码就是:
void dfs(层数)
{
if(条件)
{
输出;
}
else
{
左子树的处理;
dfs(层数+1);
右子树的处理;
dfs(层数+1);
}
}
详细描述
回溯法按深度优先策略搜索问题的解空间树。
- 首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。
- 如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
- 回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。
- 剪枝函数包括两类:
- 使用约束函数,剪去不满足约束条件的路径;
- 使用限界函数,剪去不能得到最优解的路径。
框架
bool finished = FALSE; /* 是否获得全部解? */
backtrack(int a[], int k, data input)
{
int c[MAXCANDIDATES]; /*这次搜索的候选 */
int ncandidates; /* 候选数目 */
int i; /* counter */
if (is_a_solution(a,k,input))
process_solution(a,k,input);
else {
k = k+1;
construct_candidates(a,k,input,c,&ncandidates);
for (i=0; i<ncandidates; i++) {
a[k] = c[i];
make_move(a,k,input);
backtrack(a,k,input);
unmake_move(a,k,input);
if (finished) return; /* 如果符合终止条件就提前退出 */
}
}
}
例一
Description
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
Input
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
Output
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
Sample Input
2 1
.
.#
4 4
...#
..#.
.#..
...
-1 -1
Sample Output
2
1
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n, k;
bool maze[9][9];
int column[9] = {0};
int ans = 0;
//当前为第cur层,还剩下k个可以走
void dfs(int cur,int k)
{
if(cur == n+1 || k == 0)
{
if( k == 0)
ans++;
return ;
}
//不选择cur层
dfs(cur+1,k);
//选择cur层
for(int i = 1; i <= n ; i++)
{
if(maze[cur][i] == 1 && !column[i])
{
column[i] = 1;
dfs(cur+1,k-1);
//恢复现场
column[i] = 0;
}
}
}
int main()
{
freopen("in.txt","r",stdin);
while(cin >> n >> k)
{
memset(maze,0,sizeof(maze));
memset(column,0,sizeof(column));
if(n == -1 && k == -1)
break;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
char ch;
scanf("%c",&ch);
if(ch == '
')
scanf("%c",&ch);
if(ch == '#')
maze[i][j] = 1;
}
}
ans = 0;
dfs(1,k);
cout << ans << endl;
}
return 0;
}
例二
求一个集合的全部子集
#include <iostream>
#include <cstring>
using namespace std;
int n;
int A[20],vis[20];
void print_subset(int n, int *vis,int cur)
{
if(cur == n)
{
for(int i = 0 ; i < n ; i++)
{
if(vis[i])
cout << A[i] << "";
}
cout << endl;
return ;
}
vis[cur] = 1;
print_subset(n,vis,cur+1);
vis[cur] = 0;
print_subset(n,vis,cur+1);
}
int main()
{
int n;
cin >> n;
for(int i = 0; i < n ; i++)
cin >> A[i];
memset(vis,0,sizeof(vis));
print_subset(n,vis,0);
return 0;
}
输出不重复数字的全排列
代码如下:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int N;
void permutaion(int *arr, int k,int N)
{
int i;
if(N == k)
{
for(i = 0; i < N ; i++)
cout << arr[i] << " ";
cout << endl;
return ;
}
for(i = k; i <N ; i++)
{
swap(arr[i],arr[k]);
permutaion(arr,k+1,N);
swap( arr[i],arr[k]);
}
}
int main()
{
cin >> N;
int arr[100];
for(int i = 0 ; i < N ;i++)
arr[i] = i+1;
permutaion(arr,0,N);
return 0;
}