算法-广度优先搜索

抓住那头牛

农夫知道一头牛的位置,想要抓住它。农夫和牛都位于数轴上 ,农夫起始位于点N(0<=N<=100000),牛位于点 K(0<=K<=100000)。农夫有两种移动方式:

1、从X移动到X-1或X+1,每次移动花费一分钟

2、从X移动到2*X,每次移动花费一分钟 假设牛没有意识到农夫的行动,站在原地不动。农夫最少要 花多少时间才能抓住牛?

策略1

深度优先搜索:从起 点出发,随机挑一个方向,能 往前走就往前走(扩展),走 不动了则回溯。不能走已经走 过的点(要判重)。

运气好的话: 3->4->5 或 3->6->5 问题解决!

运气不太好的话: 3->2->4->5 运气最坏的话: 3->2->1->0->4->5

要想求最优(短)解,则要遍历所有走法。可以用 各种手段优化,比如,若已经找到路径长度为n 的解,则所有长度大于n的走法就不必尝试。 运算过程中需要存储路径上的节点,数量较少。 用栈存节点。

策略2

广度优先搜索: 给节点分层。起点是第0层。从起 点最少需n步就能到达的点属于第n 层。

第1层:2,4,6

第2层:1,5

第3层:0

依层次顺序,从小到大扩展节点。 把层次低的点全部扩展出来后,才 会扩展层次高的点。

扩展时,不能扩展出已经走过的节 点(要判重)。

可确保找到最优解,但是因扩展出 来的节点较多,且多数节点都需要 保存,因此需要的存储空间较大。 用队列存节点。

若要遍历所有节点:  深搜 1-2-4-8-5-6-3-7  广搜 1-2-3-4-5-6-7-8

广搜算法

广度优先搜索算法如下:(用QUEUE)

(1) 把初始节点S0放入Open表中;

(2) 如果Open表为空,则问题无解,失败 退出;

(3) 把Open表的第一个节点取出放入 Closed表,并记该节点为n;

(4) 考察节点n是否为目标节点。若是,则 得到问题的解,成功退出;

(5) 若节点n不可扩展,则转第(2)步;

(6) 扩展节点n,将其不在Closed表和 Open表中的子节点(判重)放入Open表的尾 部,并为每一个子节点设置指向父节点的指针 (或记录节点的层次),然后转第(2)步。

cpp 复制代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
int N,K;
const int MAXN = 100000;
int visited[MAXN+10]; //判重标记,visited[i] = true表示i已经扩展过
struct Step{
    int x;  //位置
    int steps; //到达x所需的步数
    Step(int xx,int s):x(xx),steps(s) { }
};
queue<Step> q;  //队列,即Open表
int main()   {
    cin >> N >> K;
    memset(visited,0,sizeof(visited));
    q.push(Step(N,0));
    visited[N] = 1;
   while(!q.empty()) {
       Step s = q.front();
       if( s.x == K ) { //找到目标
           cout << s.steps <<endl;
           return 0;
       }
       else {
           if( s.x- 1 >= 0 && !visited[s.x-1] ) {
               q.push(Step(s.x-1,s.steps+1));
               visited[s.x-1] = 1;
           }
           if( s.x + 1 <= MAXN && !visited[s.x+1] ) {
               q.push(Step(s.x+1,s.steps+1));
               visited[s.x+1] = 1;
           }
          if( s.x * 2 <= MAXN &&!visited[s.x*2]  ) {
              q.push(Step(s.x*2,s.steps+1));
              visited[s.x*2]  = 1;
          }
           q.pop();
       }
   }
    return 0;
}

输入2 7

输出3

迷宫问题

定义一个矩阵:

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

它表示一个迷宫,其中的1表示墙壁,0表示可以走的路, 只能横着走或竖着走,不能斜着走,要求编程序找出从 左上角到右下角的最短路线。

基础广搜。先将起始位置入队列 每次从队列拿出一个元素,扩展其相邻的4个元素入队列(要判重), 直到队头元素为终点为止。队列里的元素记录了指向父节点(上一步)的指针 队列元素:

cpp 复制代码
struct {
 int r,c;
 int f; //父节点在队列中的下标
 };
cpp 复制代码
//BFS第二次编写
#include<stdio.h>
#include<string.h>

struct Node
{
    int x,y;
    int pre;
}q[5*5 + 10];//使用数组创建的队列,出栈的时候不会真正的删除元素

int map[5][5];//输入矩阵,判断是否可达
int vis[5][5];//判断是否已经访问

//维护队列的指针,并且相当于完成初始化,直接存放第一个元素
//出队和入队千万不要忘记这两个指针的维护
int qhead = 0;//指向第一个元素
int qtail = 1;//指向最后一个元素的下一个元素

int X[4] = {1,-1,0,0};
int Y[4] = {0,0,-1,1};

int test(int x,int y)
{
    if(x<0 || x>=5 || y<0 || y>=5) return 0;
    if(map[x][y] == 1) return 0;
    if(vis[x][y] == 1) return 0;

    return 1;
}

void BFS()
{
    //第一个节点入队
    q[qhead].x = 0;
    q[qhead].y = 0;
    q[qhead].pre = -1;
    vis[0][0] = 1;

    while(qhead<qtail)
    {
        Node temp_head = q[qhead];//访问队首元素
        //qhead++;此处编写错误,会影响下面的q[qtail].pre = head;
        if(temp_head.x == 4 &&temp_head.y == 4)
            return;
        for(int i = 0;i<4;i++)
        {
            int newX = temp_head.x + X[i];
            int newY = temp_head.y + Y[i];
            if(test(newX,newY))
            {
                //入队
                q[qtail].x = newX;
                q[qtail].y = newY;
                q[qtail].pre = qhead;
                vis[newX][newY] = 1;
                qtail++;
            }
        }
        qhead++;//出队,注意只有这个位置正确
    }
}
void print(int head)
{
    if(head == 0)
        printf("(0, 0)\n");
    else
    {
        if(q[head].pre != -1)
        {
            print(q[head].pre);
            printf("(%d, %d)\n",q[head].x,q[head].y);
        }
    }
}
int main()
{
    int i,j;
    for(i = 0;i<5;i++)
    {
        for(j = 0;j<5;j++)
        {
            scanf("%d",&map[i][j]);//输入数据
        }
    }
    BFS();
    print(qhead);
    memset(vis,0,sizeof(vis));//初始化vis数组
    return 0;
}

输入

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

输出

(0, 0)

(1, 0)

(2, 0)

(2, 1)

(2, 2)

(2, 3)

(2, 4)

(3, 4)

(4, 4)

鸣人和佐助

已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的 位置。地图上的每个位置都可以走到,只不过有些位置上有 大蛇丸的手下(#),需要先打败大蛇丸的手下才能到这些位 置。

鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一 个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动, 每移动一个距离需要花费1个单位时间,打败大蛇丸的手下 不需要时间。如果鸣人查克拉消耗完了,则只可以走到没有 大蛇丸手下的位置,不可以再移动到有大蛇丸手下的位置。 佐助在此期间不移动,大蛇丸的手下也不移动。

鸣人 要追上佐助最少需要花费多少时间?

状态定义为: (r,c,k) ,

鸣人所在的行,列和查克拉数量 如果队头节点扩展出来的节点是有大蛇手下的节点, 则其k 值比队头的k要减掉1。如果队头节点的查克 拉数量为0,则不能扩展出有大蛇手下的节点。

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
#define inf 0x3f3f3f3f
struct pos{
    int x;
    int y;
    int  k;//当前所剩下的查克拉
    int  t;//花费的时间
    pos(int xx, int yy, int kk, int tt) : x(xx), y(yy), k(kk), t(tt) {}
};

char maze[206][206];
int ans, dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int maxk[206][206];//到达当前格子时,所剩查克拉的最大值
int m, n, t;
queue<pos> q;
int  judge(int xx,int yy)
{
    if(xx < 0 || xx >= m || yy < 0 || yy >= n )
        return 1;
    else
        return 0;
}
void bfs()
{
    while (!q.empty())
    {
        pos now = q.front();
        q.pop();
        if (maze[now.x][now.y] == '+')
        {
            ans = now.t;
            return;
        }
        else
        {
            for (int i = 0; i < 4; i++)
            {
                int xx = now.x + dir[i][0];
                int yy = now.y + dir[i][1];
                if (judge(xx,yy)|| maxk[xx][yy] >= now.k)
                    continue;
                if (maze[xx][yy] == '#')//碰到大蛇丸手下
                {
                    if (now.k > 0)//查克拉充足
                    {
                        q.push(pos(xx, yy, now.k - 1, now.t + 1));
                        maxk[xx][yy] = now.k;
                    }
                }
                else
                {
                    q.push(pos(xx, yy, now.k, now.t + 1));
                    maxk[xx][yy] = now.k;
                }
            }
        }
    }
}

int main()
{
    cin >> m >> n >> t;
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            cin >> maze[i][j];
            maxk[i][j] = -1;//为零的时候也是可以走的,所以初始化为-1
            if (maze[i][j] == '@')//找到鸣人的位置
            {
                q.push(pos(i, j, t, 0));
                maxk[i][j] = t;
            }
        }
    }
    ans = inf;
    bfs ();
    if (ans == inf)
    {
        cout << -1 << endl;
    }
    else
    {
        cout << ans << endl;
    }
    return 0;
}

八数码问题

有一个3*3的棋盘,其中有0-8共9个数字,0表示空格, 其他的数字可以和0交换位置。求由初始状态 到达目标状态

1 2 3

4 5 6

7 8 0 的步数最少的解。

用队列保存待扩展的节点

从队首队取出节点,扩展出的新节点放入队尾, 直到队首出现目标节点(问题的解)

如果问题无解,输出"unsolvable"。如果有解,则输出空格的移动序列。"u"表示将空格向上移,"d"表示将空格向下移,"1"表示左移,"r"表示右移。

cpp 复制代码
//广度优先搜索--标志位采用set二分查找
//内存:10752kB    时间:533ms

#include<iostream>
#include<cstring>
#include<cstdio>//sprintf()头文件
#include<cstdlib>//atoi()头文件
#include<set>//STL容器
using namespace std;

int goalStatus;
const int MAXS=400000;
char result[MAXS];
struct Node
{
    int status;
    int father;
    char move;
    Node(int s,int f,char m):status(s),father(f),move(m){}
    Node(){}
};
Node myQueue[MAXS];
int qhead=0;
int qtail=1;
char moves[]="udrl";

void IntStatusToStrStatus(int n,char*strStatus)
{//字符串格式化命令,按十进制转换成9位的字符串,可在前面添加0凑足
    sprintf(strStatus,"%09d",n);//需要保留前导0
}

int NewStatus(int status,char cMove)
{//求从状态status经过cMove移动后的新状态;若不可行则返回-1
    char tmp[20];
    int zeroPos;
    IntStatusToStrStatus(status,tmp);//转换成字符串形式进行移动
    for(int i=0;i<9;++i)
        if(tmp[i]=='0'){zeroPos=i;break;}
    switch(cMove)
    {
    case'u':
        if(zeroPos-3<0)return -1;
        else {tmp[zeroPos]=tmp[zeroPos-3];
              tmp[zeroPos-3]='0';}
        break;
    case'd':
        if(zeroPos+3>8)return -1;
        else {tmp[zeroPos]=tmp[zeroPos+3];
              tmp[zeroPos+3]='0';}
        break;
    case'l':
        if(zeroPos%3==0)return -1;
        else {tmp[zeroPos]=tmp[zeroPos-1];
              tmp[zeroPos-1]='0';}
        break;
    case'r':
        if(zeroPos%3==2)return -1;
        else {tmp[zeroPos]=tmp[zeroPos+1];
              tmp[zeroPos+1]='0';}
        break;
    }
    return atoi(tmp);//再将字符串还原为整数状态返回
}

bool Bfs(int status)
{//从初始状态status到目标的路径,找不到则返回false
    int newStatus;
    set<int>expanded;//存放标记位
    expanded.insert(status);
    myQueue[qhead]=Node(status,-1,0);//初始化队列头节点(起始节点)
    while(qhead!=qtail)//队列不为空
    {
        status=myQueue[qhead].status;
        if(status==goalStatus)return true;

        for(int i=0;i<4;i++)
        {
            newStatus=NewStatus(status,moves[i]);
            if(newStatus==-1)continue;
            if(expanded.find(newStatus)!=expanded.end())continue;//set中若没找到则返回end
            expanded.insert(newStatus);
            myQueue[qtail++]=Node(newStatus,qhead,moves[i]);
        }
        qhead++;
    }
    return false;
}

int main()
{
    goalStatus=atoi("123456780");
    char line1[50];  char line2[20];
    while(cin.getline(line1,48))
    {
        int i,j;
        //将原始输入转变成数字字符串
        for(i=0,j=0;line1[i];i++)
        {
            if(line1[i]!=' '){
                if(line1[i]=='x')line2[j++]='0';
                else line2[j++]=line1[i];
            }
        }
        line2[j]=0;
        if(Bfs(atoi(line2)))
        {
            int moves=0;
            int pos=qhead;
            do{
                result[moves++]=myQueue[pos].move;
                pos=myQueue[pos].father;
            }while(pos);//pos=0说明已经退回初始状态
            for(int i=moves-1;i>=0;i--)cout<<result[i];
        }
        else cout<<"unsolvable"<<endl;
    }
    return 0;
}

2 3 4 1 5 x 7 6 8

ullddrurdllurdruldr

相关推荐
董董灿是个攻城狮7 分钟前
Transformer 通关秘籍7:词向量的通俗理解
算法
卷卷的小趴菜学编程22 分钟前
算法篇-------------双指针法
c语言·开发语言·c++·vscode·算法·leetcode·双指针法
komo莫莫da32 分钟前
Day14 动态规划(3)
算法·深度优先·动态规划
地平线开发者1 小时前
【征程 6】工具链 VP 示例为什么能运行
算法·自动驾驶
ElseWhereR1 小时前
困于环中的机器人
c++·算法·leetcode
学也不会2 小时前
d2025331
java·数据结构·算法
清晨朝暮2 小时前
【算法学习计划】贪心算法(中)
学习·算法·贪心算法
独好紫罗兰2 小时前
洛谷题单2-P2433 【深基1-2】小学数学 N 合一-python-流程图重构
开发语言·python·算法
独好紫罗兰2 小时前
洛谷题单2-P5709 【深基2.习6】Apples Prologue 苹果和虫子-python-流程图重构
开发语言·python·算法
巨可爱熊2 小时前
C++基础算法(插入排序)
java·c++·算法