mooc北大算法课第八周:深度优先搜索(一)

本周内容居然这么快就完成了,我可太厉害辽(●’◡’●)

深度优先搜索(一)

深度优先搜索(Depth-First-Search):
从起点开始,走过的点要做标记,发现有没走过的点,就随意挑一个往前走,走不了就后退,此种路径搜索策略就称为“深度优先搜索”,简称“深搜”。

其实称为“远度优先搜索”更容易理解些。因为这种策略能往前走一步就往前走一步,总是试图走得更远。所谓远近(或深度),就是以距离起点的步数来衡量的。
在图上寻找路径
代码:

Node path[MAX_LEN];// MAX-LEN取节点总数即可
int depth;
bool Dfs(V){
    if(V为终点) {
        path[depth] = V;
        return true;
    }
    if(V为旧点)
        return false;
    将V标记为旧点;
    path[depth] = V;
    ++ depth;
    对和v相邻的每个节点U {
        if(Dfs(U) == true)
            return true;
    }
    -- depth;// 从V出发走不到终点,回退V的父节点
    return false;

int main()
{
    将所有点都标记为新点;
    depth = 0;
    if(Dfs(起点)) {
        for(int i = 0; i <= depth; ++ i)
            cout << path[i] << endl;
    }
}

遍历图上所有节点:
代码:

Dfs(V) {
    if(V是旧点)
       return;
    将v标记为旧点;
    对和V相邻的每个点U {
        Dfs(U);
    }
}
int main() {
    将所有点都标记为新点;
    while(在图中能找到新点k)
       Dfs(k);
}

图的表示方法——邻接表
用一个二维数组G存放图,G[i][j]表示节点i和节点j之间边的情况(如有无边,边方向,权值大小等)。遍历复杂度:O(n^2),n为节点数目。

每个节点V对应一个一维数组,里面存放从V连出去的边,边的信息包括另一顶点。还可能包含边权值等。遍历复杂度:O(n+e),n为节点数目,e为边数目。

例题1:城堡问题
描述:
右图是一个城堡的地形图(城堡四面都是墙).请你编写一个程序,计算城堡一共有多少房间,最大的房间多大。城堡被分割为m*n(m <= 50,n <= 50)个方块,每个方块可以有0~4面墙。

     1   2   3   4   5   6   7  
   #############################
 1 #   |   #   |   #   |   |   #
   #####---#####---#---#####---#
 2 #   #   |   #   #   #   #   #
   #---#####---#####---#####---#
 3 #   |   |   #   #   #   #   #
   #---#########---#####---#---#
 4 #   #   |   |   |   |   #   #
   #############################
           (1)

   #  = Wall
   |  = No wall
   -  = No wall

输入:

  1. 程序从标准输入设备读入数据。
  2. 第一行是两个整数,分别是南北向、东西向的方块数。
  3. 在接下来的输入行里,每个方块用一个数字(0≤p≤50)描述。用一个数字表示方块周围的墙,1表示西墙,2表示北墙,4表示东墙,8表示南墙。每个方块用代表其周围墙的数字之和表示。城堡的内墙被计算两次,方块(1,1)的南墙同时也是方块(2,1)的北墙。
  4. 输入的数据保证城堡至少有两个房间

输出:

  1. 城堡的房间数、城堡中最大房间所包括的方块数。
  2. 结果显示在标准输出设备上。

样例输入:

4
7
11 6 11 6 3 10 6
7 9 6 13 5 15 5
1 10 12 7 13 7 5
13 11 10 8 10 12 13

样例输出:

5
9

解题思路:

  1. 把方块看作是节点,相邻两个方块之间如果没有墙,则在方块之间连一条边,这样城堡就能转换成一个图。
  2. 求房间个数,实际上就是在求图中有多少个极大联通子图。
  3. 一个连通子图,往里头加任何一个图里的其他点,就会变得不连通,那么这个连通子图就是极大连通子图。
  4. 对每一个房间,深度优先搜索,从而给这个房间能够到达的所有位置染色。最后统计一共用了几种颜色,以及每种颜色的数量。
  5. 从而一共有5个房间,最大的房间(1)占据9个格子。

1 1 2 2 3 3 3
1 1 1 2 3 4 3
1 1 1 5 3 5 3
1 5 5 5 5 5 3

代码:

#include 
#include 
#include 
using namespace std;
int R,C; //行列数
int rooms[60][60]; //保存图信息
int color[60][60]; //方块是否染色过的标记
int maxRoomArea = 0;
int roomNum = 0;
int roomArea; //正在探索的房间面积
void Dfs(int i, int k) {
    if(color[i][k]) //方块被走过
        return;
    ++ roomArea;
    color[i][k] = roomNum;
    //与操作
    if((rooms[i][k] & 1) == 0) Dfs(i,k-1); //向西走
    if((rooms[i][k] & 2) == 0) Dfs(i-1,k); //向北走
    if((rooms[i][k] & 4) == 0) Dfs(i,k+1); //向东
    if((rooms[i][k] & 8) == 0) Dfs(i+1,k); //向南
}
int main()
{
    cin >> R >> C;
    for(int i = 1;i <= R; ++ i)
        for(int k = 1;k <= C;++ k)
            cin >> rooms[i][k];
    memset(color,0,sizeof(color)); //注意不要漏掉
    for(int i = 1; i <= R; ++ i)
        for(int k = 1; k <= C; ++ k) {
            if(!color[i][k]) {
                ++ roomNum;
                roomArea = 0; //每次清零
                Dfs(i,k); //一个房间探索完毕
                maxRoomArea = max(roomArea,maxRoomArea); //更新最大值
            }
        }
    cout << roomNum << endl;
    cout << maxRoomArea << endl;
    return 0;
}

例题2 踩方格:
描述:
有一个方格矩阵,矩阵边界在无穷远处。我们做如下假设:
a、每走一步时,只能从当前方格移动一格,走到某个相邻的方格上;
b、走过的格子立即塌陷无法再走第二次;
c、只能向北、东、西三个方向走;
请问:如果允许在方格矩阵上走n步,共有多少种不同的方案。2种走法只要有一步不一样,即被认为是不同的方案。

输入
允许在方格上行走的步数n(n≤20)。

输出
计算出的方案数量。

输入样例:

2

输出样例:

7

思路:
递归
从(i,j)出发,走n步的方案数,等于以下三项之和:
从(i+1,j)出发,走n-1步的方案数。前提:(i+1,j)还没走过
从(i,j+1)出发,走n-1步的方案数。前提:(i,j+1)还没走过
从(i,j-1)出发,走n-1步的方案数。前提:(i,j-1)还没走过

代码:

#include 
#include 
using namespace std;
int visited[30][50];
int ways(int i, int j, int n)
{
    if(n == 0) //不走
        return 1;
    visited[i][j] = 1; //可以走
    int num = 0;
    if(! visited[i][j-1]) num += ways(i,j-1,n-1); //向西
    if(! visited[i][j+1]) num += ways(i,j+1,n-1); //向东
    if(! visited[i+1][j]) num += ways(i+1,j,n-1); //向北
    visited[i][j] = 0; //标志清零
    return num;
}
int main()
{
    int n;
    cin >> n;
    memeset(visited,0,sizeof(visited));
    cout << ways(0,25,n) << endl;
    return 0;
}

课后练习1:红与黑
描述:
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

输入:
包括多个数据集合。每个数据集合的第一行是两个整数W和H,分别表示x方向和y方向瓷砖的数量。W和H都不超过20。在接下来的H行中,每行包括W个字符。每个字符表示一块瓷砖的颜色,规则如下
1)‘.’:黑色的瓷砖;
2)‘#’:白色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
当在一行中读入的是两个零时,表示输入结束。

输出:
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

样例输入:

6 9
…#.
…#





#@…#
.#…#.
0 0

样例输出:

45

代码:

#include 
#include 
using namespace std;
char maze[30][30];
int visited[30][30];
int area = 0;
int W,H;
void dfs(int r,int c)
{
    if( r < 0|| c < 0 || r >= H || c >= W ) //如果坐标不在范围内
        return;
    if( maze[r][c] == '#' || visited[r][c]) //如果瓷砖为白色或者已经走过
        return;
    ++area;
    visited[r][c] = 1;
    dfs(r+1,c); //向南
    dfs(r-1,c); //向北
    dfs(r,c+1); //向东
    dfs(r,c-1); //向西
}

int main()
{
    while(1) {
        cin >> W >> H;
        if( W == 0 && H == 0)
            break;
        memset(visited,0,sizeof(visited));
        area = 0;
        int sr,sc;
        for(int i = 0;i < H; ++ i)
            for(int j = 0; j < W; ++ j) {
                cin >> maze[i][j];
                if( maze[i][j] == '@') {
                    sr = i;
                    sc = j;
                }
            }
        dfs(sr,sc);
        cout << area << endl;
    }
    return 0;
}

课后练习2: A Knight’s Journey
描述:
Background:
The knight is getting bored of seeing the same black and white squares again and again and has decided to make a journey around the world. Whenever a knight moves, it is two squares in one direction and one square perpendicular to this. The world of a knight is the chessboard he is living on. Our knight lives on a chessboard that has a smaller area than a regular 8 * 8 board, but it is still rectangular. Can you help this adventurous knight to make travel plans?
Problem:
Find a path such that the knight visits every square once. The knight can start and end on any square of the board.

输入:
The input begins with a positive integer n in the first line.
The following lines contain n test cases. Each test case consists of a single line with two positive integers p and q, such that 1 <= p * q <= 26.

This represents a p * q chessboard, where p describes how many different square numbers 1, . . . , p exist, q describes how many different square letters exist. These are the first q letters of the Latin alphabet: A, . . .

第一行输入样例个数,剩下行数输入棋盘行数和列数。

输出:
The output for every scenario begins with a line containing “Scenario #i:”, where i is the number of the scenario starting at 1. Then print a single line containing the lexicographically first path that visits all squares of the chessboard with knight moves followed by an empty line. The path should be given on a single line by concatenating the names of the visited squares. Each square name consists of a capital letter followed by a number.
If no such path exist, you should output impossible on a single line.

输出走完全盘的路径。

样例输入:

3
1 1
2 3
4 3

样例输出:

Scenario #1:
A1

Scenario #2:
impossible

Scenario #3:
A1B3C1A2B4C2A3B1C3A4B2C4

解题思路:
注意骑士的走法为“日字型”,即:
一 左走一格向前走两格,二 左走一格向后走两格,
三 左走两格向前走一格,四 左走两格向后走一格,
五 右走一格向前走两格,六 右走一格向后走两格,
七 右走两格向前走一格,八 右走两格向后走一格。

代码:

#include 
#include 
#include 
using namespace std;
int visited[30][30];
struct Pos
{
    int r,c;
};
Pos path[30];
Pos dir[8] = {{-2,-1},{-2,1},{-1,-2},{-1,2},
            {1,-2},{1,2},{ 2,-1},{2,1}};
int p,q;
bool Dfs(int r, int c,int step)
{//从 (r,c)出发,此时已经走了step步,看能否成功
    if( step == p * q )
        return true;
    if( r < 0 || r >= q || c < 0 || c >= p )
        return false;
    if( visited[r][c] )
        return false;
    visited[r][c] = 1;
    path[step].r = r;
    path[step].c = c;
    for( int i = 0;i < 8; ++ i ) {
        if ( Dfs( r + dir[i].r,c + dir[i].c,step + 1 ))
            return true;
    }
    visited[r][c] = 0; //回溯,取消这一步的走法,使得走其他步的时候,能绕回到这里
    return false;
}
int main()
{
    int t;
    cin >> t;
    for( int tt = 1; tt <= t; ++ tt ) {
        cout << "Scenario #" << tt << ":" << endl;
        cin >> p >> q; //p行q列
        memset(visited,0,sizeof(visited));
        int i;
        for(  i = 0; i < q; ++ i )
            for( int j = 0; j < p; ++ j ) { //注意i,j,p,q的对应关系
                if (Dfs(i,j,0)) {
                    i = q + 10; //用于区分是否走完全程
                    for( int k = 0; k < p * q; ++ k)
                        cout << char (path[k].r + 'A')
                         << (path[k].c + 1) ;
                    break;
                }
            }
        if( i == q ) //未走完全程
            cout << "impossible";
        cout << endl << endl;
    }
    return 0;
}

课后练习3: 棋盘问题:
描述:
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。
要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列。
请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。

输入:
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。

输出:
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C < 2^31)。

输入样例:

2 1
#.
.#
4 4
…#
…#.
.#…
#…
-1 -1

输出样例:

2
1

解题思路:
参考https://blog.csdn.net/xcflytosky/article/details/29222791

  1. 本题目可以某一行都不放棋子,因此,需要有个标识位来判断某一行是否放了棋子,对于某一行没放棋子的情况,则对应于Dfs(row + 1, k),对于某一行放了棋子的情况,则对应于Dfs(row + 1, k - 1),其中DFS(row, k)表示考察在行row放棋子的情况(放,则考虑放哪一列,不放,则要从下一行开始放),k表示还有k个棋子要放。
  2. 回溯的写法,即收回行row某一列的棋子,状态位要做出相应改变。

代码:

#include 
#include 
#include 
using namespace std;
int n,k;
char board[10][10];
int total;
int chessPos[10];
int Dfs(int r,int x)  //注意不能采用记忆化递归,因为不满足无后效性
{//从第r行开始摆,还有 x个棋子未摆
    if( x == 0)
        return 1;
    if( n - r < x ) //肯定摆不下了
        return 0;
    total = 0;
    for( int j = 0; j < n; ++ j ) { //要摆放第r行棋子,则遍历列,对原有棋盘进行调整
        if(  board[r][j] == '#' ) { //寻找棋子
            int i;
            for( i = 0; i < r; ++ i ) //前几行的棋子有无摆放重复列
                if( chessPos[i] == j )
                    break;
            if( i == r ) {
                chessPos[r] = j;
                total += Dfs(i+1,x-1);
            }
        }
    }
    // 不摆放第r行棋子,状态位清空,跳到下一行
    chessPos[r] = -1;
    total += Dfs(r+1,x);
    return total;
}
int main()
{
    while( true) {
        cin >> n >> k;
        if( n == -1 )
            break;
        total = 0;
        memset(chessPos,0xff,sizeof(chessPos));
        for( int i = 0;i < n; ++i)
            for(int j = 0; j < n; ++j )
                cin >> board[i][j];
        cout << Dfs(0,k) << endl; //注意函数返回值
    }
    return 0;
}

你可能感兴趣的:(c++程序设计与算法)