搜索

DFS三大经典问题

递归实现指数型枚举

题目:

从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式
输入一个整数 n。

输出格式
每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围
1≤n≤15
输入样例:
3
输出样例:

3
2
2 3
1
1 3
1 2
1 2 3

分析:

递归实现指数型枚举:
所实现的功能就是,面对当前数的时候,做出了两种选择,选或不选。
而且,我们是从前往后来的,具有顺序性(升序筛选),所得的序列都是递增的。
选和不选,当前数都已经走过了,不会重复第二次面临选择的情况

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>

using namespace std;

int n;
vector<int> v;
void dfs(int u)
{
    if (u > n)
    {
        for (auto it : v)
            cout << it << ' ';
        cout << endl;
        return;
    }
    
    dfs(u + 1);
    v.push_back(u);
    dfs(u + 1);
    v.pop_back();
}
int main()
{
    cin >> n;
    
    dfs(1);
    
    return 0;
}

递归实现组合型枚举

题目:

从 1∼n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

输入格式
两个整数 n,m ,在同一行用空格隔开。

输出格式
按照从小到大的顺序输出所有方案,每行 1 个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7 排在 1 3 6 8 前面)。

数据范围
n>0 ,
0≤m≤n ,
n+(n−m)≤25
输入样例:
5 3
输出样例:
1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5 
思考题:如果要求使用非递归方法,该怎么做呢?

分析:

这个题,和上个题差距不大,只是多了一个长度限制。
不过,我们可以在传参的时候,在搜索状态空间的时候,多加一个状态,就是start,起始位置。  

代码1:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>

using namespace std;

int n, k;
vector<int> v;
void dfs(int u)
{
    if (u > n)
    {   
        if (v.size() != k) return;
        for (auto it : v)
            cout << it << ' ';
        cout << endl;
        return;
    }
    
    v.push_back(u);
    dfs(u + 1);
    v.pop_back();
    dfs(u + 1);
}
int main()
{
    cin >> n >> k;
    
    dfs(1);
    
    return 0;
}

代码2:

#include <iostream>
#include <vector>

using namespace std;

int n, k;
vector<int> v;

void dfs(int u, int start)
{
    if (u == k)
    {
        for (int i = 0; i < v.size(); i ++ )
            cout << v[i] << " " ;
        cout << endl;
        return;
    }
    
    for (int i = start + 1; i <= n; i ++ )
    {
        v.push_back(i);
        dfs(u + 1, i);
        v.pop_back();
    }
}
int main()
{
    cin >> n >> k;
    
    dfs(0, 0);
    
    return 0;
}  

递归实现排列型枚举

题目:

把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式
一个整数 n。

输出格式
按照从小到大的顺序输出所有方案,每行 1 个。

首先,同一行相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

数据范围
1≤n≤9
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1 

分析:

递归实现排列型枚举
当前数选过后,对于本递归搜索子树来说,不需要在此使用,但是对于其它子树来说,却需要重新使用。

代码:

#include <iostream>
#include <vector>

using namespace std;

int n;
vector<int> v;
bool st[10];

void dfs(int u)
{
    if (u == n)
    {
        for (int x : v)
            cout << x << ' ';
        cout << endl;
        return;
    }
    
    for (int i = 1; i <= n; i ++ )
        if (!st[i])
        {
            st[i] = true;
            v.push_back(i);
            dfs(u + 1);
            v.pop_back();
            st[i] = false;
        }
}
int main()
{
    cin >> n;
    
    dfs(0);
    
    return 0;
}

  

DFS

 DFS大致可以分为两种,一种内部搜索,另一种为外部搜索。

内部搜索,就像是在期盼内部搜索,寻找一条通路、或者搜索有多少个格子之类的,它在格子内部搜索,而且搜索过后,当前各点的状态是不会再发生变化的。

外部搜索,一般为方案数,需要棋盘自身的状态发生变化,每一个格点都有可能发生变化,从而会对全局产生影响。这个时

候,为了解决一个格点对应多状态的情况,需要恢复现场的操作。

(1):分排列型枚举(先后顺序对结果有影响)、组合型枚举(对先后顺序没影响,如集合元素)

如果是排列型枚举:

如果是组合型枚举:可以在dfs内,加一个start,表示从第几个数开始搜索

外部搜索:

n-皇后问题 

题目:

n− 皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

1_597ec77c49-8-queens.png

现在给定整数 n,请你输出所有的满足条件的棋子摆法。

输入格式
共一行,包含整数 n。

输出格式
每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围
1≤n≤9
输入样例:
4
输出样例:
.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

代码:

#include <cstdio>

using namespace std;

const int N = 10;

int n, k;
char g[N][N];
int col[N], dg[N], udg[N];

void dfs(int u)
{
    if (u == n)//当u=n的时候,说明前面从0到n-1这n行都排列好了,那就可以输出方案了
    {
        for (int i = 0; i < n; i ++ ) printf("%s
", g[i]);
        puts("");
        return;
    }
    
    for (int i = 0; i < n; i ++ ) 
        if (!col[i] && !dg[i + u] && !udg[u - i + n]) //如果当前行,列,两个斜线都没有皇后
        {
            col[i] = dg[i + u] = udg[u - i + n] = true;
            g[u][i] = 'Q';
            dfs(u + 1);
            //恢复现场
            g[u][i] = '.';
            col[i] = dg[i + u] = udg[u - i + n] = false;
        }
}
int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i ++ ) 
        for (int j = 0; j < n; j ++ ) 
            g[i][j] = '.';
    dfs(0);
    
    return 0;
}

棋盘问题

题目:

棋盘问题
Time Limit: 1000MS		Memory Limit: 10000K
Total Submissions: 109089		Accepted: 49235
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 <cstdio>

using namespace std;

const int N = 10;

int n, k;
char g[N][N];
bool col[N];
int ans;

void dfs(int u, int cnt)
{

  if (u > n) return;//已经搜了n层,结束了
  if (cnt == k)
  {
    ans ++;
    return;
  }

  //不在这一层选,直接进入下一层
  dfs(u + 1, cnt);

  for (int i = 0; i < n; i ++ )//枚举u这一行的每一列
    if (!col[i] && g[u][i] == '#')
    {
      col[i] = true;
      dfs(u + 1, cnt + 1);
      col[i] = false;
    }
}

int main()
{
  while (scanf("%d%d", &n, &k), ~n || ~k)
  {
    for (int i = 0; i < n; i ++ )
      scanf("%s", g[i]);
    for (int i = 0; i <= n; i ++ ) col[i] = 0;

    ans = 0;
    dfs(0, 0);

    printf("%d
", ans);
  }
}

  

BFS

BFS可以在入队的时候判重 ,dijkstra可以在出队的时候判重,A*不能判重

A*算法要保证有解才可以用,不然搜索空间可能会比直接用BFS还要大呢。

模板题:844. 走迷宫

题目:

给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。

最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。

数据保证 (1,1) 处和 (n,m) 处的数字为 0,且一定至少存在一条通路。

输入格式
第一行包含两个整数 n 和 m。

接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。

输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。

数据范围
1≤n,m≤100
输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
8 

分析:

BFS搜索:
所有BFS问题要保证两点:单调性、两段性
每次将队头取出,将可达状态全部拓展、并将产生的新的状态插入队列,等待更新

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

typedef pair<int, int> PII;

#define x first
#define y second

const int N = 110;

int n,m ;
int g[N][N];
int dist[N][N];

void bfs()
{
    memset(dist, -1, sizeof dist);
    dist[1][1] = 0;
    queue<PII> q;
    q.push({1, 1});
        
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    
    while (q.size())
    {
        PII t = q.front();
        q.pop();
        // printf("%d   %d
", t.x, t.y);
        for (int i = 0; i < 4; i ++ )
        {
            int x = t.x + dx[i], y = t.y + dy[i];
            if (x <= 0 || x > n || y <= 0 || y > m) continue;
            if (dist[x][y] != -1) continue;
            if (g[x][y]) continue;
            // printf("%d   %d
", x, y);
            dist[x][y] = dist[t.x][t.y] + 1;
            q.push({x, y});
        }
        
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) 
        for (int j = 1; j <= m; j ++ ) 
            scanf("%d", &g[i][j]);
            
    bfs();
    
    printf("%d
", dist[n][m]);
    
    return 0;
}

 广搜显威题目:

八数码

题目:

在一个 3×3 的网格中,1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3×3 的网格中。

例如:

1 2 3
x 4 6
7 5 8
在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 x
例如,示例中图形就可以通过让 x 先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x
现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。

输入格式
输入占一行,将 3×3 的初始网格描绘出来。

例如,如果初始网格如下所示:

1 2 3 
x 4 6 
7 5 8 
则输入为:1 2 3 x 4 6 7 5 8

输出格式
输出占一行,包含一个整数,表示最少交换次数。

如果不存在解决方案,则输出 −1。

输入样例:
2  3  4  1  5  x  7  6  8
输出样例
19

分析:

我的想法就是将x位置的数进行四个方向的交换
如果已经出现过这个状态,就continue,如果没有就加入到队列中,
所有状态也就是9!个,不多。每次查找x的位置,然后进行转换
和那个魔板博客第二题】那题很像.

代码:(这个题以前的时候搁浅了好久,这次自己想,自己写,给写出来了,鸣炮,奏乐~~~)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <queue>

using namespace std;

char g[3][3];
unordered_map<string, int> dist;

void set(string s)
{
    for (int i = 0, t = 0; i < 3; i ++ ) 
        for (int j = 0; j < 3; j ++ ) 
            g[i][j] = s[t ++ ];

    // cout << g[0][0] << g[0][1] << g[0][2] << endl;
    // cout << g[1] << endl;
    // cout << g[2] << endl;
    // for (int i = 0; i < 3; i ++ ) 
    // {
    //     for (int j = 0; j < 3; j ++ ) 
    //         printf("%c", g[i][j]);
    //     puts("");
    // }
}

string get()
{
    string s;
    for (int i = 0, t = 0; i < 3; i ++ ) 
        for (int j = 0; j < 3; j ++ ) 
            s += g[i][j];
    // cout << s << endl;
    return s;
}

string Up(string s, int x, int y)
{
    set(s);
    swap(g[x][y], g[x - 1][y]);
    return get();
}

string Right(string s, int x, int y)
{
    set(s);
    swap(g[x][y], g[x][y + 1]);
    return get();
}

string Down(string s, int x, int y)
{
    set(s);
    swap(g[x][y], g[x + 1][y]);
    return get();
}

string Left(string s, int x, int y)
{
    set(s);
    swap(g[x][y], g[x][y - 1]);
    return get();
}

int bfs(string start, string end)
{
    if (start == end) return 0;
    
    queue<string> q;
    q.push(start);
    dist[start] = 0;
    
    // int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    while (q.size())
    {
        string t = q.front();
        q.pop();
        
        int x, y; 
        for (int i = 0; i < t.size(); i ++ ) 
            if (t[i] == 'x') x = i / 3, y = i % 3;
        
        //四个操作没问题,检查过了
        string m[4];
        if (x > 0) m[0] = Up(t, x, y);
        if (y < 2) m[1] = Right(t, x, y);
        if (x < 2) m[2] = Down(t, x, y);
        if (y > 0) m[3] = Left(t, x, y);
        
        // puts("--------------");
        // for (int i = 0; i < 4; i ++ ) cout << m[i] << endl;
        // puts("[------------]");
        for (int i = 0; i < 4; i ++ )
            if (!dist.count(m[i]))
            {
                dist[m[i]] = dist[t] + 1;
                q.push(m[i]);
                if (m[i] == end) return dist[m[i]];
            }
    }
    return -1;
}

int main()
{
    string start, end;
    end = "12345678x";
    for (int i = 1; i <= 9; i ++ )
    {
        int c; scanf("%c ", &c);
        start += c;
    }

    // set(start);
    // get();
    
    printf("%d
", bfs(start, end));
    
    // cout << s << endl;
    
    return 0;
}

  

原文地址:https://www.cnblogs.com/Iamcookieandyou/p/14728052.html