zoukankan      html  css  js  c++  java
  • 搜索

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

      

  • 相关阅读:
    java判断两集合是否相同以及求取交集,并集,差集
    鼠标样式的属性记录
    Hadoop----hdfs dfs常用命令的使用
    时间戳与Date类型转换
    ajax提交参数(2)
    嵌入式实验一
    11.21
    在别人的博客里摘来的 先存我这里
    算法分析与设计实验一
    将ASCII码表示的十进制数转换为二进制数(汇编语言)
  • 原文地址:https://www.cnblogs.com/Iamcookieandyou/p/14728052.html
Copyright © 2011-2022 走看看