算法 | 广度优先搜索(BFS)和深度优先搜索(DFS)理论与深入实践

        搜索算法不仅在实际应用中非常重要,也是各大公司编程笔试题目重头戏,本文聚焦于两大经典的图算法:广度优先搜索(Breadth First Search)和深度优先搜索(Depth First Search)。我在学习数据结构的时候初识这两种算法,后来在算法图解一书也看到了形象的介绍,虽然算法的概念不难但是因为足够的实践经验,理解不够深刻。现在,本文将对这两种经典算法进行简单的介绍,着重于算法的应用举例。一者是对自己学习过程的总结与整理,二者希望给读者以帮助,通过一些实例快速掌握这两大算法。      


理论部分

  • 广度优先搜索 

根据英文可以知道,也可被称为宽度优先搜索。是连通图的一种遍历算法,很多重要的图算法,如Dijkstra等都采用了类似的思想。广度优先也可以形象的理解为一种“按层递进”的搜索算法,一般通过队列这种数据结构来实现。 如下图

(1)入队a; (2)如队不为空,出队元素a,如果a有子节点同时入队a的子节点b c; (3)队不为空,出队b,同时入队b的子节点d e;(4)继续出队c,同时入队c的子节点f g;(5)出队d; (6)出队e,同时入队h; (7)出队f;(8)出队g;(9)出队h。

从第一层直到最后一层,BFS完美的实现了按层搜索的功能。当然这是以二叉树为例,二叉树是图的一种特例。如果是一般的图的话,出队X元素的时候同时入队X的下一邻接点,一直到队列为空搜索完成。

广度优先搜索常用于求最优解(最短时间、最短路径等等)。

具体思想:从图中某顶点a出发,在访问了a之后依次访问a的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,访问即出队,出队a节点的同时入队a的邻接点。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

  • 深度优先搜索  

如下图

深度搜索如果从A节点开始,便一直不断向邻接点搜索,上图中画了箭头,实际应为无向图。当前路径搜索到最后,无下一邻接点即可走的时候,开始回溯,再次重复上述过程,直到遍历图中所有的点。因此深度搜索的路径是不唯一的。 一般实现的时候会用到栈或者递归。

深度优先搜索不需要最优的特性,可用于求解有没有的问题,但是有时候也可以用于求解最优问题,比如迷宫问题的最短路径,DFS可以搜索所有的可能路径,不断比较路径长度更新min即可。    

关于理论,有一篇博客图比较多,简单易懂:https://www.jianshu.com/p/bff70b786bb6 

理论是前菜,但是硬菜还是Coding!我们拭目以待@@  代码基本上是经我调试好的            


实践部分

例1

首先介绍经典的迷宫问题,分别用BFS和DFS求解。 

题目描述: 

一个二维数组表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路径。(注意我们的初级版本是求解最少步数,高级版本是最少步数加所走的路径,其中DFS我们实现了求解最少步数,没有输出路径,你可以自己实现)

Input

一个5 × 5的二维数组,表示一个迷宫。数据保证有唯一解。

Output

最少步数。

Sample Input

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

Sample Output

(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)

下面我们先来介绍用广度优先搜索BFS求解。

//求解迷宫问题:输入为5*5二维数组 1表示墙壁 0表示通路 找出从左上角到右上角的最短通路

//广度优先搜索
//思路:搜索的顺序用队列来实现,用数组实现队列的功能,出队一个节点的同时将其后继节点全部入队
//层层推进,另外考虑记录点的坐标和离出发点的距离

//高级版本,输出最短路径长度 和相应的行走路径


#include <iostream>
//#include <string>
#include <cmath>
//#include <limits.h>
#include <vector>
//#include <queue>  //队列
//#include <deque>  //双端队列
//#include <set>
//#include <map>
//#include <unordered_set>  //这些都是算法题目中经常会用到的一些数据结构
//#include <cstring>

using namespace std;

char mark[25][25];   //记录走过的点,这里Mark如果没有初始化的话默认都是零
int min = 26;        //能抵达终点的最短步数
int ways = 0;        //走法数

int dir[4][2] = {   //四个方向
        {1,0}, {0,1}, {-1,0}, {0,-1}
};

struct pt {
    int x;
    int y;
    int step;
    pt * next;    
};   //定义节点的结构体

/*另一种定义方式
typedef strcut {
    int x;
    int y;
    int step;
} pt;     */

pt ptQueue[25];    //注意不用与系统的已有名重复
pt ptWay[25];
int front = 0, rear = 0;   //用这两个标识来判断队列中是否还有元素
int num = 0;

void DFS( int map[5][5] )    ///二维数组传递时 必须指定第二维的维度
{
    int i;
    int tx, ty;
    int flag = 0;  //判断是否找到终点

    //将出发点入队 也就是(0,0)
    ptQueue[rear].x = 0;
    ptQueue[rear].y = 0;
    ptQueue[rear].step = 0;   //出发点的起始步数为0
    ptQueue[rear].next = nullptr;  //将第一个节点next置为空   

    rear++;
    while( front < rear )
    {
        for(i = 0; i < 4; i++)   //先入队再出队,遍历当前节点的四个方向即下一层节点;
        {
            //1. 先确定下一层节点的位置
            tx = ptQueue[front].x + dir[i][0];  //tx是行坐标 ty是列坐标
            ty = ptQueue[front].y + dir[i][1];  //这里用dir来实现四个方向的功能 使得代码很工整
            //2. 判断边间情况
            if(tx < 0 || tx > 4 || ty < 0 || ty > 4 )
                continue;
            //3. 判断下一层的节点是否符合条件加入队列中:加入队列中的都是未访问过的节点而且是通路
            if(map[tx][ty] == 0 && mark[tx][ty] == 0)
            {
                mark[tx][ty] = 1;   ///队列的应用只是完成了一种按层递进的遍历顺序,因此当遍历到满足条件的那一层的时候此时就是步数的最短值 这也就是为什么BFS一般用来求最优解
                //3. 找到当前节点的下一层节点了开始入队
                ptQueue[rear].x = tx;
                ptQueue[rear].y = ty;
                ptQueue[rear].step = ptQueue[front].step + 1;   //step 相当于图里面的层的概念 TODO 是front而不是rear
                //TODO  
                ptQueue[rear].next = &ptQueue[front];  //相当于指向这个节点的父节点
                rear++;   //入队一个节点 rear指针指向下一位置。 注意,因为入队的点都是访问一次的节点故最多入队了25个节点 所以数组长度是25够用的

                if(tx == 4 && ty == 4) {
                    flag = 1;
             

                    //pt ptWay[25];  //ptWay 因为在print函数还要用 也就是共享 所以设定为全局变量
                    pt * temp = &ptQueue[--rear];
                
                //当走到终点的时候 用way数组保存终点返回到起点的路径  
                    while(temp != nullptr) 
                    {
                        ptWay[num].x = temp->x;   //先将最后一个节点存在
                        ptWay[num].y = temp->y;
                        temp = temp->next;
                        num++;   
                    }
                
                    break;   
                  }  
                  
            }

            // 判断终止条件  如果入队的节点的额坐标为(4,4)
           
        }

        if(flag == 1)
            break ;   //到达终点后 退出while循环

        front++;   //出队。实际上队列中的元素并没有出队,只是这个标识增加了
    }

    if(flag == 1)
        cout << "the shortest path: " << ptQueue[rear].step << endl;   //注意这里不是 rear-1 ,因为在定义temp的时候已经减1了
    else
        cout << "can not find a path." << endl;
}


void print()
{
    --num;
    for(int i = num; i >= 0; i--)
        cout << "(" << ptWay[i].x << "," << ptWay[i].y << ")" << endl;
}

int main ( int argc, char ** argv )
{

    int map[5][5];
    memset(map, 0, sizeof(map));    //利用memset初始化

    for(int i = 0; i < 5; i++) {
        for(int j = 0; j < 5; j++) {
            cin >> map[i][j];
            //C语言版本
            //if( scanf("%d", &map[i][j]) == EOF )  return 0;
        }
    }

    DFS(map);
    
    //打印 
    print();    

    return 0;
}

///Great! you have done it.

接下来是用DFS求解,输出了最少步数。 

//
// Created by max on 19-4-15.
//
//同样是迷宫问题 现在用dfs算法来求解 要求输出最短路径步数
//思路:沿着一条路径一直走到头 然后返回上一节点尝试其他方向  
//depth first search!

#include <iostream>
#include <cstring>   //用到了memset函数

using namespace std;

int mark[5][5];   // 标记用int型来表示
char map[5][5];
int minStep = 26;
int ways = 0;   //走法  


int dir[4][2] = {
        {0,1},{1,0},{0,-1},{-1,0}
};

void Initial() {
    memset(map,'0', sizeof(map));
    ///如果是0 表示ASC 为0的字符 即null空字符,而map默认的初始化都为空字符 '0'的ASC为48
    memset(mark, 0, sizeof(mark));
    for(int i = 0; i < 5; i++) {
        for(int j = 0; j < 5; j++) {
            cin >> map[i][j];   ///如果声明了是char数组, 所以这里的cin输入直接会转换为char,键盘打0 则将其转换为‘0’(48)
        }
    }

}

//不像广度那样不断地出队和入队节点 深度优先可以不用定义节点结构体 而原结构体中的信息用函数的参数来表示即可
void DFS( int step, int x, int y ) {    //深度优先因为会用到回溯 所以肯定是带参数的 也就是递归函数
    int tx, ty;   //下一节点的坐标位置,每次搜索一次相当于进入一个新的节点

    //如果这次搜索满足条件就得到一条路径
    if(x == 4 && y == 4) {
        if ( step <= minStep )
            minStep = step;  //todo min是系统已有名 不要用min来变量命名
        ways++;
        return;   //这一部分相当于递归函数的终止条件!
    }

    //遍历x y点的四个方向 也就是图中节点的邻接点
    for(int i = 0; i < 4; ++i) {
        tx = x + dir[i][0];
        ty = y + dir[i][1];
        if( tx < 0 || tx > 4 || ty < 0 || ty > 4 )
            continue;
        //如果下一层中的节点满足条件 那么就递归深度搜索
        if( map[tx][ty] == '0' && mark[tx][ty] == 0 ) {
            mark[tx][ty] = 1;
            DFS(step + 1, tx, ty);   //进行下一步尝试
            mark[tx][ty] = 0;   //todo 深度优先搜素中一个点可以能反复访问,所以在递归结束之后要置为0
        }

    }
}


int main () {
    Initial();

    DFS(0, 0, 0);

    cout << "the shortest path:" << minStep << endl;

    //TEST
    //char a;
    //cin >> a;
    //cout << sizeof(a) << endl;

    return 0;
}

例2 

题目描述: 

gnatius被魔王抓走了,有一天魔王出差去了,这可是Ignatius逃亡的好机会. 

魔王住在一个城堡里,城堡是一个A*B*C的立方体,可以被表示成A个B*C的矩阵,刚开始Ignatius被关在(0,0,0)的位置,离开城堡的门在(A-1,B-1,C-1)的位置,现在知道魔王将在T分钟后回到城堡,Ignatius每分钟能从一个坐标走到相邻的六个坐标中的其中一个.现在给你城堡的地图,请你计算出Ignatius能否在魔王回来前离开城堡(只要走到出口就算离开城堡,如果走到出口的时候魔王刚好回来也算逃亡成功),如果可以请输出需要多少分钟才能离开,如果不能则输出-1. 

输入描述:

输入数据的第一行是一个正整数K,表明测试数据的数量.每组测试数据的第一行是四个正整数A,B,C和T(1<=A,B,C<=50,1<=T<=1000),它们分别代表城堡的大小和魔王回来的时间.然后是A块输入数据(先是第0块,然后是第1块,第2块......),每块输入数据有B行,每行有C个正整数,代表迷宫的布局,其中0代表路,1代表墙.(如果对输入描述不清楚,可以参考Sample Input中的迷宫描述,它表示的就是上图中的迷宫)

特别注意:本题的测试数据非常大,请使用scanf输入,我不能保证使用cin能不超时.在本OJ上请使用Visual C++提交. 

输出描述:

对于每组测试数据,如果Ignatius能够在魔王回来前离开城堡,那么请输出他最少需要多少分钟,否则输出-1.

示例1

输入

1
3 3 4 20
0 1 1 1
0 0 1 1
0 1 1 1
1 1 1 1
1 0 0 1
0 1 1 1
0 0 0 0
0 1 1 0
0 1 1 0

输出

11

思路: 这道题实际上是个三维的迷宫问题,稍作改动即可。  

//#include <iostream>
//因为题目中说了用scanf 所以这里用C语言版本的输入输出   

//这种写法有个缺点就是 不能输出所走的具体路径  只是判断了最短路径的长度    

#include <stdio.h>
#include <queue>   

using namespace std;  

bool mark[50][50][50];   //标记是否走过 
int maze[50][50][50];    //保存迷宫信息  

struct Node 
{
    int x, y, z; 
    int t;   // 时间,相当于步数   
}; 

queue<Node> Q; 

int go[][3] = {  //这里第一维可以不指定 会自动识别  
    1, 0, 0,
    -1, 0, 0, 
    0, 1, 0, 
    0, -1, 0, 
    0, 0, 1, 
    0, 0, -1
};  

int BFS ( int a, int b, int c ) 
{
    int i; 
    Node temp; 
    while( !Q.empty() )  //队列不为空的时候循环 
    {
        ///核心思想就是: 广度优先遍历所有的可达节点 遍历的时候保存了节点的层次信息,当遍历到最后的目标节点时 此时的层数最少 
        //先出队再入队  
        Node now = Q.front();
        Q.pop(); 
        for(int i = 0; i < 6; i++)  //将可达邻接节点入队
        {
            int nx = now.x + go[i][0]; 
            int ny = now.y + go[i][1]; 
            int nz = now.z + go[i][2]; 
            if(nx<0 || nx>=a || ny<0 ||ny>=b || nz<0 || nz>=c)
                continue; 
            if(maze[nx][ny][nz] == 1 || mark[nx][ny][nz] == true)
                continue;  
                
            //下一节点入队  
            temp.x = nx; 
            temp.y = ny; 
            temp.z = nz; 
            temp.t = now.t + 1;   
            Q.push(temp);  
            mark[nx][ny][nz] = true;   
            
            if(nx==a-1 && ny==b-1 && nz==c-1)  //如果遍历到的下一节点是终点  
                return temp.t;     
        }
    }
    
    return -1;  //
}
     

//main  
int main(int argc, char ** argv) 
{
    int n; 
    int a, b, c, t; 
    int i, j, k; 
    int minT;   //保存最小时间 也就是最小步数   
    
    scanf("%d", &n); 
    while(n--)
    {
    
        scanf("%d%d%d%d", &a, &b, &c,&t);
        
        for(i = 0; i < a; i++)
        {
            for(j = 0; j < b; j++)
            {
                for(k = 0; k < c; k++)
                {
                    scanf("%d", &maze[i][j][k]);
                    mark[i][j][k] = false;    
                }
            }
        }
    }
    
    while( !Q.empty() ) Q.pop();     //Q不为空的时候弹出,清空队列
    mark[0][0][0] = true;            //先标记起点  
    
    //将第一个点入队以启动DFS 不然while循环不开始啊    
    Node firstN; 
    firstN.x = 0; firstN.y = 0; firstN.z =0; firstN.t = 0;
    Q.push( firstN );   
    minT = BFS(a, b, c);
        
    if(minT <= t && minT != -1) 
        printf("%d\n", minT);
    else
        printf("-1\n");   
        
    return 0;      
}

例3 

Tempter of the bone

时间限制:1秒 空间限制:32768K 热度指数:148

题目描述

The doggie found a bone in an ancient maze, which fascinated him a lot. However, when he picked it up, the maze began to shake, and the doggie could feel the ground sinking. He realized that the bone was a trap, and he tried desperately to get out of this maze. 

The maze was a rectangle with sizes N by M. There was a door in the maze. At the beginning, the door was closed and it would open at the T-th second for a short period of time (less than 1 second). Therefore the doggie had to arrive at the door on exactly the T-th second. In every second, he could move one block to one of the upper, lower, left and right neighboring blocks. Once he entered a block, the ground of this block would start to sink and disappear in the next second. He could not stay at one block for more than one second, nor could he move into a visited block. Can the poor doggie survive? Please help him. 

输入描述:

The input consists of multiple test cases. The first line of each test case contains three integers N, M, and T (1 < N, M < 7; 0 < T < 50), which denote the sizes of the maze and the time at which the door will open, respectively. The next N lines give the maze layout, with each line containing M characters. A character is one of the following:
       

'X': a block of wall, which the doggie cannot enter; 
       
'S': the start point of the doggie; 
       
'D': the Door; or 
'.': an empty block.
       

The input is terminated with three 0's. This test case is not to be processed.

输出描述:

For each test case, print in one line "YES" if the doggie can survive, or "NO" otherwise.

示例1

输入

4 4 5
S.X.
..X.
..XD
....
3 4 5
S.X.
..X.
...D
0 0 0

输出

NO
YES

题目大意为: 有一个n*m的迷宫,包括起点s,终点d,墙x和地面,0秒时主人公从s出发,每秒能走到四个与其相邻的位置中的一个,且每个位置被行走之后都不能再次走入,问是否存在这样一条路径使在T秒刚好走到d


#include <stdio.h>
#include <iostream>
#include <stdlib.h>

char maze[8][8];    
//题目中 1<n,m<7  所以建立8维 可以用maze[1]~maze[6] 至于maze[i][7]应该是结束符'\0'
int n, m, t;

int flag;  //是否找到
int go[][2] = {
        1,0,
        0, 1,
        -1, 0,
        0, -1
};

void DFS(int x, int y, int time)
{
    int i;
    int nx, ny;
    for( i = 0; i < 4; i++)
    {
        nx = x + go[i][0];
        ny = y + go[i][1];
        if(nx<1 || nx>n || ny<1 || ny>n)
            continue;
        if(maze[nx][ny] == 'X')    //如果是墙直接跳过
            continue;

        if(maze[nx][ny] == 'D')  //如果下一步为门的话 进而判断时间
        {
            if(time + 1 == t)
            {
                flag = 1;  //成功
                return;
            }
            else
                continue;  //如果时间不对则继续找
        }

        maze[nx][ny] = 'X';  ///每一条路径走过的路不能重复走,所以这里相当于movedMark[nx][ny]=true
        DFS(nx, ny, time + 1);  //搜索下一节点
        maze[nx][ny] = '.';
        if(flag == 1)    return;  ///上一个return相当于递归的终止条件 这个return是外层函数的返回 TODO 测试没有这一行是不是也可以
    }
}


int main()
{
    int i, j;
//  while ( std::cin >> n >> m >> t )
    while( scanf("%d%d%d", &n, &m, &t) != EOF ){    //循环开始条件:输入成功
        //有了这一句,那么可以不断输入用例, 所以最好有个终止while的选择
        if(n == 0 && m == 0 && t == 0)
            break;

        for(i = 1; i <= n; ++i)
        {
            scanf("%s", maze[i] + 1);  ///这一句有意思!
            //首先%s表示输入是一个字符串,注意遇到空格等空白字符赋值结束;函数的第二个参数是地址值例如scanf("%d", &a).但是maze[i]是一个字符数组已经是地址了
            //maze[i]+1表示从首地址的下一地址开始即maze[i][1]开始存储输入的字符串 回车正好结束一次赋值
        }

        flag = 0;   //初始为0没有找到,为1表示找到了满足条件

        //找到S点并开始搜索
        for( i=1; i<=n; ++i)
        {
            for( j=1; j<=m; ++j)
            {
                if( maze[i][j] == 'S')
                {
                    //开始DFS
                    maze[i][j] = 'X';   //将起始点标记为走过
                    DFS(i, j, 0);
                }
            }
        }
        if(flag)
            printf("YES\n");
        else
            printf("NO\n");
    }

    return 0;

}

///我的见解 可能在搜索S和D点的时候会超时 所以我门可以在输出的时候采用当个char输入 scanf("%c", &maze[i][j]) 然后用if判断
/*  实际上找D的坐标不需要
char maze[8][8];
int sx, sx;
for(int i=1; i<=n; i++)
{
    for(int j=1; j<=m; j++)
    {
        scanf("%c", &maze[i][j])
        if(maze[i][j] == 'S')
        {
            sx = i;
            sy = j;
        }
    }
}

 maze[sx][sy] = 'X';
 DFS(sx, sy, 0);
*/

例4  DFS

谷歌的题目:24点 

拿出一副完整的扑克牌,然后把花牌和10以上的牌全部清理掉,只剩下数字的牌A-9放在一起,代表1-9。分配好以后每人拿出2张牌出来,看谁先想到24点。加减乘除都可以,但是4张牌必须都用上。

思路: 

利用DFS。求24点也是DFS经典应用之一。把所有的数字放好,然后列举所有可能的运算。
以nums[4] = {4, 1, 7, 8}为例。

上图中可以看到nums[0] != 24 所以回溯,将13与7的运算改为 -  。所以整个算法流程是个DFS  


bool dfs(double* nums, int numsSize) {
	printf("%.1f ", nums[0]); 
	if(numsSize == 1) {
		if(fabs(nums[0]-24)<1e-2) return true;
		else  return false;
	}
	double a,b;
	for(int i = 0; i < numsSize; i++) {
		for(int j = i+1; j < numsSize; j++) {
			a = nums[i];
			b = nums[j];
			nums[j] = nums[numsSize-1];
 
			nums[i] = a+b;
			if(dfs(nums, numsSize-1)) return true;
			nums[i] = a-b;
			if(dfs(nums, numsSize-1)) return true;
			nums[i] = b-a;
			if(dfs(nums, numsSize-1)) return true;
			nums[i] = a*b;
			if(dfs(nums, numsSize-1)) return true;
			//除法分母不为0 
			if(b != 0) nums[i] = a/b;
			if(dfs(nums, numsSize-1)) return true;
			if(a != 0) nums[i] = b/a;
			if(dfs(nums, numsSize-1)) return true;
			//回溯 
			nums[i]=a;
			nums[j]=b;
		}
	}
	return false;
}
 
 
bool judgePoint24(int* nums, int numsSize) {
	double nums2double[numsSize];  // 保证除法是小数除法 
	for(int i = 0; i < numsSize; i++) {
		nums2double[i] = *(nums+i);
	}
    	if (dfs(nums2double, numsSize)) return true; 
    	else return false;
}

例5 

描述 
给出三个水杯,大小不一,并且只有最大的水杯的水是装满的,其余两个为空杯子。三个水杯之间相互倒水,并且水杯没有标识,只能根据给出的水杯体积来计算。现在要求你写出一个程序,使其输出使初始状态到达目标状态的最少次数。

输入 
第一行一个整数N(0<N<50)N(0<N<50)表示N组测试数据 
接下来每组测试数据有两行,第一行给出三个整数V1 V2 V3 (V1>V2>V3V1<100V3>0)(V1>V2>V3V1<100V3>0)表示三个水杯的体积。 
第二行给出三个整数E1 E2 E3 (体积小于等于相应水杯体积)表示我们需要的最终状态 
输出 
每行输出相应测试数据最少的倒水次数。如果达不到目标状态输出-1 
样例输入

2
6 3 1
4 1 1


样例输出

3

分析:  

è¿éåå¾çæè¿°

从上图中可看出,是一个回溯的思想。

思想:

回溯算法 ,利用bfs实现 ,同时为了运算简单,要进行剪枝,去掉不可能的情况(就是不让不可能的情况进入队列的堆栈,或者遇到他们直接返回)
这个题告诉我们,并不是只有图论才会用到bfs,dfs。但凡有搜索,回溯的情形,都可以用bfs,dfs
--------------------- 
 

广度优先搜索bfs的框架
BFS()
{
    int ... ...;//定义一些变量
    queue<node> q;
    q.push(head);
    while(!q.empty())
    {
        node pr=q.front();
        q.pop();
        //下面就是对出队的pr进行的操作
        if(... ...)
        {
            ... ...
            q.push(...);
        }
        if(...)
        {        ... ...
            q.push(...);
        }
    }
}
--------------------- 
题目来源:https://blog.csdn.net/MosBest/article/details/68985740 !

//算法思想:回溯算法  利用bfs实现

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

using namespace std;

typedef struct{
    int w[4];
    int step;
}node;

int N;
int V[4],E[4];
bool visited[101][101][101];//用于 剪枝,visited[5][3][7]=true用来标记第1个杯子装5单位水, 
                            //第2个杯子装3单位水,第3个杯子装7单位水已经访问过,
                            //以后遇到这种情况不用压入队列了 

void Pour(int i,int k,node* next)//第i个杯子给k倒水,将结果存储在next中 
{
    if(next->w[i] <= V[k] - next->w[k])
    {
        next->w[k]=next->w[i]+next->w[k];
        next->w[i]=0;

    }
    else
    {
        next->w[i]=next->w[i]-(V[k] - next->w[k]);
        next->w[k]=V[k];
    }
    return;
}

void BFS()
{
    queue<node> q;
    node start,pr;
    memset(visited,false,sizeof(visited));
    start.step=0;
    start.w[1]=V[1];
    start.w[2]=0;
    start.w[3]=0;
    visited[start.w[1]][start.w[2]][start.w[3]]=true;//竟然忘了这一步 
    q.push(start);
    while(!q.empty())
    {
        pr=q.front();
        q.pop();
        if(pr.w[1]==E[1] && pr.w[2]==E[2] && pr.w[3]==E[3])
        {
            printf("%d\n",pr.step);
            return; 
        }
        node next;
        //pr中的杯子倒水,将结果存储在next中
        //共有6种情况,1倒给2,3 ,2倒给1,3,3倒给1,2 ,所以有下面代码
        for(int i=0;i<3;i++) 
        {
            for(int j=1;j<3;j++)
            {
                int k=(i+j)%3;//此式子可以保证当i+1为1时,k+1取2,3 
                                        //i+1=2时,k+1=1,3;i+1=3时,k+1=1,2 
                next=pr;
                Pour(i+1,k+1,&next);
                next.step=next.step+1;
                if(!visited[next.w[1]][next.w[2]][next.w[3]])
                {
                    visited[next.w[1]][next.w[2]][next.w[3]]=true;
                    q.push(next);
                }   
            }
        }
    }
    printf("%d\n",-1);
    return; 
}

int main()
{
    scanf("%d",&N);
    for(int i=0;i<N;i++)
    {
        scanf("%d%d%d",&V[1],&V[2],&V[3]);
        scanf("%d%d%d",&E[1],&E[2],&E[3]);
        BFS();
    }
    return 0;
}
--------------------

Thank  you!     

猜你喜欢

转载自blog.csdn.net/weixin_43795395/article/details/89399874