(c语言实现)算法笔记之bfs及pta习题

目录

一,bfs(广度优先搜索)的定义

二,bfs(广度优先搜索)的应用

三,题型训练

1,奇怪的电梯

2,寻宝 

3,龙舌兰酒吧 

四,总结 



       

一,bfs(广度优先搜索)的定义

BFS 全称是 Breadth First Search,中文名是宽度优先搜索,也叫广度优先搜索。

是图上最基础、最重要的搜索算法之一。

所谓宽度优先。就是每次都尝试访问同一层的节点。 如果同一层都访问完了,再访问下一层。

这样做的结果是,BFS 算法找到的路径是从起点开始的 最短 合法路径。换言之,这条路径所包含的边数最小。

在 BFS 结束时,每个节点都是通过从起点到该点的最短路径访问的。

算法过程可以看做是图上火苗传播的过程:最开始只有起点着火了,在每一时刻,有火的节点都向它相邻的所有节点传播火苗。


以上资料来自于BFS(图论) - OI Wiki (oi-wiki.org)

 说点人话环节(doge):比如走迷宫,我们一开始肯定会有一个起始位置,而之后改如何走是不太确定的,因为有四个方向可以走,广搜就是,一次搜索就走这四个方向,搜过的点就标记一下,下次不要再回去找(所以这里会标记起始的点,而由起始点走出的四个点还没有搜索,所以不必标记)。之后再从走出的四个点分别进行搜索接下去的四个方向的点。由一次搜索走出来的所有点就是一层。在每次走完一层之后,就先判定一下这一层的点有没有所需要的答案,如果有,那么就不需要再找了。

在图上就是,以V1为起点,第一次搜索就搜V2,V3,并将V1标记。此为一层,这时,应该先判断一下V2,V3是不是我们所需要的点,如果不是,那么就继续,往下搜索V4,V5,V6,V7. 直到找到目标。

那这时候就有人要问了:那找不到怎么办?也有走不通的迷宫啊!

回答这个问题之前,我们要先想想,bfs搜索的时候,数据要放到哪里?我们想想,每一次搜索的时候是不是就是先将要搜索的点放入储存的结构中,然后搜索完标记并退出。这符合“先进先出”的特点,也就是队列的特点。由于只会c语言我们只能去模拟队列的实现。如何模拟呢?用一个数组和两个指针变量就可以了。

    struct node queque[10000];
    memset(queque,0,sizeof(queque));
    int head=0,end=1;

就可以模拟队列的实现,当然,如果要使用模拟循环队列也是可以的。(记得加个判断,end>数组长度时,重置即可)但是要主要开好足够的空间 ,不然可能还没搜索,那一层的部分数据就会被覆盖掉。

因为一层一层搜索,所以如果还能走,必然有数据入队,自然就不会出现head>=end的情况(如果是循环队列,那么判断条件应该是head!=end,即head不会与end相等)。一旦出现上述情况,那么就说明没得搜了,已经都走遍了,如果此时还没找到答案,那么就是没有答案了。

二,bfs(广度优先搜索)的应用

目前我所遇到的基本上bfs的应用在于寻找最短路径类,之后如果有遇到其他的应用,会再写一篇文章来描述新的应用。

那么这里就以走迷宫为例,看看如何实现吧。

首先肯定要创建好结构体记录每次走的点,并创建好队列

struct node
{
    int num;
    int i;
    int j;
};

    struct node queque[10000];
    memset(queque,0,sizeof(queque));
    int head=0,end=1;

 然后创建走的四个方向

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

之后就是主体部分的实现了

int tmp=end;
        while(head<tmp)//一层搜索
        {
           for(int i=0;i<4;i++)//每个点搜索四个方位
           {
               int tmpi=queque[head].i+local[i][0];
               int tmpj=queque[head].j+local[i][1];
               if(tmpi<0||tmpi>=n||tmpj<0||tmpj>=m)//判断边界条件
               {
                   continue;
               }
               else if(arr[tmpi][tmpj]==-1)//搜索过的或者不可走点
               {
                   continue;
               }
               else
               {
                   //存下数值和地址,并将其入队
                   queque[end].num=arr[tmpi][tmpj];
                   queque[end].i=tmpi;
                   queque[end].j=tmpj;
                   end++;
                   arr[tmpi][tmpj]=-1;
               }
           }
            head++;
        }

三,题型训练

1,奇怪的电梯

 c大楼有一个一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i 层楼 (1≤i≤N) 上有一个数字Ki(0≤Ki≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:3,3,1,2,5 代表了Ki (K1=3,K2=3,…),在1楼,按“上”可以到4楼,按“下”是不起作用的,因为没有−2楼。那么,从A楼到B楼至少要按几次按钮呢?

输入格式:

第一行包含3个用空格隔开的正整数,分别表示N,A,B (1≤N≤200,1≤A,B≤N) 。 第二行包含N 个用空格隔开的非负整数,表示Ki 。

输出格式:

输出共一行,即最少按键次数,若无法到达,则输出 −1 。

输入样例:

5 1 5
3 3 1 2 5

输出样例:

3

这道题也是典型bfs的题目,具体的实现即解释也在代码中

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int judge(int arr[])
{
    int i=0;
    for(i=0;i<2000;i++)
    {
        if(arr[i]!=0)
            return 1;
    }
    return 0;
}
int main()
{
    int n;
    scanf("%d",&n);
    int arr[n+1];
    int i=0;
    int sta,end;
    scanf("%d%d",&sta,&end);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&arr[i]);
    }
    arr[0]=0;
    int queque[2000];//模拟队列的实现
    memset(queque,0,sizeof(queque));
    int top=0,bottom=1;
    queque[top]=sta;
    int count=0;
    int flag=0;
    while(judge(queque))
    {
        //广搜对一层的搜索
        int tmp=bottom;
        while(top<tmp)
        {
           //此处节点标记采用arr[i]=0方式
            if(arr[queque[top]]!=0)
            {
               if(queque[top]+arr[queque[top]]<=n)
               {
                //对上楼层节点进行访问,将可访问节点放入队列并标记该节点
                queque[bottom++]=queque[top]+arr[queque[top]];
                //arr[queque[top]+arr[queque[top]]]=0;
               }
               if(queque[top]-arr[queque[top]]>0)
               {
                //对下楼层进行访问,将可访问节点放入队列并标记该节点
                queque[bottom++]=queque[top]-arr[queque[top]];
               }
                arr[queque[top]]=0;
            }
            top++;
        }
        //搜索一次就需要按一次电梯
        count++;
        //判断目前的层数中有无目标数
        int i=0;
        for(;i<top;i++)
        {
            if(queque[i]==end)
            {
                flag=1;
                 goto ending;
            }
            queque[i]=0;
        }
    }
    ending:
    if(flag)
    printf("%d",count-1);
    else
    printf("-1");
    return 0;
}

2,寻宝 

奕哥今天玩到一款寻宝游戏,地图是一个n*m的矩阵,其中分布着一些宝藏,每个宝藏具有一定的价值,奕哥只能拿走其中一个宝藏。奕哥起始在a行b列。奕哥可以向相邻的一格移动,但不能走出地图外。奕哥初始体力值X,移动一格需要消耗体力值为1。体力耗尽后奕哥无法继续移动。地图中有一些障碍物,奕哥无法移动到障碍物上。奕哥想知道他能拿到的最具价值的宝藏是哪一个。

输入格式:

第一行包含5个整数n,m,a,b,X。n,m分别表示地图行数,列数;a,b表示奕哥起始位置在a行b列;X表示奕哥的体力。

( 1<=n,m<=1000, 1<=a<=n, 1<=b<=m, 0<=X<=1e10)

接下来n行,每行m个整数。第i行第j个整数为Aij (-1<=Aij<=1e9)。若Aij=-1,则表示第i行第j列有障碍物;否则表示该位置有一个价值为Aij的宝物。保证起始位置没有障碍物。

输出格式:

奕哥能拿到价值最高的宝藏的价值。

输入样例:

3 3 1 1 3
0 -1 10
3 5 7
-1 8 15

输出样例:

8

这就是迷宫题了,上面的点已经说得清楚了,不懂的可以再上去看看

#include<stdio.h>
#include<string.h>
int local[][2]=
{
    {0,1},
    {0,-1},
    {1,0},
    {-1,0}
};
struct node
{
    int num;
    int i;
    int j;
};
int main()
{
	int n, m, a, b, x;
	scanf("%d%d%d%d%d", &n, &m, &a, &b, &x);
	a--;//数组中初始位置要减一
	b--;
	int arr[n][m];//读入地图
    int i=0;
	for (i = 0; i<n; i++)
	{
		int j = 0;
		for (j = 0; j<m; j++)
		{
			scanf("%d", &arr[i][j]);
		}
	}
    struct node queque[10000];
    memset(queque,0,sizeof(queque));
    int head=0,end=1;
    queque[head].num=arr[a][b];
    queque[head].i=a;//记录对应数值的坐标
    queque[head].j=b;
    arr[a][b]=-1;//搜索过的就直接标记
    while(x--)
    {
        int tmp=end;
        while(head<tmp)//一层搜索
        {
           for(int i=0;i<4;i++)//每个点搜索四个方位
           {
               int tmpi=queque[head].i+local[i][0];
               int tmpj=queque[head].j+local[i][1];
               if(tmpi<0||tmpi>=n||tmpj<0||tmpj>=m)//判断边界条件
               {
                   continue;
               }
               else if(arr[tmpi][tmpj]==-1)//搜索过的或者不可走点
               {
                   continue;
               }
               else
               {
                   //存下数值和地址,并将其入队
                   queque[end].num=arr[tmpi][tmpj];
                   queque[end].i=tmpi;
                   queque[end].j=tmpj;
                   end++;
                   arr[tmpi][tmpj]=-1;
               }
           }
            head++;
        }
    }
    int max=0;
    for(int i=0;i<end;i++)
    {
        if(queque[i].num>max)
            max=queque[i].num;
    }
    printf("%d",max);
	return 0;
}

3,龙舌兰酒吧 

有一个大小为n*m的矩形小镇,城镇上有房屋(“#”表示无法通过),有空地(“.”表示可通行),每次移动只能朝上下左右四个方向,且需花费1单位时间。

一天,二乔和承太郎约定在龙舌兰酒吧见面,两人同时从各自所在位置向酒吧出发。请问最少需要过多少时间他们才能在酒吧碰面。

地图上P表示二乔的位置,W表示承太郎的位置,B表示酒吧的位置。

输入格式:

第一行包含两个整数n,m,表示城镇的大小。(1<=m,n<=1000)。

接下来n行,每行m个字符,表示小镇的情况。

输出格式:

输出两人在酒吧碰面的最少花费时间,如果无法在酒吧碰面则输出-1。

输入样例:

5 4
.W.#
P#..
....
B..#
#...

输出样例:

4

具体代码实现如下:

#include<stdio.h>
#include<string.h>
//四个方位
int local[][2]={
    {0,1},
    {0,-1},
    {1,0},
    {-1,0}
};
struct node
{
    char ch;
    int i;
    int j;
};
int judge(struct node *queque,int n)
{
    int i=0;
    for(;i<n;i++)
    {
        if(queque[i].ch!=0)
            return 1;
    }
    return 0;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    char arrp[n][m];//供p寻找
    char arrw[n][m];//供w寻找
    struct node queque[600000];//模拟队列实现
    memset(queque,0,sizeof(queque));
    for(int i=0;i<n;i++)
    {
        getchar();
        for(int j=0;j<m;j++)
        {
            scanf("%c",&arrp[i][j]);
        }
    }
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
            arrw[i][j]=arrp[i][j];
    }
    int pi,pj,wi,wj,tari,tarj;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            if(arrp[i][j]=='P')//找到P坐标
            {
                pi=i;
                pj=j;
            }
            if(arrp[i][j]=='W')//找到W坐标
            {
                wi=i;
                wj=j;
            }
        }
    }
    
  /*
  此处为测试arrp和arrw是否正常 
         for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            printf("%c",arrw[i][j]);
        }
        printf("\n");
    }
   */
    int countp=0;
    int countw=0;
    int flag1=0;
    //此处开始为两次bfs的实现
    //1.arrp的路径寻找
    int head=0;
    int end=1;
    queque[head].ch=arrp[pi][pj];
    queque[head].i=pi;
    queque[head].j=pj;
    while(judge(queque,end))//判断队列是否为空
    {
        int tmp=end;
        while(head<tmp)
        {
          for(int i=0;i<4;i++)
          {
              int tmpi=queque[head].i+local[i][0];
              int tmpj=queque[head].j+local[i][1];
              if(tmpi<0||tmpi>=n||tmpj<0||tmpj>=m)
                  continue;
              else if(arrp[tmpi][tmpj]=='#')
                  continue;
              else
              {
                  queque[end].ch=arrp[tmpi][tmpj];
                  queque[end].i=tmpi;
                  queque[end].j=tmpj;
                  arrp[tmpi][tmpj]='#';
                  end++;
              }
          }
            queque[head].ch=0;//搜索完出队列
            head++;
        }
        countp++;//搜索完一次走一步
        for(int i=head;i<end;i++)
        {
            if(queque[i].ch=='B')
            {
                flag1=1;
                break;
            }
        }
        if(flag1)
            break;
    }
    memset(queque,0,sizeof(queque));
    head=0;
    end=1;
    queque[head].ch=arrw[wi][wj];
    queque[head].i=wi;
    queque[head].j=wj;
    //2.arrw路径寻找
    int flag2=0;
    while(judge(queque,end))//判断队列是否为空
    {
        int tmp=end;
        while(head<tmp)
        {
          for(int i=0;i<4;i++)
          {
              int tmpi=queque[head].i+local[i][0];
              int tmpj=queque[head].j+local[i][1];
              if(tmpi<0||tmpi>=n||tmpj<0||tmpj>=m)
                  continue;
              else if(arrw[tmpi][tmpj]=='#')
                  continue;
              else
              {
                  queque[end].ch=arrw[tmpi][tmpj];
                  queque[end].i=tmpi;
                  queque[end].j=tmpj;
                  arrw[tmpi][tmpj]='#';
                  end++;
              }
          }
            queque[head].ch=0;
            head++;
        }
        countw++;
        for(int i=head;i<end;i++)
        {
            if(queque[i].ch=='B')
            {
                flag2=1;
                break;
            }
        }
        if(flag2)
            break;
    }
    if(flag1==0||flag2==0)
        printf("-1");
    else
    {
        printf("%d",countp>countw?countp:countw);
    }
    return 0;
}

四,总结 

总体来说,bfs理解了就会很难(doge),bfs也是图论的一个基础算法,希望这篇文章能帮助到你掌握bfs,看到这里不妨给我点个赞吧。之后大概就是一周一篇了。要多输入,才能有输出嘛,多数时间还是要拿来学习的。(还是太菜)。

  • 34
    点赞
  • 120
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值