目录

1. BFS原理

1.1 BFS介绍

1.2 BFS如何实现?

1.3 BFS处理逻辑 可视化

2. C代码实现

2.1 队列(链表)

2.2 前期准备

2.3 BFS函数代码

2.4 主函数输出

2.5 BFS总代码

2.6 运行结果演示

3. 进阶:如何标记路径?

3.1 思路

3.2 最短路径标记的实现

3.3 BFS路径标记总代码

3.4 运行结果演示

4. 总结


1. BFS原理

1.1 BFS介绍

        BFS(广度优先探索),与 DFS(深度优先探索) 相对应。形象地说,是一种从起点开始不断向外扩散搜索的一种搜索算法。

        如图,黑色色块代表起点,灰色代表未搜索色块,灰白色代表已搜索色块,蓝色代表当前本轮搜索到的色块,绿色代表下一轮将搜索的色块,红色代表终点。

1.2 BFS如何实现?

        (形象化描述)该算法中,首先需要一个 标记遍历的数组 ,记录已搜索过的地方。其次,对于本轮与下一轮的顺序处理,需要使用 队列 进行依次处理,这样保证了只有待上一轮处理完才会轮到下一轮处理。

        对于本轮搜索的每一个元素,会将其从队首弹出并判断是否为终点,然后将符合搜索条件的下一轮元素从队尾入队,如果无符合搜索条件的下一轮元素,则无新的元素入队。若到搜索完毕都没发现终点,则不再有新的元素入队,最后一轮元素不断退队直至队列为;若搜索到终点,则不再搜索, 队伍剩余元素不断退队直至队列为空。

        总而言之,BFS是 顺序处理 上一轮与这一轮的元素,直至搜索到终点或再无符合搜索条件元素入队。

1.3 BFS处理逻辑 可视化

        如今有一搜索范围,确定起点与终点。

初始化:初始化标记遍历都为 0;生成队列,将起点入队,标记为 1。

搜索条件:标记遍历不为 1,地图不为墙

循环操作:(循环终止条件:队列为

        (1) 将队首(此时为起点)弹出,并判断是否为终点(否,继续搜索)。

        (2) 向四周查找符合搜索条件的元素,将这些元素逐个入队,标记为1。同时判断是否为终点(否,继续搜索)。

    进入 下一个循环 

        (1) 将队首(此时为起点上方的元素)弹出,并判断是否为终点(否,继续搜索)。

        (2) 向四周查找符合搜索条件的元素,将这些元素逐个入队,标记为1。判断是否为终点(否,继续搜索)。

    ......处理至下一轮并 继续循环 

 

边界处:只将符合搜索条件的元素入队即可。

搜索到终点:停止继续搜索,不再有新元素入队,队列中剩余元素直接逐个出队直至

灰色为已出队元素,深蓝色为当前即将出队元素

结束后:


2. C代码实现

2.1 队列(链表

固定头节点  head 

typedef struct node
{
    // 坐标记录
    int x;
    int y;
    // 第n轮搜索
    int step;
    // 链表链接
    struct node *next;
} node;

// 头节点
node *head;

入队

void push(node *newNode)
{
    node *p = head;
     // 遍历至队尾
    while (p->next != NULL)
    {
        p = p->next;
    }
    // 链接
    p->next = newNode;
}

出队

void pop()
{
    // 删除头节点下一节点
    node *temp = head->next;
    head->next = temp->next;
    free(temp);
}

2.2 前期准备

Map & Sign

#define ROW 8
#define COL 8
// ROW * COL Map
// 0为路径,1为墙,2为终点
int map[ROW + 2][COL + 2] = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 1, 1},
    {1, 1, 1, 1, 0, 1, 1, 1, 1, 1},
    {1, 0, 0, 1, 0, 0, 1, 0, 1, 1},
    {1, 0, 1, 0, 0, 1, 1, 0, 1, 1},
    {1, 0, 1, 0, 1, 1, 1, 0, 1, 1},
    {1, 0, 1, 0, 0, 0, 0, 0, 1, 1},
    {1, 0, 0, 0, 1, 1, 1, 0, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 0, 2, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};
int sign[ROW + 2][COL + 2] = {};

方向

// += direction[i].x
// += direction[i].y
struct
{
    int x;
    int y;
} direction[4] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

2.3 BFS函数代码

// 以 x, y 坐标为起点
int bfs(int x, int y)
{
    // 记录最小步数
    int MinStep = 0;
    // 记录是否已抵达终点
    int flag = 0;

    // 初始化头节点        
    head = (node *)malloc(sizeof(node));
    head->next = NULL;

    // 起点节点入队
    node *start = (node *)malloc(sizeof(node));
    start->x = x;
    start->y = y;
    start->step = 0;
    start->next = NULL;

    push(start);

    // 标记遍历为 1
    sign[x][y] = 1;
    // 在地图上标记起点为 5
    map[x][y] = 5;

    do
    {
        // 提取队首(注意此时还未出队)
        node *prevNode = head->next;
		
        // 如果当前已找到终点,队首直接出队,不再继续搜索
        if (flag)
        {
            pop();
            continue;
        }

        // 未找到,继续搜索
        int i;
        for (i = 0; i < 4; ++i)
        {
            // 对四个方向进行搜索判断
            // 新建节点存储待判断元素信息
            node *newNode = (node *)malloc(sizeof(node));
            newNode->x = prevNode->x + direction[i].x;
            newNode->y = prevNode->y + direction[i].y;
            newNode->step = prevNode->step + 1;
            newNode->next = NULL;

            // 若找到终点,标记后不再搜索
            if (map[newNode->x][newNode->y] == 2)
            {
                flag = 1;
                // 地图上标记为 3
                map[newNode->x][newNode->y] = 3;
                // 记录最小步数
                MinStep = newNode->step;
                free(newNode);
                break;
            }

            // 若符合搜索条件,入队并标记
            else if (sign[newNode->x][newNode->y] == 0 && map[newNode->x][newNode->y] == 0)
            {
                // 入队
                push(newNode);
                // 标记遍历为 1
                sign[newNode->x][newNode->y] = 1;
                // 地图上标记为 3
                map[newNode->x][newNode->y] = 3;
            }
            // 不符合搜索条件
            else
                free(newNode);
        }
        // 此处的下一级元素全部入队后,队首出队
        pop();
    } while (head->next != NULL);

    return MinStep;
}

2.4 主函数输出

int main()
{
    // 输出初始Map状态
    int i, j;
    for (i = 1; i <= ROW; ++i)
    {
        for (j = 1; j <= COL; ++j)
        {
            printf("%d ", map[i][j]);
        }
        putchar(10);
    }
    putchar(10);

    // BFS 从 1,1 开始
    int MinStep = bfs(1, 1);

    // 输出最小步数
    if (MinStep)
        printf("MinStep = %d\n", MinStep);
    else
        printf("Not Found!");

    for (i = 1; i <= ROW; ++i)
    {
        for (j = 1; j <= COL; ++j)
        {
            printf("%d ", map[i][j]);
        }
        putchar(10);
    }

    // 小心内存泄漏
    free(head);
    return 0;
}

2.5 BFS总代码

#include <stdio.h>
#include <stdlib.h>

#define ROW 8
#define COL 8

typedef struct node
{
    int x;
    int y;
    int step;
    struct node *next;
} node;
node *head;

struct
{
    int x;
    int y;
} direction[4] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

int map[ROW + 2][COL + 2] = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 1, 1},
    {1, 1, 1, 1, 0, 1, 1, 1, 1, 1},
    {1, 0, 0, 1, 0, 0, 1, 0, 1, 1},
    {1, 0, 1, 0, 0, 1, 1, 0, 1, 1},
    {1, 0, 1, 0, 1, 1, 1, 0, 1, 1},
    {1, 0, 1, 0, 0, 0, 0, 0, 1, 1},
    {1, 0, 0, 0, 1, 1, 1, 0, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 0, 2, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};
int sign[ROW + 2][COL + 2] = {};

void push(node *newNode)
{
    node *p = head;
    while (p->next != NULL)
    {
        p = p->next;
    }
    p->next = newNode;
}

void pop()
{
    node *temp = head->next;
    head->next = temp->next;
    free(temp);
}

int bfs(int x, int y)
{
    int MinStep = 0;
    int flag = 0;
     
    head = (node *)malloc(sizeof(node));
    head->next = NULL;

    node *start = (node *)malloc(sizeof(node));
    start->x = x;
    start->y = y;
    start->step = 0;
    start->next = NULL;

    push(start);

    sign[x][y] = 1;
    map[x][y] = 5;

    do
    {
        node *prevNode = head->next;
		
        if (flag)
        {
            pop();
            continue;
        }

        int i;
        for (i = 0; i < 4; ++i)
        {
            node *newNode = (node *)malloc(sizeof(node));
            newNode->x = prevNode->x + direction[i].x;
            newNode->y = prevNode->y + direction[i].y;
            newNode->step = prevNode->step + 1;
            newNode->next = NULL;

            if (map[newNode->x][newNode->y] == 2)
            {
                flag = 1;
                map[newNode->x][newNode->y] = 3;
                MinStep = newNode->step;
                free(newNode);
                break;
            }

            else if (sign[newNode->x][newNode->y] == 0 && map[newNode->x][newNode->y] == 0)
            {
                push(newNode);
                sign[newNode->x][newNode->y] = 1;
                map[newNode->x][newNode->y] = 3;
            }
            else
                free(newNode);
        }
        pop();
    } while (head->next != NULL);
    return MinStep;
}

int main()
{
    int i, j;
    for (i = 1; i <= ROW; ++i)
	{
        for (j = 1; j <= COL; ++j)
        {
            printf("%d ", map[i][j]);
        }
        putchar(10);
    }
    putchar(10);

    int MinStep = bfs(1, 1);

    if(MinStep)
        printf("Min step = %d\n", MinStep);
    else
        printf("Not Found!");

    for (i = 1; i <= ROW; ++i)
    {
        for (j = 1; j <= COL; ++j)
        {
            printf("%d ", map[i][j]);
        }
        putchar(10);
    }
	
    free(head);
    return 0;
}

2.6 运行结果演示


3. 进阶:如何标记路径?

3.1 思路

        首先,要求记录每一子节点的父节点。

        于是,应在结构体中增加父节点指针。

typedef struct node
{
    struct node *prev;    // 父节点
    int x;
    int y;
    int step;
    struct node *next;
} node;

        其次,所有入队节点应保留用于回溯查询。

        于是,不能弹出队首,应将指针指向下一节点

        最后,要记得 free 防止内存泄漏哦!

void quit()
{
    while (head->next != NULL)
    {
        node *temp = head->next;
        head->next = temp->next;
        free(temp);
    }
    free(head);
    head = NULL;
}

3.2 最短路径标记的实现

        从终点开始不断向前找父节点直至  NULL

void route()
{
    node *p = head;
    // 遍历到队尾(终点节点)
    while (p->next != NULL)
    {
        p = p->next;
    }
    // 从终点节点的上一节点开始向前寻找路径(父节点)
    p = p->prev;
    while (p->prev != NULL)
    {
        // 在地图上标记为 4
        map[p->x][p->y] = 4;
        // 向前到父节点
        p = p->prev;
    }
}

3.3 BFS路径标记总代码

#include <stdio.h>
#include <stdlib.h>

#define ROW 8
#define COL 8
// 高亮显示
#define Green "\033[1;32m"
// 清除高亮
#define None "\033[m"

typedef struct node
{
    struct node *prev;
    int x;
    int y;
    int step;
    struct node *next;
} node;
node *head;

struct
{
    int x;
    int y;
} direction[4] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

int map[ROW + 2][COL + 2] = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 1, 1},
    {1, 1, 1, 1, 0, 1, 1, 1, 1, 1},
    {1, 0, 0, 1, 0, 0, 1, 0, 1, 1},
    {1, 0, 1, 0, 0, 1, 1, 0, 1, 1},
    {1, 0, 1, 0, 1, 1, 1, 0, 1, 1},
    {1, 0, 1, 0, 0, 0, 0, 0, 1, 1},
    {1, 0, 0, 0, 1, 1, 1, 0, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 0, 2, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};
int sign[ROW + 2][COL + 2] = {};

void push(node *newNode)
{
    node *p = head;
    while (p->next != NULL)
    {
        p = p->next;
    }
    p->next = newNode;
}

void quit()
{
    while (head->next != NULL)
    {
        node *temp = head->next;
        head->next = temp->next;
        free(temp);
    }
    free(head);
    head = NULL;
}

void route()
{
    node *p = head;
    while (p->next != NULL)
    {
        p = p->next;
    }
    p = p->prev;
    while (p->prev != NULL)
    {
        map[p->x][p->y] = 4;
        p = p->prev;
    }
}

int bfs(int x, int y)
{
    int flag = 0;

    head = (node *)malloc(sizeof(node));
    head->next = NULL;

    node *start = (node *)malloc(sizeof(node));
    // 初始化 start 节点的父节点为 NULL 用于终止路径标记
    start->prev = NULL;
    start->x = x;
    start->y = y;
    start->step = 0;
    start->next = NULL;

    push(start);
    sign[x][y] = 1;
    map[x][y] = 5;

    node *prevNode = head;
    do
    {
        // 指向下一节点
        prevNode = prevNode->next;
        if (flag)
            continue;

        int i;
        for (i = 0; i < 4; ++i)
        {
            node *newNode = (node *)malloc(sizeof(node));
            // 记录父节点
            newNode->prev = prevNode;
            newNode->x = prevNode->x + direction[i].x;
            newNode->y = prevNode->y + direction[i].y;
            newNode->step = prevNode->step + 1;
            newNode->next = NULL;
            if (map[newNode->x][newNode->y] == 2)
            {
                flag = 1;
                // 终点节点入队
                push(newNode);
                break;
            }
            else if (sign[newNode->x][newNode->y] == 0 && map[newNode->x][newNode->y] == 0)
            {
                push(newNode);
                sign[newNode->x][newNode->y] = 1;
                map[newNode->x][newNode->y] = 3;
            }
            else
                free(newNode);
        }
    } while (prevNode->next != NULL);
    // 前进直至终点节点
	
    // 输出终点节点步数
    return prevNode->step;
}

int main()
{
    // 便于之后高亮显示,否则会乱码
    system("cls");
	
    int i, j;
    for (i = 1; i <= ROW; ++i)
    {
        for (j = 1; j <= COL; ++j)
        {
            printf("%d ", map[i][j]);
        }
        putchar(10);
    }
    putchar(10);

    int MinStep = bfs(1, 1);
    // 标记最短路径
    route();

    if(MinStep)
        printf("Min step = %d\n", MinStep);
    else
        printf("Not Found!");
	
    for (i = 1; i <= ROW; ++i)
    {
        for (j = 1; j <= COL; ++j)
        {
            // 高亮显示路径
            if (map[i][j] == 4)
                printf(Green "%d " None, map[i][j]);
            else
                printf("%d ", map[i][j]);
        }
        putchar(10);
    }
	
    // 防止内存泄漏
    quit();
    return 0;
}

3.4 运行结果演示

4. 总结

        BFS广度优先算法的实现要注意入队与出队时机循环结束条件以及继续搜索条件

        试试看自己写一遍,想想如果想以矩形方式扩散搜索该怎么实现呢?(如下图)


(文中代码皆已经过编译运行)

文章为本人原创,创作不易,感谢支持!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐