蓝桥集训之BFS、DFS和链式前向星

这篇具有很好参考价值的文章主要介绍了蓝桥集训之BFS、DFS和链式前向星。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

配套视频

https://www.bilibili.com/video/BV1RD4y1F7Fq

一、建图基础

前言

图一般定义为二元集

顶点集边集构成。

或者更抽象的说,由一个集合(顶点),和集合上的关系(边)构成

图的基本概念名词

邻接矩阵

邻接表

度,出度,入度

  • 在有向图中,箭头是具有方向的,从一个顶点指向另一个顶点,这样一来,每个顶点被指向的箭头个数,就是它的入度。从这个顶点指出去的箭头个数,就是它的出度

有向边,无向边,重边。

环,自环。

闭包等

有向图和无向图

有向图就是边在表示的时候有一个单向性,无向图就是在边表示的时候有一个双向性,这一点在我们建图的时候也能提现到

邻接矩阵(稠密图)

我们用一个二维矩阵来表示这个图,二维矩阵的两个维度就分别对应着起点,和终点,我们习惯把第二维度的作为起点,第一维度的作为终点

那么对于有向图来说我们只用不断地维护顶点地关系即可,举个栗子

m p [ i ] [ j ] mp[i][j] mp[i][j]表示地是 i i i这个点指向j这个点的时候地边的权值

邻接表(稀疏图)

对于邻接表而言,我们建图的方式就很多了,我这里举两个常用的方式

使用容器vector

大家都知道,vector是一个变长数组的容器,它会根据你的需求来分配对应的空间,所以我们就可以根据这个来建图

我们先定义一个结构体,这个结构体要包含哪些信息呢:终点信息、边权值

那么我们就能写出来了:

struct Edge{
    int v,w;//v表示的是终点、w表示的是起点到重点的权值
};
vector<Edge>E[N];//这个N是根据你的顶点的大小来决定的

这样一来我们发现,我们也能维护这个图push_back(node)

使用原生数组

由于数组不能是变长的,有些时候又因为点不多,但是都挺大,造成了数组空间不够,我们因此就能想到链表的结构来维护这个图,于是你就得到了下面这个结构体

struct Edge{
    int v,w;
    struct Edge* next;
};
Edge E[N];

这样每一个点就是一条链表,这样我们也能很好的维护这个图

链式前向星

前向星

前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序,并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了.

我们用 l e n [ i ] len[i] len[i]来记录所有以 i i i为起点的边在数组中的存储长度.

我们用用 h e a d [ i ] head[i] head[i]记录以 i i i为边集在数组中的第一个存储位置.

举个栗子:

假设我们有这样一个图:

蓝桥集训之BFS、DFS和链式前向星

这个边的输入情况如下:

1 2

2 3

3 4

1 3

4 1

1 5

4 5

排完序后可以得到如下边顺序:

编号: 1 2 3 4 5 6 7
起点: 1 1 1 2 3 4 4
终点: 2 3 5 3 4 1 5

然后我们就能获得head数组和len数组的信息了

head len
head[1] = 1 len[1] = 3
head[2] = 4 len[2] = 1
head[3] = 5 len[3] = 1
head[4] = 6 len[4] = 2

这个建图的方法能帮我们优化后面要学的DFS和BFS,但是仅仅是这样就足够了吗?答案显然是不够,我们可以学到一种更优的建图方法:链式前向星

链式前向星

我们根据前面所学的启发可以想到建立一种新的边的结构体

struct Edge{
    int last;
    int to;
    int w;
};

其中edge[i].to表示第 i i i条边的终点,edge[i].last表示与第 i i i条边同起点的上一条边的存储位置,edge[i].w为边权值.

另外受到前向星的启发,我们还有一个head数组,它是用来表示以 i i i为起点的第一条边存储的位置,实际上你会发现这里的第一条边存储的位置其实在以 i i i为起点的所有边的最后输入的那个编号.

我们将head数组初始化为-1或者0,cnt初始化为0,cnt表示的是当前加的边数,然后对它不断地更新操作,很显然我们能得到这样地一个加边地操作

void add(int u,int v,int w)
{
    edge[cnt].w = w;//更改边权
    edge[cnt].to = v;//更改下一个点的位置
    edge[cnt].last = head[u];//记录上一个以u为终点的边的位置
    head[u] = cnt++;//更新一下head数组
}

还是用上面的图,我们就能得到如下的边权关系

edge[i].to edge[i].last head[i]
edge[0].to = 2 edge[0].last = -1 head[1] = 0;
edge[1].to = 3 edge[1].last= -1 head[2] = 1
edge[2].to = 4 edge[2],last = -1 head[3] = 2
edge[3].to = 3 edge[3].last = 0 head[1] = 3
edge[4].to = 1 edge[4].last = -1 head[4] = 4
edge[5].to = 5 edge[5].last= 3 head[1] = 5
edge[6].to = 5 edge[6].last = 4 head[4] = 6

head[i]就是保存的最后的那条边的编号、这个链式前向星在遍历图的时候是倒着遍历的,所以我们用其中一个成员last表示上一个节点的位置,这样对图也不会有什么影响

所以我们就能得到一个遍历的方式:

for(int i=head[u];i!=-1;i=edge[i].last)//中间那个循环判断也可以写成~i,不懂得同学可以去了解一下负数补码

二、DFS(深度优先搜索)

问题引出

我们想知道在一个迷宫里面是否有一个路线能让我们从起点走到终点,无论改路径是否是最优的

例题:http://acm.mangata.ltd/p/E427

思路

由于我们只想知道这个迷宫有没有解,所以我们期望能够一条路就走到终点,然后保存这个信息,但是这个过程中我们很可能就走到了一个死路,正常的思维会怎么想呢?退回来,一只退回到有没走过的岔路口,然后走向另一个方向,然后重复这个过程直到找到了终点,或者说所有的点都走过了,那么我们就退出,这就是深度优先的思想。

重点

重点理解一下这个递归的过程,递的过程其实就是往下一个地方走,归的过程就是走到死胡同了,我们要返回到岔路口。这里归的过程也就是回溯,回溯的状态是非常关键的,有的时候我们要利用回溯这个过程记录一些信息,比如路径权值和等,所以DFS不仅能运用到路径的搜索,在很多地方都能用到

实现的方式

实现的方式也就是通过递归实现,不断向下探索,然后遇到死胡同就归上来

给出一个模板:

void dfs(int x,int y){//x、y表示的是坐标点的位置
	if(vis[x][y]) return;//这个表示已经访问过了
	vis[x][y] = true;//如果没有访问过,那么我们现在访问过了
	ans++;
	for(int i = 0;i < 4; ++i) {//这里就是往上下左右四个方向遍历
		int nx = x + dx[i];
		int ny = y + dy[i];
		if(!vis[nx][ny] && nx > 0 && nx <= H && ny > 0 && ny <= W && mp[nx][ny] != '#') {//我们这里就是看下一个位置是否能递归访问
			dfs(nx,ny);
		}
	}
}

三、BFS(广度优先搜索)

问题引出

我们想知道在一个迷宫里面是否有一个路线能让我们从起点走到终点,并且路线是最优的,然后输出最优路径的长度

例题:http://acm.mangata.ltd/p/E427

思路

由于我们现在的这个问题转变为了最优路径求解,所以我们经量就不要使用DFS(因为递归的过程很耗时间),这个时候就需要BFS(广度优先搜索),什么意思呢,我们尽可能地找到靠近我们当前这个点的周围的点。然后将这个周围的点加入我们即将探寻的这个队列里面。这个过程大概就是一层一层的去访问这些可行的点,这也就是广度优先搜索。

1.我们先将起点放进队列,然后逐步去找起点周围的点,然后将这个周围的点也放进队列,然后将起点移出队首。

2.我们再取出当前队首的点,然后重复上面的过程,直到取出的点是终点。

重点

重点就是这个入队的过程的理解,你要知道广度优先搜索的工作方式是优先将靠近当前点的周围的点放进队列,然后逐步去访问操作,在后续的过程中我们可以根据这个思维去优化SPFA算法以及优化。

实现方式

实现方式也就是队列的应用,主要理解的这个思路是广度优先

int dx[4]={0,0,-1,1};
int dy[4]={1,-1,0,0};
int bfs(int sx,int sy){
    int cnt = 0;
    q.push(node{sx,sy,0});//压入队列
    while(!q.empty()){//队列不为空
        node p=q.top();//取出队列第一个元素
        q.pop();//弹出
        if(p.x == ex,p.y == ey){//找到终点然后直接返回路径的长度
            return p.k;
        }
        if(vis[p.x][p.y]) continue;//已去过就不去了
        vis[p.x][p.y] = true;//标记已去过
        for(int i=0;i < 4;++i){
           int nx = x + dx[i];
           int ny = y + dy[i];
           if(check(nx,ny)){
               que.push(node{nx,ny,p.k+1});
           }
        }
    }
    return -1;//没有路径的
}

四、总结两种方式

维护方式

DFS用递归的形式,用到了栈结构,先进后出。

BFS选取状态用队列的形式,先进先出。

复杂度

DFS的复杂度与BFS的复杂度大体一致,不同之处在于遍历的方式与对于问题的解决出发点不同,DFS适合目标明确,而BFS适合大范围的寻找。

思想

思想上来说这两种方法都是穷竭列举所有的情况。但是不同的是,DFS可以通过剪枝等操作优化,而BFS必须穷举出所有情况

我的堆优化加链式前向星优化的迪杰斯特拉的板子

这个算法也就是BFS加上了一个贪心的思想,然后加上了一个链式前向星的优化最后写出来的一个最短路算法。只要你理解了BFS这个算法也就不会有什么问题。文章来源地址https://www.toymoban.com/news/detail-406038.html

#include<cstdio>
#include<cstring>
#include<queue>//
using namespace std;
const int N=2e5+5;//数据范围
struct edge{//存储边
    int u,v,w,next;//u为起点,v为终点,w为权值,next为前继
};
edge e[N];
int head[N],dis[N],n,m,s,cnt;//head为链中最上面的,dis表示当前答案,n为点数,m为边数,s为起点,cnt记录当前边的数量
bool vis[N];//vis表示这个点有没有走过
struct node{
    int w,to;//w表示累加的权值,to表示到的地方
    bool operator <(const node &x)const{//重载“<”号
        return w>x.w;
    }
};
priority_queue<node>q;//优先队列(堆优化)
void add(int u,int v,int w){
    ++cnt;//增加边的数量
    e[cnt].u=u;//存起点
    e[cnt].v=v;//存终点
    e[cnt].w=w;//存权值
    e[cnt].next=head[u];//存前继
    head[u]=cnt;//更新链最上面的序号
}//链式前向星(加边)
void Dijkstra(){
    memset(dis,0x3f3f3f3f,sizeof(dis));//初始化,为dis数组附一个极大值,方便后面的计算
    dis[s]=0;//起点到自己距离为0
    q.push(node{0,s});//压入队列
    while(!q.empty()){//队列不为空
        node x=q.top();//取出队列第一个元素
        q.pop();//弹出
        int u=x.to;//求出起点
        if(vis[u]) continue;//已去过就不去了
        vis[u]=true;//标记已去过
        for(int i=head[u];i;i=e[i].next){
            int v=e[i].v;//枚举终点
            if(dis[v]>dis[u]+e[i].w){//若中转后更优,就转
                dis[v]=dis[u]+e[i].w;//更新
                q.push(node{dis[v],v});//压入队列
            }
        }
    }
}
int main(){
    int u,v,w = 1;
    s = 1;
    scanf("%d%d",&n,&m);//输入
    for(int i=1;i<=m;++i){
        scanf("%d%d",&u,&v);
        add(u,v,w);
        add(v,u,w);
    }
    Dijkstra();//DJ
    printf("%d\n",dis[n]-1);//输出1-n的最短路
    return 0;
}

到了这里,关于蓝桥集训之BFS、DFS和链式前向星的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 算法复习6.1链式前向星(代码块式理解)

    一号初始节点head[1]=3(cnt=3)指向四号节点 edge[3(cnt=3)],其中edge[3].to=4 即四号节点 ,同时令edge[3].next=(new)cnt 指向下一个 ... (循环) 有点像指针,如果功能不复杂的话,可以直接简写为vector快捷操作  

    2024年02月21日
    浏览(55)
  • 【图论】图的存储--链式前向星存图法以及深度优先遍历图

    无向图 - 就是一种特殊的有向图 - 只用考虑有向图的存储即可 邻接矩阵 邻接表 邻接表 存储结构: (为每一个点开了一个单链表,存储这个点可以到达哪个点) 1 : 3-4-null 2 : 1-4-null 3 : 4-null 4 : null 插入一条新的边 比如要插一条边: 2-3 先找到 2 对应的 单链表 然后将 3 插入到单链表

    2024年04月14日
    浏览(45)
  • 【图论C++】树的重心——教父POJ 3107(链式前向星的使用)

    UpData Log👆 2023.9.26 更新进行中 Statement0🥇 一起进步 Statement1💯 有些描述是个人理解,可能不够标准,但能达其意 树 是 图 的一种 特例 , 树 就是 “没有环” 的 连通图 判断一个 图 是否是一个 树 ,需要满足的条件: 1)树根 :一棵树可以基于 无向图 与 有向图 ,区别在

    2024年02月07日
    浏览(41)
  • 蓝桥杯练习题dfs与bfs

    本文主要是【算法】——dfs与bfs的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是听风与他🥇 ☁️博客首页:CSDN主页听风与他 🌄每日一句:狠狠沉淀,顶峰相见

    2024年01月21日
    浏览(43)
  • 蓝桥杯专题之递归+dfs+bfs篇

    2013年:第39级台阶 2014年:李白打酒,地宫取宝 2015年:牌型种数 2016年:方格填数,剪邮票 2018年:全球变暖 2019年:迷宫 2020年:走方格,七段码 2022年模拟赛:2021变1的最短操作数 2022年第一次模拟赛:15级台阶 2022年国赛:扩散 小明刚刚看完电影《第39级台阶》,离开电影院

    2023年04月08日
    浏览(43)
  • 图论:DFS与BFS

    目录 1.DFS(图论) 1.1.DFS过程 1.2.应用 2.BFS(图论) 2.1.BFS过程 2.2.应用 2.3.双端队列BFS 实现 2.4.优先队列BFS(堆优化 Dijkstra算法) DFS全称是,中文名是深度优先搜索,是一种用于遍历或搜索树或图的算法。所谓深度优先,就是说每次都尝试向更深的节点走。广义上的DFS:DFS最

    2024年03月24日
    浏览(32)
  • 拓扑排序算法 -- dfs、bfs

    210. 课程表 II 该题用到「拓扑排序」的算法思想,关于拓扑排序,直观地说就是,让你把⼀幅图「拉平」,⽽且这个「拉平」的图⾥⾯,所有箭头⽅向都是⼀致的,⽐如上图所有箭头都是朝右的。 很显然,如果⼀幅有向图中存在环,是⽆法进⾏拓扑排序的,因为肯定做不到所

    2024年02月10日
    浏览(41)
  • 【机器学习】P18 反向传播(导数、微积分、链式法则、前向传播、后向传播流程、神经网络)

    反向传播(back propagation)是一种用于训练神经网络的算法,其作用是计算神经网络中每个参数对损失函数的影响,从而进行参数更新,使得神经网络的预测结果更加准确。 具体来说,反向传播算法首先通过 前向传播 计算神经网络的预测结果,并与实际结果进行比较,得到

    2024年02月04日
    浏览(59)
  • 迷宫问题-DFS-BFS

    🚀学习过算法程序设计的应该都学习过迷宫这个问题,迷宫问题主要设计的算法就是DFS-深度优先遍历和BFS-广度优先遍历。 🚀在一个二维数组中,元素为1的位置表示这个位置是墙,0表示有通路,迷宫的入口和出口都是0(否则不会有路径能出去),并且路径不唯一。例如下

    2023年04月22日
    浏览(43)
  • 算法-图BFS/DFS-单词接龙

    https://leetcode-cn.com/problems/number-of-islands 给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则: 每次转换只能改变一个字母。 转换过程中的中间单词必须是字典中的单词。 说明: 如果不存在这样的转换序列,返回

    2024年02月10日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包