No.4.3 层层递进-广度优先搜索


一、深度优先搜索:从开始位置进行尝试,直到走不通的时候,再返回到上一层“递归“继续尝试

广度优先搜索:从开始位置起,搜索每一种可能路径并加入到队列,再从队列中的所有元素搜索所有的下一层可能路径,即首先穷尽最上层可能,再逐层递进,那么找到目标的那一刻,最短层数既是最短路径!

把此图顺时针旋转45度,呈现树的形态,即可以更加形象化的视线,理解广度优先搜索算法!

#include
struct node{
  int x; //横坐标
  int y; //纵坐标
  int s; //步数
};

//算法是用了两种数据结构实现的:二维数组和队列(队列元素是一个三维数组)
int main(){
  struct node que[2501];   //这是一个队列,队列中的元素是 struct node
  int a[51][51]={0},book[51][51]={0};   //二维平面依旧使用二维数组表示
  

  int next[4][2]={
    {0,1}, // 右
    {1,0}, // 下
    {0,-1}, // 左
    {-1,0} // 上
  };

  int i,j,k,n,m,startx,starty,p,q,tx,ty,flag;

  //平面初始化
  scanf("%d %d",&n,&m);
  for(i=1;i<=n;i++)
    for(j=1;j<=m;j++){
      scanf("%d",&a[i][j]);
    }
  scanf("%d %d %d %d",&startx,&starty,&p,&q);

  //队列初始化

  int head=1,tail=1;

  que[tail].x=startx;
  que[tail].y=starty;
  que[tail].s=0;
  tail++;      //tail指向空值,所以步数的增加应该是 que[head].s+1,下一步尝试也是操作que[head]
  book[startx][starty]=1;

  flag=0;
  while(head     for(k=0;k<=3;k++){
      tx=que[head].x+next[k][0];  
      ty=que[head].y+next[k][1];
      if (tx<1||tx>n||ty<1||ty>m)
        continue;
      if(a[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;
        tail++;
      }
      if(tx==p && ty==q){  //只要找到(p,q),就一定是最短路径,退出循环
        flag=1;
        break;
      }
    }
    if(flag==1)  //只要找到(p,q),就一定是最短路径,退出程序
      break;
    head++;  //同一级队列元素尝试完毕,即可退出队列,无需取消标记book
    }
  printf("%d",que[tail-1].s);
  getchar();getchar();return 0;
}

二、将上面的例子增加点难度,要求打印出最短路径(并不要求打印出所有的最短路径):用一个笨方法,保存当前节点的父节点

struct node{
  int x; //横坐标
  int y; //纵坐标
  int s; //步数
  int f[3];  //保存父节点的x,y,s
};

//算法是用了两种数据结构实现的:二维数组和队列(队列元素是一个三维数组)
int main(){
  struct node que[2501]; //这是一个队列,队列中的元素是 struct node
  int a[51][51]={0},book[51][51]={0}; //二维平面依旧使用二维数组表示
  int next[4][2]={
    {0,1}, // 右
    {1,0}, // 下
    {0,-1}, // 左
    {-1,0} // 上
  };

  int i,j,k,n,m,startx,starty,p,q,tx,ty,flag;
  int cu,pa;

  scanf("%d %d",&n,&m);
  for(i=1;i<=n;i++)
    for(j=1;j<=m;j++){
      scanf("%d",&a[i][j]);
    }
  scanf("%d %d %d %d",&startx,&starty,&p,&q);

  int head=1,tail=1;

  que[tail].x=startx;

  que[tail].y=starty;
  que[tail].s=0;
  que[tail].f[0]=startx;
  que[tail].f[1]=starty;
  que[tail].f[2]=que[tail].s;

  tail++; //tail指向空值
  book[startx][starty]=1;

  flag=0;
  while(head     for(k=0;k<=3;k++){
      tx=que[head].x+next[k][0];
      ty=que[head].y+next[k][1];
      if (tx<1||tx>n||ty<1||ty>m)
        continue;
      if(a[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;
        que[tail].f[0]=que[head].x;
        que[tail].f[1]=que[head].y;
        que[tail].f[2]=que[head].s;
        tail++;
      }
      if(tx==p && ty==q){
        flag=1;

        //打印所有路径,用作验证
        for (cu=tail-1;cu>0;cu--)
          printf("z=%d, %d %d %d, %d %d %d\n",cu,que[cu].x,que[cu].y,que[cu].s,que[cu].f[0],que[cu].f[1],que[cu].f[2]);
        //打印最短路径
        cu = tail-1;
        while(cu>1){
          pa=cu-1;
          while(true){
            if(que[cu].f[0]==que[pa].x && que[cu].f[1]==que[pa].y && que[cu].f[2]==que[pa].s){
              printf("cur=%d %d %d, pre=%d %d %d\n",que[cu].x,que[cu].y,que[cu].s,que[pa].x,que[pa].y,que[pa].s);
              break;
            }
            else
              pa--;
          }
          cu=pa;
        }

      break;
      }
    }
    if(flag==1)
      break;
    head++;
  }
  printf("\n最小路径长度为%d",que[tail-1].s);
  getchar();getchar();return 0;
}

三、方法二太麻烦了,优雅点

struct node{
  int x; //横坐标
  int y; //纵坐标
  int s; //步数
  int f;  //保存父节点的指针(head)
};

int main(){
  struct node que[2501]; //这是一个队列,队列中的元素是 struct node
  int a[51][51]={0},book[51][51]={0}; //二维平面依旧使用二维数组表示
  int next[4][2]={
    {0,1}, // 右
    {1,0}, // 下
    {0,-1}, // 左
    {-1,0} // 上
  };

  int i,j,k,n,m,startx,starty,p,q,tx,ty,flag;
  int cu,pa;

  scanf("%d %d",&n,&m);
  for(i=1;i<=n;i++)
    for(j=1;j<=m;j++){
      scanf("%d",&a[i][j]);
    }
  scanf("%d %d %d %d",&startx,&starty,&p,&q);

  int head=1;tail=1;
  que[tail].x=startx;
  que[tail].y=starty;
  que[tail].s=0;
  que[tail].f=0;

  tail++; //tail指向空值
  book[startx][starty]=1;

  flag=0;
  while(head

    //每一个head,尝试完所有可能之后,就会被释放,并标记为book[tx][ty]=1,防止后续被重新入栈
    for(k=0;k<=3;k++){
      tx=que[head].x+next[k][0];
      ty=que[head].y+next[k][1];
      if (tx<1||tx>n||ty<1||ty>m)
        continue;
      if(a[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;
        que[tail].f=head;  //当前尝试的父节点都是head指针所指向的对象
        tail++;
      }
      if(tx==p && ty==q){
        flag=1;

        //打印已经探索出来的路径,用作验证
        for (cu=tail-1;cu>0;cu--)
          printf("z=%d, %d %d %d, %d\n",cu,que[cu].x,que[cu].y,que[cu].s,que[cu].f);

        cu=tail-1;
        while(cu>0){  //看这里,知道为何初始化的时候“que[tail].f=0”了吧!
          printf("z=%d, %d %d %d, %d\n",cu,que[cu].x,que[cu].y,que[cu].s,que[cu].f);
          cu=que[cu].f;  //倒序递归
        }
        break;
      }
    }
    if(flag==1)  //已经找到了最短路径,结束程序,虽然还有其他可能的最短路径!
      break;
    head++;
  }
  printf("\n最小路径长度为%d",que[tail-1].s);
  getchar();getchar();return 0;
}

四、如果还想求解共有多少条最短路径的话,广度优先搜索就不合适了,因为这里为了不陷入循环,就像一条有向图一样,不准走回头路,那么就不能对同一个节点进行多种尝试!

这时,还是要用上节中的深度优先算法,或者排列组合算法!

相关