• 广度优先搜索


    一、思想

    广度优先搜索(Breadth First Search),又称宽度优先搜索,简称广搜,BFS。是对图的一种遍历方式。

    以这个无向带权图为例,结点上的数字为该结点的编号,边上的数字为该边的权值。

    假如以结点1为源点,以结点7为目标结点,广度优先搜索扫描到结点的顺序应当是这样的。(如图,结点旁的红色数字表示搜到结点的顺序,对于由同一个结点引出的多个节点,按照字典序分别扫描。)

    第一次,结点1。第二次,结点2。第三次,结点5。第四次,结点4。

    第五次,结点6。第六次,结点7。第七次,结点3。

    可以发现,它是从源点开始,对每一个与源点连通的点一一扫描,扫描完成后,对于扫描到的点,再按照每个点被扫描到的顺序,执行同样的操作。那么就有一个问题,对于扫描到的点,如何记录其被扫描到的顺序?答案是使用一种数据结构——队列。

    把扫描到的结点按照扫描的顺序入队,一个结点引出的所有结点入队完成后,把该结点(即队头)出队,那么出队后的队头就是比它后一个进队(即后一个被扫描到)的结点。

    若共有n个结点,用a[i][j]表示图中结点i到结点j的权值(假设没有负权和为0的权),若结点i到结点j无边,那么令a[i][j]=0,用Q来表示该队列,h表示队头所表示的结点,pop表示队头出队,push(i)表示结点i进队,empty表示该队列是否为空,为空则返回0。那么从源点s到终点e,广度优先搜索的伪代码可以写成:

    BFS1

    Q.push(s);

    while not Q.empty

           for j = 1 to n do

                  if not a[i][j] = 0

                         Q.push(j);

           Q.pop;

    这段代码会把整个图搜索一遍,但是我们要求搜索到终点,在实际的应用中,往往是要输出方案或者能否搜索到终点,所以这段代码是有问题的。

    因此,我们在搜索到每个结点(在代码中即该结点入队)时,要判断该结点是否为目标结点,而为了达到这个目的,只要修改一下push就可以了

    下面给出在结点入队时判断是否为目标结点的代码(搜到目标结点后输出’T’并结束程序):

    push1(i)

           if i = e

                  print(“T”);

                  while not Q.empty

                         Q.pop;

           else

                  push(i);

    相应地,原来的代码中push(i);的部分也要改为push1(i);

    对于与上面的图相类似的无环图,这样的方法可以适用,但是如果在上图的3,5结点间加一条边(如图),6,7结点间加一条边,那么这幅图成为有环的图。

    用同样的方法,我们来模拟一下。

    首先结点1进队,队列:1

    然后结点2,5进队,1出队,队列:2 5

    然后结点4进队,2出队,队列:5 4

    然后结点1,3,6,7进队,5出队,队列:4 1 3 6 7(结点1又进队了?)

    然后结点2,3进队,4出队,队列:1 3 6 7 2 3(结点23又进队了?队里有两个3?)

    ……

    不难发现,按照同样的方法,对于有环的图来说,有环上的结点会重复进队,假如用一个数组来储存队列的话,相对于无环图,将会增加很大的空间开销。

    所以,要防止结点重复进队,但是怎样防止呢?很明显,所有的结点可以分为两类:进过队的(注意:是进过,不是在队内,因为有结点出队)和没进过队的,那么使用布尔数组b来储存结点是否进过队,假如结点i进过队,就令b[i]=true,那么b[i]要全部初始化为false。

    下面给出防止结点重复进队的代码和函数push3:

    BFS1

    b[1..n]=false;

    Q.push(s);

    while not Q.empty

           for j = 1 to n do

                  if not a[Q.h][j] = 0

                         Q.push2(j);

           Q.pop;

    push2(i)

           if b[i]=true

                  return;

           b[i]=true;

           if i = e

                  print(T);

                  while not Q.empty

                         Q.pop;

           else

                  push(i);

    二、例题

    题目描述

    考虑将如此安排在一个 3 * 3 行列中的九个时钟:

    目标要找一个最小的移动顺序将所有的指针指向12点。下面原表格列出了9种不同的旋转指针的方法,每一种方法都叫一次移动。选择1到9号移动方法,将会使在表格中对应的时钟的指针顺时针旋转90度。

    移动方法  受影响的时钟

    1              ABDE

    2              ABC

    3              BCEF

    4              ADG

    5              BDEFH

    6              CFI

    7              DEGH

    8              GHI

    9              EFHI

    输入

    9个数字,代表各个钟的初始状态

    输出

    把所有的钟指向12点的最短移动方案

    输入样例

    9 9 12

    6 6 6

    6 3 6

    输入样例

    4 5 8 9

    对于这样一道题目,首先分析是否可以用广搜,广搜是一种图的遍历方式,那么就要知道图是怎么样的,源点是什么,以及目标结点是什么。显而易见地,源点应该是各个钟的最初状态,可以使用一个数组来储存。既然源点是一个状态,那么每个结点也是一个状态,即所有钟的指针指向。目标结点就是所有钟都指向12点的状态。

    从任意一个结点出发,引出的结点(也就是经过1次移动后变成的状态)个数明显与移动方法的种数相同,即9种。

    使用常数数组a[9][9]来储存移动的方法,a[i][j]表示第i种方法对第j个钟是否有影响。

    那么根据题意,a可以初始化为{{1, 1, 0, 1, 1, 0, 0, 0, 0}, {1, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 1, 0, 0, 1, 0, 0}, {0, 1, 0, 1, 1, 1, 0, 1, 0}, {0, 0, 1, 0, 0, 1, 0, 0, 1}, {0, 0, 0, 1, 1, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 0, 1, 1, 1}, {0, 0, 0, 0, 1, 1, 0, 1, 1}}

    使用b[4][4][4][4][4][4][4][4][4]来储存结点是否进过队,

    b[i1][i2][i3][i4][i5][i6][i7][i8][i9]表示9个钟状态为i1,i2,i3,i4,i5,i6,i7,i8,i9的结点是否进过队。用结构体point来储存结点,每个结点包括以下几个信息:

    f表示它的父结点,即它由哪个结点扩展而来。

    x表示该结点的钟的状态。

    c表示扩展至该结点的移动方法。

    为什么要有f呢?在输出方案的时候需要从目标结点往回找,所以需要父结点。

    为什么要有x呢?在扩展到这个结点的子节点时需要这个结点的状态。

    为什么要有c呢?这还用问。。。要求输出的就是移动方法。

    用结构体数组q来表示队列,h是队头指针,t是队尾指针,那么出队就是h++,入队就是t++并把入队结点的信息存在q[t]里面。

    这个q数组要开多大呢?这取决于结点有几个,即状态有几种,9个钟每个有4个状态,那么就有4^9=262144种。所以q数组只要开得比这个大就可以了,比如300000。

    在读入初始状态信息时,先把它入队,然后按照广搜的方法一一做。

    在入队的push过程中,如果已经入过队,那么就直接跳出,如果没有,就把它入队,在判断一下该结点是否为目标结点,如果是,就从该结点开始,找到父结点,再找父结点的父结点,一直找到源点,每到一个点,就把它的移动方法(c)储存下来,结束后再倒序输出。

  • 相关阅读:
    (2)javascript的基本语法、数据结构、变量
    (1)认识javascript
    CSS 浅析position:relative/absolute定位方式
    jquery实现下拉框多选
    Vue.js not detected
    手机代理调试Charles Proxy和Fiddler
    render函数之jsx应用
    vue组件通信方式(多种方案)
    点击页面空白处地方,隐藏弹窗
    css圆角不圆和1px方案
  • 原文地址:https://www.cnblogs.com/zkx06111/p/3536830.html
Copyright © 2020-2023  润新知