惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
Webroot Blog
Webroot Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
TaoSecurity Blog
TaoSecurity Blog
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
N
News | PayPal Newsroom
S
Security Affairs
T
Tor Project blog
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security @ Cisco Blogs
H
Heimdal Security Blog
Hacker News: Ask HN
Hacker News: Ask HN
Help Net Security
Help Net Security
U
Unit 42
云风的 BLOG
云风的 BLOG
The Hacker News
The Hacker News
Cisco Talos Blog
Cisco Talos Blog
量子位
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
有赞技术团队
有赞技术团队
T
Troy Hunt's Blog
P
Privacy & Cybersecurity Law Blog
Forbes - Security
Forbes - Security
人人都是产品经理
人人都是产品经理
L
Lohrmann on Cybersecurity
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
腾讯CDC
AI
AI
Last Week in AI
Last Week in AI
Latest news
Latest news
Google Online Security Blog
Google Online Security Blog
N
Netflix TechBlog - Medium
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
Martin Fowler
Martin Fowler
Blog — PlanetScale
Blog — PlanetScale
V2EX - 技术
V2EX - 技术
酷 壳 – CoolShell
酷 壳 – CoolShell

青空之蓝

[青空之蓝-2023] - 色彩 | 青空之蓝 [青空之蓝-2022] - 平静 | 青空之蓝 [青空之蓝-2021] - 远望 | 青空之蓝 浅谈垃圾回收 | 青空之蓝 浅谈泛型擦除 | 青空之蓝 浅谈单点登录 | 青空之蓝 使用 Kotlin 编写 Spring 测试 | 青空之蓝 设计模式系列文章 | 青空之蓝 从零实现一个 Java 微框架 - IoC | 青空之蓝 从零实现一个 Java 微框架 - 前言 | 青空之蓝 浅谈 JVM:类加载 | 青空之蓝 浅谈 IO | 青空之蓝 浅谈并发:synchronized & ReentrantLock | 青空之蓝 浅谈并发:CAS & AQS | 青空之蓝 浅谈并发:ThreadLocal | 青空之蓝 浅谈并发:三大特性 | 青空之蓝 浅谈组合注解 & 注解别名 | 青空之蓝 [青空之蓝-2020]-迷茫 | 青空之蓝 Java 系列文章 | 青空之蓝 HTTP 系列文章 | 青空之蓝 浅谈 EatWhatYouKill | 青空之蓝 浅谈可扩展线程池 | 青空之蓝 聊聊写框架 | 青空之蓝 聊聊现状-[2020-09] | 青空之蓝 浅谈并发:锁 | 青空之蓝 浅谈并发:基础 | 青空之蓝 浅谈缓存 | 青空之蓝 无须定义类,Spring 快速注入 Json 参数 | 青空之蓝 浅谈 Proxy 和 Aop | 青空之蓝 从零实现一个 PHP 微框架 - 初始化请求 | 青空之蓝 为 Vue3 添加一个简单的 Store | 青空之蓝 从零实现一个 PHP 微框架 - 服务提供者 | 青空之蓝 WSL2 踩坑记录 | 青空之蓝 浅谈浏览器Event Loop [更新] | 青空之蓝 从零实现一个 PHP 微框架 - Bootstrap 启动加载 | 青空之蓝 从零实现一个 PHP 微框架 - IoC 容器 | 青空之蓝 从零实现一个 PHP 微框架 - PSR & Composer | 青空之蓝 从零实现一个 PHP 微框架 - 前言 | 青空之蓝 MVVM 简单实现 | 青空之蓝 浅谈 DI 和 IoC | 青空之蓝 中间件实现 [PHP] | 青空之蓝 告别 Windows 终端的难看难用,打造好用的 PowerShell | 青空之蓝 VSCode Java输出中文乱码问题解决[更新] | 青空之蓝 浅谈浏览器渲染 | 青空之蓝 Vue-Cli@2 项目迁移日志 | 青空之蓝 Laragon & Scoop 集成踩坑记录 | 青空之蓝 「一行代码」优雅管理 Windows 软件 | 青空之蓝 [青空之蓝-2019]-年度总结 | 青空之蓝 为Vue添加简单的Store | 青空之蓝 为React添加简单的Store | 青空之蓝 为Vuex添加同步Action | 青空之蓝 浅谈B+树 | 青空之蓝 浅谈跳表 | 青空之蓝 浅谈数据库索引 | 青空之蓝 MySQL事务隔离 | 青空之蓝 算法复杂度分析(1) | 青空之蓝 一年来的经验总结 | 青空之蓝 Acrylic - VSCode Extension | 青空之蓝 ace编辑器设置惯性滚动 | 青空之蓝 Java二叉树实现 | 青空之蓝 Java图实现 | 青空之蓝 为apt方式安装的nginx重新编译增加WebDAV | 青空之蓝 XK-Editor - 一个支持富文本和Markdown的编辑器 | 青空之蓝 JS生成列表树 | 青空之蓝 Laravel生成目录树 | 青空之蓝 XK-Note - 集各种神奇功能的云笔记 | 青空之蓝 PHP GD生成验证码 | 青空之蓝 PHP GD图片处理[转换格式-水印-缩略图] | 青空之蓝 Origami - 简洁轻快的WordPress主题 | 青空之蓝 为WordPress启用WorkBox | 青空之蓝 [青空之蓝-2018]-年度总结 | 青空之蓝 VSCode Java手动导入jar和源码包 | 青空之蓝 Windows IP变化自动发送邮件 | 青空之蓝 C链表实现重制版 | 青空之蓝 C 结构体的定义和使用 | 青空之蓝 Java链表实现 | 青空之蓝 C 快速排序 | 青空之蓝 C 插入排序 | 青空之蓝 C 归并排序 | 青空之蓝 C语言链表实现 | 青空之蓝 VSCode配置Java调试环境[Windows] | 青空之蓝 C 选择排序 | 青空之蓝 C 冒泡排序 | 青空之蓝 VSCode配置PHP调试环境[Windows] | 青空之蓝 VSCode配置C/C++ GDB调试环境[Windows] | 青空之蓝 WordPress友情链接模板 | 青空之蓝 Intel Optane 傲腾内存体验 | 青空之蓝 Mysql双机热备实战 | 青空之蓝 博客一年记录 | 青空之蓝 为WordPress启用Service Worker | 青空之蓝 Bing每日一图API | 青空之蓝 iframe延迟加载 | 青空之蓝 写在2018年高考前 | 青空之蓝 The Fox主题汉化分享 | 青空之蓝 [青空之蓝-2017]-崭新 | 青空之蓝 本博客评论规则 | 青空之蓝 世界,您好! | 青空之蓝
图的搜索(遍历) - BFS & DFS | 青空之蓝
Otstar Lin · 2018-12-19 · via 青空之蓝

BFS,即 Breath First Search(广度优先搜索)

DFS,即 Deep First Search (深度优先搜索)

图的搜索是对于连通图的一种遍历策略,常用于走迷宫的问题

本文的算法基于 C 语言编写,过几天会使用 Java 重写这两个算法

另外本文的算法是对基于数组的图进行搜索,基于链表的搜索暂时未弄 (逃

1.BFS

广度优先搜索顾名思义,就是在广范围进行搜索,从一个顶点 V0 开始,辐射状地优先遍历其周围较广的区域

算法的基本思路

在广搜中不需要记录节点是否走过,但是要记录上一个节点的位置,若将整个过程画成树,即可观察到广搜是一层一层进行遍历的(这里就不画图了,逃),这时若要搜索下一层节点就需要读取上一层节点的位置,并对父节点的所有节点逐个进行搜索,将符合要求的子节点逐个存入下一层节点,直到判断到达终点即停止搜索

基础算法采用递归,存储搜索各层节点采用队列,但由于 C 语言中没有队列所以采用结构体数组代替,使用二维结构体数组,其中一维对应层数,另一维存储该层的节点,另外若要使广搜拥有寻迹功能,即可以输出行进路程中每一步,就需要在结构体中增加一个结构体指针指向父节点,然后记录最后一个节点的地址,通过访问指针来访问父节点,直到到达起点

算法的实现

#include <stdio.h>
//广度优先搜索 - 可寻迹

//地图数组
int map[100][100];
//地图的个数
int n;
//定义起点和终点
int begin_x = 0, begin_y = 0, end_x = 4, end_y = 4;
//能到达终点的最后一个节点的指针
struct POINT *end_p = NULL;
//步进存储结构体
struct POINT
{
    int x; //X轴坐标
    int y; //Y轴坐标
    int is = 0; //是否是达到需求的节点,作为下一轮搜索的索引
    struct POINT *prev;
} po[100][100]; //po[轮数][不同节点存储,逐个存储]

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

//首节点应传入-1
int BFS(int u)
{
    //重置定位个数用的索引
    int i = 0;
    //判断是否是起点,若是只需要将起点存入即可,同时将轮数索引加 1
    if (u == -1)
    {
        po[u + 1][0].x = begin_x;
        po[u + 1][0].y = begin_y;
        po[u + 1][0].is = 1;
        //再次调用进行下一轮搜索
        return BFS(u + 1);
    }
    //重置下一轮搜索的索引
    int j = 0;
    //判断父节点是否取尽
    while (po[u][i].is == 1)
    {
        //搜索部分
        int x = po[u][i].x;
        int y = po[u][i].y;
        //判断是否已经到达终点,若是则返回最短步数
        if (x == end_x && y == end_y)
        {
            end_p = &po[u][i];
            return u+1;
        }
        //将父节点设为不符合条件的节点,防止重复
        map[x][y] = 1;

        int k;
        //循环搜索各方向
        for (k = 0; k < 4; k++)
        {
            //定义临时x的坐标
            int tx = x + next[k][0];
            //定义临时y坐标
            int ty = y + next[k][1];
            //判断临时坐标是否到达边界,若是则尝试下一组
            if (tx < 0 || tx >= n || ty < 0 || ty >= n)
            {
                continue;
            }
            //判断该节点的数据是否符合要求,若符合就将该节点存入队列
            if (map[tx][ty] == 0)
            {
                //存入队列操作
                po[u + 1][j].x = tx;
                po[u + 1][j].y = ty;
                //将存在数据的队列节点标记,方便遍历队列,省去传递队列个数的操作
                po[u + 1][j].is = 1;
                //定位上一个节点
                po[u + 1][j].prev = &po[u][i];
                //将子节点的索引递增一,防止下一个子节点覆盖上一个子节点
                j++;
            }
        }
        //将父节点的索引递增一,进行下一轮子节点的搜索
        i++;
    }
    //当前父节点的所有子节点都已经搜索完毕,再次调用搜索函数,将当前所有子节点当成父节点,进行下一轮搜索
    return BFS(u + 1);
}

int main(int argc, char const *argv[])
{
    //输入图
    int i, j;
    printf("输入要输入图的长宽\n");
    scanf("%d", &n);
    printf("输入数据\n");
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < n; j++)
        {
            scanf("%d", &map[j][i]);
        }
    }
    // 定义存储输出轨迹的数组
    int foot[100][2];
    // 通过广度搜索返回最短步
    int k=BFS(-1);
    // 将每一步存入输出数组
    for(i=0;i<k;i++)
    {
        foot[i][0] = end_p->y;
        foot[i][1] = end_p->x;
        // 重新定位指针
        end_p = end_p->prev;
    }
    // 循环输出路径
    for(i=k-1;i>0;i--)
    {
        printf("(%d, %d)->",foot[i][0],foot[i][1]);
    }
    printf("(%d, %d)\n",foot[i][0],foot[i][1]);
    return 0;
}
#include <stdio.h>
//广度优先搜索 - 可寻迹

//地图数组
int map[100][100];
//地图的个数
int n;
//定义起点和终点
int begin_x = 0, begin_y = 0, end_x = 4, end_y = 4;
//能到达终点的最后一个节点的指针
struct POINT *end_p = NULL;
//步进存储结构体
struct POINT
{
    int x; //X轴坐标
    int y; //Y轴坐标
    int is = 0; //是否是达到需求的节点,作为下一轮搜索的索引
    struct POINT *prev;
} po[100][100]; //po[轮数][不同节点存储,逐个存储]

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

//首节点应传入-1
int BFS(int u)
{
    //重置定位个数用的索引
    int i = 0;
    //判断是否是起点,若是只需要将起点存入即可,同时将轮数索引加 1
    if (u == -1)
    {
        po[u + 1][0].x = begin_x;
        po[u + 1][0].y = begin_y;
        po[u + 1][0].is = 1;
        //再次调用进行下一轮搜索
        return BFS(u + 1);
    }
    //重置下一轮搜索的索引
    int j = 0;
    //判断父节点是否取尽
    while (po[u][i].is == 1)
    {
        //搜索部分
        int x = po[u][i].x;
        int y = po[u][i].y;
        //判断是否已经到达终点,若是则返回最短步数
        if (x == end_x && y == end_y)
        {
            end_p = &po[u][i];
            return u+1;
        }
        //将父节点设为不符合条件的节点,防止重复
        map[x][y] = 1;

        int k;
        //循环搜索各方向
        for (k = 0; k < 4; k++)
        {
            //定义临时x的坐标
            int tx = x + next[k][0];
            //定义临时y坐标
            int ty = y + next[k][1];
            //判断临时坐标是否到达边界,若是则尝试下一组
            if (tx < 0 || tx >= n || ty < 0 || ty >= n)
            {
                continue;
            }
            //判断该节点的数据是否符合要求,若符合就将该节点存入队列
            if (map[tx][ty] == 0)
            {
                //存入队列操作
                po[u + 1][j].x = tx;
                po[u + 1][j].y = ty;
                //将存在数据的队列节点标记,方便遍历队列,省去传递队列个数的操作
                po[u + 1][j].is = 1;
                //定位上一个节点
                po[u + 1][j].prev = &po[u][i];
                //将子节点的索引递增一,防止下一个子节点覆盖上一个子节点
                j++;
            }
        }
        //将父节点的索引递增一,进行下一轮子节点的搜索
        i++;
    }
    //当前父节点的所有子节点都已经搜索完毕,再次调用搜索函数,将当前所有子节点当成父节点,进行下一轮搜索
    return BFS(u + 1);
}

int main(int argc, char const *argv[])
{
    //输入图
    int i, j;
    printf("输入要输入图的长宽\n");
    scanf("%d", &n);
    printf("输入数据\n");
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < n; j++)
        {
            scanf("%d", &map[j][i]);
        }
    }
    // 定义存储输出轨迹的数组
    int foot[100][2];
    // 通过广度搜索返回最短步
    int k=BFS(-1);
    // 将每一步存入输出数组
    for(i=0;i<k;i++)
    {
        foot[i][0] = end_p->y;
        foot[i][1] = end_p->x;
        // 重新定位指针
        end_p = end_p->prev;
    }
    // 循环输出路径
    for(i=k-1;i>0;i--)
    {
        printf("(%d, %d)->",foot[i][0],foot[i][1]);
    }
    printf("(%d, %d)\n",foot[i][0],foot[i][1]);
    return 0;
}

本算法由于要进行寻迹操作,所以保留了每一层的数据,若不需要寻迹建议只保留两个队列,即两个一维结构体数组

2.DFS

深度优先搜索类似于广度优先搜索,也是一种图的搜索算法,但是不同于广度优先搜索,广搜注重范围,而深搜注重深度,通俗来说,广搜就是广撒网,深搜就是一路走到黑,不撞南墙不回头\( ̄︶ ̄\))

算法的基本思路

深搜需要定义一个跟原图相同的数组,然后通过该数组对数据进行标记,若已经走过就标记,回到父节点时将父节点的标记取消,基础算法依旧使用递归

算法的实现

#include <stdio.h>
#include <limits.h>
//深度优先搜索

//地图数组
int map[100][100];
//标记数组,用于标记是否已经搜索过
int book[100][100];
//地图的个数,最短的步数
int n, min = INT_MAX;
//定义起点和终点
int begin_x = 0, begin_y = 0, end_x = 4, end_y = 4;

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

void DFS(int x, int y, int step)
{
    //判断是否已经到达终点,若是则比较目前的步数是否是最短的,若是则将当前步数赋给min,同时退出函数
    if (x == end_x && y == end_y)
    {
        min = min < step ? min : step;
        return;
    }
    int k;
    if(x==0&&y==0) book[0][0] = 1;
    //循环搜索各方向
    for (k = 0; k < 4; k++)
    {
        //定义临时x的坐标
        int tx = x + next[k][0];
        //定义临时y坐标
        int ty = y + next[k][1];
        //判断临时坐标是否到达边界,若是则尝试下一组
        if (tx < 0 || tx >= n || ty < 0 || ty >= n)
        {
            continue;
        }
        //判断该坐标的数据是否符合条件,若符合并且该数值之前没有被搜索到,就进行下一步搜索
        if (map[tx][ty] == 0 && book[tx][ty] != 1)
        {
            //将该坐标标记为已经搜索过
            book[tx][ty] = 1;
            //进行下一步搜索,即向深层次搜索
            DFS(tx, ty, step + 1);
            //取消坐标标记,因为该坐标的子节点已经在上方的递归中搜索完毕
            book[tx][ty] = 0;
        }
    }
    return;
}

int main(int argc, char const *argv[])
{
    int i, j;
    printf("输入要输入图的长宽\n");
    scanf("%d", &n);
    printf("输入数据\n");
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < n; j++)
        {
            scanf("%d", &map[j][i]);
        }
    }
    end_x = n-1;
    end_y = n-1;
    DFS(begin_x, begin_y, 0);
    //打印最短的步数
    printf("%d", min);
    return 0;
}
#include <stdio.h>
#include <limits.h>
//深度优先搜索

//地图数组
int map[100][100];
//标记数组,用于标记是否已经搜索过
int book[100][100];
//地图的个数,最短的步数
int n, min = INT_MAX;
//定义起点和终点
int begin_x = 0, begin_y = 0, end_x = 4, end_y = 4;

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

void DFS(int x, int y, int step)
{
    //判断是否已经到达终点,若是则比较目前的步数是否是最短的,若是则将当前步数赋给min,同时退出函数
    if (x == end_x && y == end_y)
    {
        min = min < step ? min : step;
        return;
    }
    int k;
    if(x==0&&y==0) book[0][0] = 1;
    //循环搜索各方向
    for (k = 0; k < 4; k++)
    {
        //定义临时x的坐标
        int tx = x + next[k][0];
        //定义临时y坐标
        int ty = y + next[k][1];
        //判断临时坐标是否到达边界,若是则尝试下一组
        if (tx < 0 || tx >= n || ty < 0 || ty >= n)
        {
            continue;
        }
        //判断该坐标的数据是否符合条件,若符合并且该数值之前没有被搜索到,就进行下一步搜索
        if (map[tx][ty] == 0 && book[tx][ty] != 1)
        {
            //将该坐标标记为已经搜索过
            book[tx][ty] = 1;
            //进行下一步搜索,即向深层次搜索
            DFS(tx, ty, step + 1);
            //取消坐标标记,因为该坐标的子节点已经在上方的递归中搜索完毕
            book[tx][ty] = 0;
        }
    }
    return;
}

int main(int argc, char const *argv[])
{
    int i, j;
    printf("输入要输入图的长宽\n");
    scanf("%d", &n);
    printf("输入数据\n");
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < n; j++)
        {
            scanf("%d", &map[j][i]);
        }
    }
    end_x = n-1;
    end_y = n-1;
    DFS(begin_x, begin_y, 0);
    //打印最短的步数
    printf("%d", min);
    return 0;
}

两种算法的比较

广搜由由于需要维护多条路径,同时存储多条路径,所以在正常情况下广搜使用的内存会比深搜多,编写也相对复杂,但是由于广搜一般是采用队列来存储路径所以没有爆栈的危险(C 语言不适用),而深搜是使用栈进行存储的,由于系统分配给程序的栈是有限的,所以当深度过高时深受可能会出现爆栈。

一般来说用 DFS 解决的问题都可以用 BFS 来解决。DFS 多用于连通性问题因为其运行思想与人脑的思维很相似,故解决连通性问题更自然。BFS 多用于解决最短路问题,其运行过程中需要储存每一层的信息,所以其运行时需要储存的信息量较大,资源的消耗也较多。但是在不同问题中两者占优的情况是不同的,当图较为复杂二者其实差别不大。

图的搜索(遍历) - BFS & DFS

https://blog.ixk.me/post/graph-search-bfs-dfs
  • 许可协议

    BY-NC-SA

  • 本文作者

    Otstar Lin

  • 发布于

    2018/12/19

转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!

C 结构体的定义和使用Java链表实现