回溯算法

回溯算法理解

深度优先搜索

基本思想:将解的空间,转化为了树或者图的表示,进行深度优先搜索,并加以剪枝,在遍历过程中找到所有最优解或者可行解

回溯算法的求解过程实质上是一个先序遍历一棵"状态树"的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中

当遍历某分支的时候,若发现条件不满足,则退回到根节点进入下一个分支的遍历。这就是“回溯”这个词的来源。而根据条件有选择的遍历,叫做剪枝或分枝定界

伪代码

总结一下,伪代码就是:

void dfs(层数)
{
    if(条件)
    {
        输出;
    }
    else
    {
        左子树的处理;
        dfs(层数+1);
        右子树的处理;
        dfs(层数+1);
    }
}

详细描述

回溯法按深度优先策略搜索问题的解空间树。

  • 首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。
  • 如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
  • 回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。
  • 剪枝函数包括两类:
  1. 使用约束函数,剪去不满足约束条件的路径;
  2. 使用限界函数,剪去不能得到最优解的路径。

框架

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;
}

原文地址:https://www.cnblogs.com/pprp/p/7638043.html