DFS、BFS实例(啊哈算法)

DFS:

A.将n张不同的牌放入n个箱子里,一个箱子一张牌,总共几种方法?

思路:此处一共分为四步操作:

1.按规定顺序放牌入箱子里(这里规定放牌的顺序从小到大)

2.人在箱子间的移动(step++)

3.箱子都放满了并且输出这种情况

4.返回收牌(若不满足放牌的条件则一直返回收牌直到:a.全部情况输出完成,结束循环;b.满足放牌条件进行而新的一轮循环(返回第一步)。

通过以上四步循环操作输出全部结果。

#include<stdio.h>
int n,s[10],book[10];//设置全局变量使数组初始值为0(一开始牌全部在手上)
//book[]用来标记牌的状态:0.在手上未使用过;1.在箱子里已经使用过
void dfs(int step);
int main()
{
    scanf("%d",&n);//输入手牌数(=箱子数)
    dfs(1);//刚开始选择第一个箱子
    return 0;
}
/*DFS是解决当前怎么做*/
void dfs(int step)//调用一次dfs函数就相当于移动了一步(换了一个箱子放牌)
{
    int i;
    if(step>n)//如果箱子全部放满
    {
        for(i=1;i<=n;i++)
            printf("%d ",s[i]);//则输出已排序好的序列
        putchar('\n');
        return;//并且返回上一个箱子拿回牌
    }
    for(i=1;i<=n;i++)//在每一个箱子前都要依次判断手牌情况
    {
        if(book[i]==0)//如过这张手牌在手上
        {
            s[step]=i;//则将这张手牌放入箱子
            book[i]=1;//并且标记这张手牌已经使用过了防止在输出一种情况前二次使用
            dfs(step+1);//放下一张手牌后选择下一个箱子继续放置手牌

            book[i]=0;//拿回已输出的情况的牌并且标记拿回的牌状态为未使用过
            /*若到达这一步,可能有两种情况(相当于一种情况):
            1.箱子全部放满,收回最后一个箱子里的牌(在第一个return处返回);
            2.在一个箱子前但是手上的手牌都已经放到过这个箱子(在最后一个return处返回)*/
        }
        //如果不在手上则继续判断i+1张牌在不在手上
    }
    return;
}

B.再来一道类似的题目:用九张牌1~9组成xxx+xxx=xxx的组合,每种牌只有一张。

(与第一题相比:满足输出条件的要求不一样,牌和箱子数不一样)

#include<stdio.h>
int s[10],book[10],sum;
void dfs(int step);
int main()
{
    dfs(1);
    printf("\nsum=%d\n",sum/2);/*输出所有满足条件的组合,
    需要除以2是因为没有规定被加数一定比加数小而造成输出重复*/
    return 0;
}
void dfs(int step)
{
    int i;
    if(step>9)//一共九张牌
    {
        if(s[1]*100+s[2]*10+s[3]+s[4]*100+s[5]*10+s[6]==s[7]*100+s[8]*10+s[9])//新增一个返回条件
        {
            sum++;//记录组合数
            printf("%d%d%d + %d%d%d = %d%d%d\n",s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[8],s[9]);
            return;
        }
    }
    for(int i=1;i<=9;i++)
    {
        if(book[i]==0)
        {
            s[step]=i;
            book[i]=1;
            dfs(step+1);
            book[i]=0;
        }
    }
    return;
}

c.找出前往迷宫中目标地点的最短路径(0为空地,1为障碍物)

思路:1.输入一张迷宫图,用1代表障碍物,0代表空地,设定起始和结束坐标;

2.规定走迷宫的方向:从右开始顺时针选定行走方向,一直走直到遇到墙或者障碍物转向

3.找到目标后记录总路径并和当前最短路径进行比较,保留最短路径

4.多次循环直到遍历完整个迷宫,输出最短路径

#include<stdio.h>
int n,m;
int s[51][51],book[51][51];//设定迷宫大小在50*50以内
int sx,sy,tx,ty,ex,ey,min=0x3f3f3f3f,step;//设置min为无穷大
void dfs(int x,int y);
/*void dfs(int x,int y,int step);
也可以将路径和作为参数传入函数,每调用一次函数路径加1*/
int main()
{
    int i,j;
    scanf("%d %d",&n,&m);
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
            scanf("%d",&s[i][j]);//输入迷宫图
    scanf("%d %d %d %d",&sx,&sy,&ex,&ey);//输入起始和目标坐标
    book[sx][sy]=1;//标记当前起始位置已经走过
    dfs(sx,sy);//参数换为了当前位置的横纵坐标
    printf("min = %d\n",min);//输出最短路径
    return 0;
}
void dfs(int x,int y)//传入当前坐标
{
    int k;
    int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//设定next数组,确定当前位置下一步的走法
    //分别为{1,0}向右走一步,{0,1}向下走一步,{-1,0}向左走一步,{0,-1}向上走一步(顺时针)
    if(x==ex&&y==ey)//当 当前位置就是目标位置时比较路径总和
    {
        if(step<min)
            min=step;//保留最短路径
        return;//返回一步找有没有其它路径
    }
    for(k=0;k<4;k++)//遍历四种方向的走法
    {
        tx=x+next[k][0];//(tx,ty)为 可能的下一步坐标
        ty=y+next[k][1];
        if(tx<1||ty<1||tx>n||ty>m)//这个可能的坐标不能在迷宫外
            continue;//换一个方向再作为 可能的下一步坐标 看能不能走

        if(book[tx][ty]==0&&s[tx][ty]==0)//若在迷宫内且没走过且是空地
        {
            step++;//走一步,总路径加1
            book[tx][ty]=1;//标记当前坐标为走过
            dfs(tx,ty);//传入新的当前坐标(若路径step作为参数则此处是dfs(tx,ty,step+1))
            book[tx][ty]=0;//两种情况:1.走到目标位置;2.走到死路退回
        }
    }
    return;
}
/*
测试用例
5 4
0 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1
1 1 4 3
*/

D.炸弹人小游戏,给一张图,找出能到达的且消灭敌人数最多的敌人的点

在一个点放置炸弹,上下左右四个方向上的敌人都会被炸到,但炸弹炸不烂墙

('#'是墙,'G'是敌人,'.'是空地)

#include<stdio.h>
char s[21][21];
int book[21][21];
int n,m,max,mx,my;//此处将sum设为全局变量可以不用getnum带回返回值(void getnum())
void dfs(int x,int y);
int getnum(int x,int y);//用于计算每个点的击杀数
int main()
{
    int i,sx,sy;
    scanf("%d %d %d %d",&n,&m,&sx,&sy);
    for(i=0;i<n;i++)
        scanf("%s",s[i]);
    book[sx][sy]=1;//标记当前坐标走过
    max=getnum(sx,sy);//初始化max为起始点的击杀数(可以省略)
    mx=sx;
    my=sy;//初始化mx,my坐标(可省)
    dfs(sx,sy);
    printf("[%d,%d] %d\n",mx,my,max);//输出消灭敌人最多的坐标和消灭敌人数
    return 0;
}
void dfs(int x,int y)
{
    int tx,ty,sum,k;
    int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}}; 
    sum=getnum(x,y);//计算当前坐标可以消灭敌人的数量
    if(sum>max)
    {
        max=sum;
        mx=x;
        my=y;//保留最大点的坐标等信息
    }
    for(k=0;k<4;k++)
    {
        tx=x+next[k][0];
        ty=y+next[k][1];
        if(tx<0||ty<0||tx>n-1||ty>m-1)//不超过边界(与之前题不一样因为这次输入的图从(0,0)开始)
            continue;
        if(book[tx][ty]==0&&s[tx][ty]=='.')//不能写s[tx][ty]!='#'因为不能在敌人身上放炸弹
        {
            book[tx][ty]=1;
            dfs(tx,ty);
            //这里无需再重置book[tx][ty]=0,因为不需要重复计算一个点的击杀数
        }
    }
    return;
}
int getnum(int x,int y)//计算每点击杀数
{
    int i,j,sum;
    sum=0;//每次计算前都将sum重置为0
    i=x;j=y;//不改变x,y的值用i,j代替
    while(s[i][j]!='#')//当遇到障碍物之前
    {
        if(s[i][j]=='G')//遇到敌人则击杀数加1
            sum++;
        i--;//向上走
    }
    i=x;j=y;
    while(s[i][j]!='#')
    {
        if(s[i][j]=='G')
            sum++;
        i++;//向下走
    }
    i=x;j=y;
    while(s[i][j]!='#')
    {
        if(s[i][j]=='G')
            sum++;
        j--;//向左走
    }
    i=x;j=y;
    while(s[i][j]!='#')
    {
        if(s[i][j]=='G')
            sum++;
        j++;//向右走
    }
    return sum;//返回此点的总击杀数
}
/*
13 13 3 3
#############
#GG.GGG#GGG.#
###.#G#G#G#G#
#.......#..G#
#G#.###.#G#G#
#GG.GGG.#.GG#
#G#.#G#.#.#.#
##G...G.....#
#G#.#G###.#G#
#...G#GGG.GG#
#G#.#G#G#.#G#
#GG.GGG#G.GG#
#############
*/

E.给出一张小岛的地图,从降落点开始,计算小岛总面积,0代表海洋,

其余数字都代表小岛,且每一个数字都代表面积加1

#include<stdio.h>
int book[51][51],s[51][51];
int n,m,sum;
void dfs(int x,int y);
int main()
{
    int i,j,sx,sy;
    scanf("%d %d %d %d",&n,&m,&sx,&sy);
    for(i=0;i<n;i++)
        for(j=0;j<m;j++)
            scanf("%d",&s[i][j]);
    book[sx][sy]=1;
    sum=1;//标记降落点(一定是陆地)走过且面积+1
    dfs(sx,sy);
    printf("%d\n",sum);
    return 0;
}
void dfs(int x,int y)
{
    int tx,ty,k;
    int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}}; 
    for(k=0;k<4;k++)
    {
        tx=x+next[k][0];
        ty=y+next[k][1];
        if(tx<0||ty<0||tx>n-1||ty>m-1)
            continue;
        if(book[tx][ty]==0&&s[tx][ty]!=0)
        {
            sum++;
            book[tx][ty]=1;
            dfs(tx,ty);
            //此处也不用返回因为面积也是每一步都计算的
        }
    }
    return;
}
/*
10 10 6 8
1 2 1 0 0 0 0 0 2 3
3 0 2 0 1 2 1 0 1 2
4 0 1 0 1 2 3 2 0 1
3 2 0 0 0 1 2 4 0 0
0 0 0 0 0 0 1 5 3 0
0 1 2 1 0 1 5 4 3 0
0 1 2 3 1 3 6 2 1 0
0 0 3 4 8 9 7 5 0 0
0 0 0 3 7 8 6 0 1 2
0 0 0 0 0 0 0 0 1 0
*/

F.水管游戏:给定一张未联通的水管图,1~4表示弯管,5~6表示直管:

1.上右;2.下右;3.下左;4.上左;5.左右;6.上下

设定进水口在左边为进水口1(1就代表从左边进水),顺时针编号:上为进水口2,右为进水口3,下为进水口4

输出所有能使水管联通的路径(从(1,1)到(n,m)),设定刚开始进水口是1(左边),最后进水口也是1。

如图:

#include<stdio.h>
int s[11][11], book[11][11];
int n, m, flag;
void dfs(int x, int y, int front);//front为进水口的位置
struct node
{
	int x;
	int y;//记录路径
}map[101];
int top = 0;//top是路径下标(也是总和),一开始没有路径所以是0
int main()
{
	int i, j;
	scanf("%d %d", &n, &m);
	for (i = 1; i <= n; i++)
		for (j = 1; j <= m; j++)
			scanf("%d", &s[i][j]);
	dfs(1, 1, 1);//第一个水管在(1,1)且进水口在左边
	if (!flag)//若找不到有效路径
		printf("impossible!\n");
    putchar('\n');
	return 0;
}
void dfs(int x, int y, int front)
{
	int i;
	if (x == n && y == m + 1)//若坐标达到目标位说明成功联通
    //y=m+1是因为最后联通口在右边的墙上
	{
		flag = 1;//记录路径成功
		for (i = 1; i <= top; i++)
			printf("(%d,%d) ", map[i].x, map[i].y);//输出记录的全部路径
		return;
	}
	if (x<1 || y<1 || x>n || y>m || book[x][y] == 1)
		return;
	book[x][y] = 1;//记录此处管道转动过了
	top++;//路径+1
	map[top].x = x;
	map[top].y = y;//将当前位置坐标记录在数组中
	if (s[x][y] == 5 || s[x][y] == 6)//如果管道是直管的情况
	{
		if (front == 1)//进水口在左边
			dfs(x, y + 1, 1);//相当于向右移动一步
		if (front == 2)
			dfs(x + 1, y, 2);
		if (front == 3)
			dfs(x, y-1, 3);
		if (front == 4)
			dfs(x-1, y , 4);
	}
	if (s[x][y] == 1 || s[x][y] == 2 || s[x][y] == 3 || s[x][y] == 4)//弯管
	{
		if (front == 1)//进水口在左边,弯管可以向上也可以向下两种情况
		{
			dfs(x - 1, y, 4);//向上弯曲相当于向上移动一步横坐标减1(x-1),对于下一个水管来说相当于进水口在下面(4)
			dfs(x + 1, y, 2);//向下弯曲相当于向下移动一部横坐标加1(x+1),对于下一个水管来说相当于进水口在上面(2)
		}
		if (front == 2)
		{
			dfs(x, y - 1, 3);
			dfs(x, y + 1, 1);
		}
		if (front == 3)
		{
			dfs(x - 1, y, 4);
			dfs(x + 1, y, 2);
		}
		if (front == 4)
		{
			dfs(x, y - 1, 3);
			dfs(x, y + 1, 1);
		}
	}
	book[x][y] = 0;//退一步重置路径标记
	top--;//路径长度减1(直到遍历完所有情况)
	return;
}
/*
5 4 
5 3 5 3
1 5 3 0
2 3 5 1
6 1 1 5
1 5 5 4
*/

G. 城市地图:导航是如何计算出一个城市到另一个城市的最短路径的?

此处给出若干行从一个城市到另一个城市的路径,求给定两个城市之间的最短路径

比如说给出如下数据(都是单向路径):

5 8//五个城市,八条路径

1 2 2//起始城市,目标城市,之间的路径

......

使用邻接矩阵表示(最左边纵列表示起始城市a,最上面横列表示目标城市b,矩阵中的数据代表对应两个城市之间的路径):

邻接矩阵_diviner_s的博客-CSDN博客_邻接矩阵

 自己到自己的城市路径为0,未列出的都是不通的路径设长度设为无穷。

从图中可以看出,两个城市之间的最短路径不一定是连接这两个城市的路,

可以先从起始城市到中间城市再转到目标城市以实现最短路径的寻找,

此类问题也可以使用dfs算法,通过遍历所有起始城市到目标城市的所有路径以找出最短路径

代码如下(dfs):

#include<stdio.h>
int min=0x3f3f3f3f,book[101],n,e[101][101];//0x3f3f3f3f表示无穷大,e表示城市之间的邻接矩阵
void dfs(int cur,int sum)//cur表示当前城市,sum表示当前总路径
{
    if(sum>min)//如果当前路径已经大于最短路径则不用计算直接返回
        return;
    if(cur==n)//达到目标城市
    {
        if(sum<min)
            min=sum;//记录最短路径
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(e[cur][i]!=0x3f3f3f3f&&book[i]==0)//若两个城市间连通且曾经没有通过此城市
        {
            book[i]=1;//标记城市为走过
            dfs(i,sum+e[cur][i]);//寻找可以到达目标城市的中间城市路径
            book[i]=0;//返回再遍历另一条可以到达目标城市的路径
        }
    }
    return;
}
int main()
{
    int m,a,b,c;
    scanf("%d %d",&n,&m);//此处假设从一号城市出发,到达n号目标城市
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(i!=j) e[i][j]=0x3f3f3f3f;//将城市之间的路径都初始化为无穷
            /*此处可省略if(i==j) e[i][j]=0;(自己到自己的城市路径为0)*/
    for(int i=1;i<=m;i++)//创建城市之间的邻接矩阵
    {
        scanf("%d %d %d",&a,&b,&c);
        e[a][b]=c;//因为是有向图,若是无向图则还需加上e[b][a]=c表示从城市b到城市a也可以连通
    }
    book[1]=1;//标记1号城市走过
    dfs(1,0);//当前城市为1,总路径为0
    printf("%d\n",min);//输出两个城市之间的最短路径
    return 0;
}
/*
5 8
1 2 2
1 5 10
2 3 3
2 5 7
3 1 4
3 4 4
4 5 5
5 3 3
*/

BFS:

A./*DFS C题*/

#include<stdio.h>
struct bfs{
    int x;
    int y;//结构体记录当前操作点的横纵坐标
    int s;//和已扩展完的结点数
};
int main()
{
    int head,tail,flag=0;//flag用来标记是否能到达目标点
    int n,m,sx,sy,ex,ey,tx,ty;
    struct bfs que[2501];//对于整张迷宫的队列扩展(结点数不超过50*50=2500)
    int book[51][51]={0},s[51][51];
    int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&s[i][j]);
    scanf("%d %d %d %d",&sx,&sy,&ex,&ey);

    //初始化队列
    head=1,tail=1;//刚开始时没有点入队所以队首等于队尾
    que[tail].x=sx;
    que[tail].y=sy;//待扩展的点(起始点)入队
    que[tail].s=0;//刚开始无一个点被扩展(步数为0)
    tail++;//准备让下一个满足要求扩展点入队,tail标记队尾(最后一位)的下一个位置
    book[sx][sy]=1;//标记起始点为走过

    while(head<tail)//当存在可扩展的点时(队列不为空的时候)一直进行扩展
    {
        for(int k=0;k<4;k++)
        {
            tx=que[head].x+next[k][0];
            ty=que[head].y+next[k][1];//向四个方向分别进行扩展
            if(tx>n||ty>m||ty<1||tx<1)//判断越界
                continue;
            if(s[tx][ty]==0&&book[tx][ty]==0)
            {
                book[tx][ty]=1;
                que[tail].x=tx;
                que[tail].y=ty;//被扩展的点入队作为待扩展的点
                que[tail].s=que[head].s+1;//步数+1
                tail++;//准备让下一个满足要求的点入队
            }
            /*和dfs很相似,但此处不同于bfs不用返回去寻找未被遍历的点
            (对每个点进行扩展的时候就已经把满足要求的所有点都入队了)*/

            if(tx==ex&&ty==ey)//如果到达目的地
            {
                flag=1;//标记路径存在
                break;
            }
        }
        if(flag==1)//如果路径存在则退出输出长度
            break;
        head++;//head++让已扩展完的点出队,要不队列不可能为空而造成无限循环
    }
    printf("%d",que[tail-1].s);//tail是指队尾的下一个位置,所以要-1
    return 0;
}
/*
5 4
0 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1
1 1 4 3
*/

B.最少转机问题,求从起始城市到目标城市之间的最少转机数(不考虑时间和路径)

5 7 1 5    //城市总数,路径数,起始城市,目标城市

1 2         //注意此处是双向图,表示从1号城市和二号城市之间可以互相到达

......

同样使用邻接矩阵表示(注意是双向到达),这里可以相通的两个城市间的距离都用1表示:

#include<stdio.h>
struct bfs{
    int x;//当前城市编号
    int s;//当前转机次数
};
int main()
{
    struct bfs que[2501];
    int e[51][51]={0},book[51]={0};
    int head,tail;
    int n,m,a,b,cur,start,end,flag=0;
    scanf("%d %d %d %d",&n,&m,&start,&end);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(i!=j) e[i][j]=0x3f3f3f3f;
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&a,&b);
        e[a][b]=1;
        e[b][a]=1;//创建无向图
    }
    //初始化队列
    head=1,tail=1;
    que[tail].x=start;
    que[tail].s=0;
    tail++;
    book[start]=1;

    while(head<tail)
    {
        cur=que[head].x;//cur表示当前城市
        for(int i=1;i<=n;i++)//扩展与当前城市连通的所有城市
        {
            if(e[cur][i]!=0x3f3f3f3f&&book[i]==0)
            {
                que[tail].x=i;//将满足条件的城市入队
                que[tail].s=que[head].s+1;//转机次数+1
                tail++;
                book[i]=1;
            }
            if(que[tail-1].x==end)//找到通向目标城市的路径
            {
                flag=1;
                break;
            }
        }
        if(flag)
            break;
        head++;//已扩展完的城市出队
    }
    printf("%d\n",que[tail-1].s);//输出最少转机次数
    return 0;
}
/*
5 7 1 5
1 2 
1 3 
2 3
2 4
3 4
3 5
4 5
*/

 java面试题:DFS和BFS的优缺点及应用场景_我是方小磊的博客-CSDN博客_dfs和bfs的优缺点

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TGRD

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值