编程语言
首页 > 编程语言> > 夜深人静写算法(十)- 单向广搜

夜深人静写算法(十)- 单向广搜

作者:互联网

文章目录

一、前言

  掌握了广搜就意味着至少可以拿一块省赛银牌,这或许是一句玩笑话,但是我觉得还是有几分道理的,广搜的涉及面很广,而且可以辅助你更好得理解动态规划,因为两者都有状态的概念,而且广搜的状态更加容易构造,不学广搜就无法理解 A*、SPFA、差分约束、稳定婚姻、最大流 等等其它的图论算法。
  回想自己十几年前刚开始学习搜索的时候,总是分不清楚什么时候应该用广搜,什么时候应该用深搜,所以,我把之前遇到的问题做了一个总结,发现最重要的还是那两个字:状态。今天这篇文章会围绕这两个字进行一个非常详细的讲解。
  当然,任何事情都有一个循序渐进的过程,我不会把所有关于广搜的内容一次性讲完,看完这篇文章,你至少应该可以自己手写一个单向广搜的代码。后面的章节会对 最短路、A* 、双向广搜 逐一进行讲解。在这里插入图片描述

二、单向广搜简介

【例题1】公主被关在一个 n × m ( n , m < = 500 ) n \times m(n,m <= 500) n×m(n,m<=500) 的迷宫里,主公想在最快的时间内救出公主。但是迷宫太大,而且有各种墙阻挡,主公每次只能在 上、下、左、右 四个方向内选择周围的非墙体格子前进一格,并且花费 1 单位时间,问主公救出公主的最少时间。

图二-1
(图中 ♂ 代表主公,♀代表公主,□ 代表墙体不能通行)

三、先进先出队列

1、队列的基础结构

class Queue {
public:
    Queue();
    virtual ~Queue();
public:
    ...
private:
    QueueData *data_;
    int front_, rear_;
};
const int MAXQUEUE = 1000000;

Queue::Queue() : data_(NULL) {
    data_ = new QueueData[MAXQUEUE];
}

Queue::~Queue() {
    if (data_) {
        delete[] data_;
        data_ = NULL;
    }
}

图三-1-3

2、队列的数据元素

struct QueueData {
    int height;
};
struct QueueData {
    int age;
};
struct QueueData {
    int x, y;
};
struct QueueData {
    int width, height;
};
struct QueueData {
    int x, y, z;
};
struct QueueData {
    int x, y, dir;
};

3、队列的接口

class Queue {
    ...
public:
    void clear();                    // 1)清空队列
    void push(const QueueData& bs);  // 2)压入数据
    QueueData& pop();                // 3)弹出数据
public:
    bool empty() const;              // 4)队列是否为空
private:
    ...
};

1)清空队列

void Queue::clear() {
    front_ = rear_ = 0;
}

在这里插入图片描述

图三-3-1

2)压入数据

void Queue::push(const QueueData& bs) {
    data_[rear_++] = bs;
}

图三-3-2

3)弹出数据

QueueData& Queue::pop(){
    return data_[front_++];
}

在这里插入图片描述

图三-3-3

4)队列判空

bool Queue::empty() const {
    return front_ == rear_;
}

4、队列的容错机制

1)循环队列

void Queue::push(const QueueData& bs) {
    data_[rear_++] = bs;
    if (rear_ == MAXQUEUE) rear_ = 0;
}
QueueData& Queue::pop(){
    if (++front_ == MAXQUEUE) front_ = 0;
    if (front_ == 0)
        return data_[MAXQUEUE - 1];
    else
        return data_[front_ - 1];
}

2)动态扩容

四、单向广搜的原理

1、状态的概念

1)状态

2)状态转移

图四-1-1

3)初始状态 和 结束状态

图四-1-2

4)状态哈希

2、状态的程序描述

1)结构体定义

struct Pos {
    int x, y;
    
    bool isInBound() {
        return !(x < 0 || y < 0 || x >= XMAX || y >= YMAX);
    }
    bool isObstacle() {
        return (Map[x][y] == MAP_BLOCK);
    }
};

struct BFSState {
    Pos p;
    ...
};

2)接口定义

const int MAXSTATE = 1000000;

struct BFSState {
	...
public:
    inline bool isValidState();                   // 1)
    inline bool isFinalState();                   // 2)
    inline int getStep() const;
    inline void setStep(int step);
protected:
    int getStateKey() const;
public:
    static int step[MAXSTATE];                   // 3)
};
bool BFSState::isValidState() {
    return p.isInBound() && !p.isObstacle();
}
bool BFSState::isFinalState() {
    return (Map[p.x][p.y] == MAP_EXIT);
}
int BFSState::getStep() const {
    return step[getStateKey()];
}
void BFSState::setStep(int sp) {
    step[getStateKey()] = sp;
}

3、状态的降维

int BFSState::getStateKey() const {
    return (p.x * K) + p.y;
}
int BFSState::getStateKey() const {
    return p.x << 6 | p.y;
}
int stateId = 0;
for (int i = 0; i < K; ++i)
    for (int j = 0; j < K; j++)
            pos2State[i][j] = stateId++;
int BFSState::getStateKey() const {
    return pos2State[p.x][p.y];
}

4、单向广搜的实现

1)广搜算法描述

单向广搜的算法大致可以描述如下:
  1)初始化所有状态的步数为无穷大,并且清空队列;
  2)将 起始状态 放进队列,标记 起始状态 对应步数为 0;
  3)如果队列不为空,弹出一个队列首元素,如果是 结束状态,则返回 结束状态 对应步数;否则根据这个状态扩展状态继续压入队列;
  4)如果队列为空,说明没有找到需要找的 结束状态,返回无穷大;

2)广搜算法框架

class BFSGraph {
public:
    int  bfs(BFSState startState);
private:
    void bfs_extendstate(const BFSState& fromState);
    void bfs_initialize(BFSState startState);
private:
    Queue queue_;
};
const int inf = -1;

int BFSGraph::bfs(BFSState startState) {
    bfs_initialize(startState);        // 1)
    while (!queue_.empty()) {
        BFSState bs = queue_.pop();    
        if (bs.isFinalState()) {       // 2)
            return bs.getStep();
        }
        bfs_extendstate(bs);           // 3)
    }
    return inf;
}

3)广搜算法初始化

const int inf = -1;

void BFSGraph::bfs_initialize(BFSState startState) {
    memset(BFSState::step, inf, sizeof(BFSState::step));
    queue_.clear();
    startState.setStep(0);                      
    queue_.push(startState);
}

4)广搜算法的状态扩展

图四-4-1
const int dir[DIR_COUNT][2] = {
    { 1, 0 },  // 下
    { 0, 1 },  // 右
    { 0, -1 }, // 左
    { -1, 0 }  // 上
};

图四-4-2

void BFSGraph::bfs_extendstate(const BFSState& fromState) {
    int stp = fromState.getStep() + 1;            // 1)
    BFSState toState;
    for (int i = 0; i < DIR_COUNT; ++i) {
        toState.p = fromState.p.move(i);          // 2)
        if (!toState.isValidState() || toState.getStep() != inf) {            
            continue;                             // 3)
        }
        toState.setStep(stp);                     // 4)
        queue_.push(toState);
    }
}
struct Pos {
    ... 
    Pos move(int dirIndex) const {
        return Pos(x + dir[dirIndex][0], y + dir[dirIndex][1]);
    }
};

五、单向广搜的应用场景

1、迷宫问题

1)双人迷宫

【例题2】给定一个 n × m ( n , m < = 20 ) n \times m (n,m <= 20) n×m(n,m<=20) 的迷宫,有些格子是墙体不能进入,迷宫中有一个 主公 和 一位 公主,主公每次可以选择上、下、左、右四个方向进行移动,每次主公移动的同时,公主可以按照相反方向移动一格(如果没有墙体遮挡的话)。当主公和公主相邻或者进入同一个格子则算游戏结束,问至少多少步能让游戏结束。

struct BFSState {
    Pos p[2];
    ...
};
bool BFSState::isFinalState() {
    return abs(p[0].x - p[1].x) + abs(p[0].y - p[1].y) <= 1;
}

2)推箱子

【例题3】给定一个 n × m ( n , m < = 8 ) n \times m (n,m <= 8) n×m(n,m<=8) 的迷宫,上面有 x ( x < = 4 ) x(x <= 4) x(x<=4) 个箱子 和 1个人,以及一些障碍和箱子需要放置的最终位置,求一种方案,用最少步数将所有的箱子推到指定位置。

图五-1-1

struct BFSState {
    Pos man, box[4];
    ...
};

3)右转迷宫

【例题4】给定一个 n × m ( n , m < = 500 ) n \times m (n,m <= 500) n×m(n,m<=500) 的迷宫,一个入口一个出口。走迷宫的规则是优先选择右边的方向走,如果右边有墙就往前走,如果还有墙就往左,如果还有就掉头,问从入口到出口,以及出口到入口,能否将整个迷宫的区域走遍。如图5就是一种可行方案。在这里插入图片描述

图五-1-5

struct BFSState {
    Pos p;
    char dir;
    ...
};

4)收集物品

【例题5】给定一个 n × m ( n , m < = 20 ) n \times m (n,m <= 20) n×m(n,m<=20) 的迷宫,一个入口一个出口。并且有 x ( x < = 10 ) x( x <= 10 ) x(x<=10) 个金币,问从入口到出口并且收集到所有 x 的最少时间。

图五-1-7

struct BFSState {
    Pos p;
    int coinMask;
    ...
};

5)贪吃蛇

【例题6】一个 n × m ( n , m < = 20 ) n \times m (n,m <= 20) n×m(n,m<=20) 的迷宫,左上角 (0, 0) 为出口,一条蛇在迷宫中,蛇的身体长度为 L,最多占用 8 个格子,有上下左右四个方向可以走,蛇走的时候不能碰到自己的身体,问最少需要多少步才能走到出口。

图五-1-8

struct BFSState {
    Pos p;
    int dir[7];
    ...
};
struct BFSState {
    Pos p;
    int dirMask;
    ...
};

2、同余搜索

【例题7】给定一个不能被 2 或 5 整除的数 n ( 0 < = n < = 10000 ) n (0 <= n <= 10000) n(0<=n<=10000),求一个十进制表示都是 1 的数 K K K ,使得 K K K 是 n n n 的倍数,且最小。例如: n = 3 n = 3 n=3,那么答案就是 111,因为 111 m o d    3 = 0 111 \mod 3 = 0 111mod3=0。

3、预处理


本文所有示例代码均可在以下 github 上找到:github.com/WhereIsHeroFrom/模板/广度优先搜索



六、单向广搜题集整理

题目链接难度解法
PKU 1096 Space Station Shielding★☆☆☆☆FloodFill
HDU 2952 Counting Sheep★☆☆☆☆FloodFill
HDU 1026 Ignatius and the Princess I★☆☆☆☆优先队列应用
HDU 1240 Asteroids!★☆☆☆☆【例题1】三维迷宫
HDU 1415 Jugs★☆☆☆☆经典广搜 - 倒水问题
HDU 1495 非常可乐★☆☆☆☆经典广搜 - 倒水问题
HDU 1195 Open the Lock★☆☆☆☆一维的数码可达问题
PKU 1915 Knight Moves★★☆☆☆马的走位
HDU 1372 Knight Moves★★☆☆☆马的走位
HDU 2235 机器人的容器★★☆☆☆FloodFill
HDU 3713 Double Maze★★☆☆☆2个人的迷宫问题
HDU 2216 Game III★★☆☆☆【例题2】2个人的迷宫问题
HDU 3309 Roll The Cube★★☆☆☆2个人的迷宫问题
HDU 1254 推箱子★★☆☆☆【例题3】推箱子问题
PKU 1475 Pushing Boxes★★☆☆☆【例题3】推箱子问题
HDU 1253 胜利大逃亡★★☆☆☆三维迷宫
HDU 1252 Hike on a Graph★★☆☆☆3个人的迷宫问题
HDU 1044 Collect More Jewels★★☆☆☆【例题5】二进制状态压缩的应用
PKU 2157 Maze★★☆☆☆二进制状态压缩的应用
HDU 3220 Alice’s Cube★★☆☆☆预处理 + 位运算
HDU 1429 胜利大逃亡(续)★★☆☆☆二进制状态压缩的应用
PKU 1077 Eight★★☆☆☆经典八数码
HDU 2170 Frogger★★☆☆☆带停留的搜索
HDU 1226 超级密码★★☆☆☆枚举位数
PKU 2551 Ones★★☆☆☆同余搜索
PKU 1426 Find The Multiple★★☆☆☆同余搜索
PKU 1860 Currency Exchange★★☆☆☆SPFA
PKU 1237 The Postal Worker Rings★★☆☆☆SPFA
PKU 1724 ROADS★★☆☆☆优先队列应用
HDU 2822 Dogs★★☆☆☆优先队列应用
HDU 2851 Lode Runner★★☆☆☆优先队列应用
HDU 2237 无题III★★☆☆☆多维状态搜索
HDU 3912 Turn Right★★☆☆☆【例题4】右转迷宫 + 增加方向维度
PKU 2283 Different Digits★★★☆☆同余搜索
PKU 2206 Magic Multiplying Machine★★★☆☆同余搜索
HDU 1104 Remainder★★★☆☆同余搜索
PKU 3000 Frogger★★★☆☆同余搜索
HDU 1317 XYZZY★★★☆☆最长路判环
HDU 1384 Intervals★★★☆☆差分约束
HDU 1531 King★★★☆☆差分约束
PKU 1716 Integer Intervals★★★☆☆差分约束
PKU 3501 Escape from Enemy Territory★★★☆☆二分答案 + BFS
PKU 1292 Will Indiana Jones Get★★★☆☆二分答案 + BFS
PKU 1485 Fast Food★★★☆☆SPFA
PKU 1511 Invitation Cards★★★☆☆SPFA
PKU 1545 Galactic Import★★★☆☆SPFA
PKU 1734 Sightseeing trip★★★☆☆无向图最小环
PKU 1420 Spreadsheet★★★☆☆建立拓扑图后广搜
PKU 2353 Ministry★★★☆☆需要存路径
PKU 2046 Gap★★★☆☆A*
PKU 1778 All Discs Considered★★★☆☆
PKU 1097 Roads Scholar★★★☆☆SPFA
PKU 1324 Holedox Moving★★★☆☆【例题6】状态压缩的广搜
PKU 1062 昂贵的聘礼★★★☆☆优先队列应用
PKU 3897 Maze Stretching★★★☆☆
PKU 3346 Treasure of the Chimp★★★☆☆
PKU 2983 Is the Information Reliable★★★☆☆最长路判环
PKU 1482 It’s not a Bug, It’s a★★★☆☆
HDU 3008 Warcraft★★★☆☆
HDU 3036 Escape★★★☆☆
PKU 3322 Bloxorz I★★★☆☆当年比较流行这个游戏
HDU 1043 Eight★★★☆☆数据较强,需要预处理
HDU 1307 N-Credible Mazes★★★☆☆多维空间搜索,散列HASH
HDU 3681 Prison Break★★★☆☆状态压缩
HDU 3500 Fling★★★☆☆某个消除游戏
HDU 2605 Snake★★★★☆状态压缩
HDU 1122 Direct Visibility★★★★☆计算几何判断连通性
PKU 3912Up and Down★★★★☆离散化 + BFS
PKU 3463 Sightseeing★★★★☆SPFA
PKU 3328 Cliff Climbing★★★★☆日本人的题就是这么长
PKU 3455 Cheesy Chess★★★★☆仔细看题
PKU 1924 The Treasure★★★★☆
PKU 3702 Chessman★★★★★弄清状态同余的概念
HDU 3278 Puzzle★★★★★几乎尝试了所有的搜索 -_-
HDU 3900 Unblock Me★★★★★8进制压缩状态,散列HASH,位运算加速

标签:状态,夜深人静,PKU,队列,单向,HDU,int,算法,BFSState
来源: https://blog.csdn.net/WhereIsHeroFrom/article/details/112727824