第三章 搜索与图论 【完结】
以下总结摘自y总
目录
- DFS和BFS
- 树与图的存储
- 树与图的遍历
- 拓扑排序
- 朴素dijkstra算法
- 堆优化版dijkstra
- Bellman-Ford算法
- spfa 算法(队列优化的Bellman-Ford算法)
- spfa判断图中是否存在负环
- floyd算法
- 朴素版prim算法
- Kruskal算法
- 染色法判别二分图
- 匈牙利算法 (二分图匹配)
DFS和BFS
842. 排列数字
843. n-皇后问题
844. 走迷宫
845. 八数码
树与图的存储
树是一种特殊的图,与图的存储方式相同。
对于无向图中的边ab,存储两条有向边a->b, b->a。
因此我们可以只考虑有向图的存储。
(1) 邻接矩阵:g[a][ b ] 存储边a->b
(2) 邻接表:
有向图的邻接表存储就是对于每个点 v 对应一个头节点, 记录在h[v]
idx是图里边的编号,和建图的顺序有关,对于某一个点v, 它的所有邻边的编号不一定是连续的。
e 数组是edge的缩写,记录了某一条有向边的终点
ne数组是next的缩写,记录了邻接表里的同一个点的下一条邻边的idx
ne[idx]=h[a]; h[a]=idx; 就是把新建的边插入队头。(先把新建的边的next指向现在队头的next,然后更新队头的next)
然后再idx++, 给下一次建边使用
// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;// 添加一条边a->b
void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}// 初始化
idx = 0;
memset(h, -1, sizeof h);
树与图的遍历
时间复杂度 O(n+m), n 表示点数,m 表示边数
(1) 深度优先遍历 —— 模板题 AcWing 846. 树的重心
int dfs(int u)
{st[u] = true; // st[u] 表示点u已经被遍历过for (int i = h[u]; i != -1; i = ne[i]){int j = e[i];if (!st[j]) dfs(j);}
}
(2) 宽度优先遍历 —— 模板题 AcWing 847. 图中点的层次
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);while (q.size())
{int t = q.front();q.pop();for (int i = h[t]; i != -1; i = ne[i]){int j = e[i];if (!st[j]){st[j] = true; // 表示点j已经被遍历过q.push(j);}}
}
拓扑排序
模板题 AcWing 848. 有向图的拓扑序列
时间复杂度 O(n+m), n 表示点数,m 表示边数
bool topsort()
{int hh = 0, tt = -1;// d[i] 存储点i的入度for (int i = 1; i <= n; i ++ )if (!d[i])q[ ++ tt] = i;while (hh <= tt){int t = q[hh ++ ];for (int i = h[t]; i != -1; i = ne[i]){int j = e[i];if (-- d[j] == 0)q[ ++ tt] = j;}}// 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。return tt == n - 1;
}
朴素dijkstra算法
模板题 AcWing 849. Dijkstra求最短路 I
时间复杂是 O(n2+m), n表示点数,m 表示边数
int g[N][N]; // 存储每条边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N]; // 存储每个点的最短路是否已经确定// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{memset(dist, 0x3f, sizeof dist);dist[1] = 0;for (int i = 0; i < n - 1; i ++ ){int t = -1; // 在还未确定最短路的点中,寻找距离最小的点for (int j = 1; j <= n; j ++ )if (!st[j] && (t == -1 || dist[t] > dist[j]))t = j;// 用t更新其他点的距离for (int j = 1; j <= n; j ++ )dist[j] = min(dist[j], dist[t] + g[t][j]);st[t] = true;}if (dist[n] == 0x3f3f3f3f) return -1;return dist[n];
}
堆优化版dijkstra
模板题 AcWing 850. Dijkstra求最短路 II
时间复杂度 O(mlogn), n 表示点数,m 表示边数
typedef pair<int, int> PII;int n; // 点的数量
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{memset(dist, 0x3f, sizeof dist);dist[1] = 0;priority_queue<PII, vector<PII>, greater<PII>> heap;heap.push({0, 1}); // first存储距离,second存储节点编号 //因为要找距离源点最近的点,而pair默认先按第一关键字排再按第二关键字排while (heap.size()){auto t = heap.top();heap.pop();int ver = t.second, distance = t.first;if (st[ver]) continue;st[ver] = true;for (int i = h[ver]; i != -1; i = ne[i]){int j = e[i];if (dist[j] > distance + w[i]) // w[i] 切记{dist[j] = distance + w[i];heap.push({dist[j], j});}}}if (dist[n] == 0x3f3f3f3f) return -1;return dist[n];
}
Bellman-Ford算法
模板题 AcWing 853. 有边数限制的最短路
时间复杂度 O(nm), n 表示点数,m 表示边数
上图摘自小呆呆大神 :https://www.acwing.com/solution/content/6320/
backup就相当于,我们bfs()四个方向枚举的时候,是用当前点枚举的,
不能走一个方向后,用新的点接着串联枚举。
const int N=510;
const int M=1e4+10;int n, m; // n表示点数,m表示边数
int dist[N],backup[N]; // dist[x]存储1到x的最短路距离struct Edge // 边,a表示出点,b表示入点,w表示边的权重
{int a, b, w;
}edges[M];// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{memset(dist, 0x3f, sizeof dist);dist[1] = 0;// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,//由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。for (int i = 0; i < n; i ++ ){memcpy(backup,dist,sizeof dist);for (int j = 0; j < m; j ++ ){int a = edges[j].a, b = edges[j].b, w = edges[j].w;dist[b]=min(dist[b],backup[a]+w);}}if (dist[n] > 0x3f3f3f3f / 2) return -1;return dist[n];
}
spfa 算法(队列优化的Bellman-Ford算法)
模板题 AcWing 851. spfa求最短路
时间复杂度 平均情况下 O(m),最坏情况下 O(nm), n 表示点数,m 表示边数
Bellman_ford算法会遍历所有的边,但是有很多的边遍历了其实没有什么意义,
我们只用遍历那些到源点距离变小的点所连接的边即可,
只有当一个点的前驱结点更新了,该节点才会得到更新;
因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。
上图摘自:
小呆呆:https://www.acwing.com/solution/content/6325/
orzorz: https://www.acwing.com/solution/content/9306/
int n; // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储每个点到1号点的最短距离
bool st[N]; // 存储每个点是否在队列中// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{memset(dist, 0x3f, sizeof dist);dist[1] = 0;queue<int> q;q.push(1);st[1] = true;//标记入队了while (q.size()){auto t = q.front();q.pop();st[t] = false;//标记出队了for (int i = h[t]; i != -1; i = ne[i]){int j = e[i];if (dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i];if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入{q.push(j);st[j] = true;}}}}if (dist[n] == 0x3f3f3f3f) return -1;return dist[n];
}
spfa判断图中是否存在负环
模板题 AcWing 852. spfa判断负环
时间复杂度是 O(nm), n 表示点数,m 表示边数
int n; // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N], cnt[N]; // dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数
bool st[N]; // 存储每个点是否在队列中// 如果存在负环,则返回true,否则返回false。
bool spfa()
{// 不需要初始化dist数组// 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。queue<int> q;for (int i = 1; i <= n; i ++ ){q.push(i);st[i] = true;}while (q.size()){auto t = q.front();q.pop();st[t] = false;for (int i = h[t]; i != -1; i = ne[i]){int j = e[i];if (dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i];cnt[j] = cnt[t] + 1;if (cnt[j] >= n) return true; // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环if (!st[j]){q.push(j);st[j] = true;}}}}return false;
}
floyd算法
模板题 AcWing 854. Floyd求最短路
时间复杂度是 O(n3), n 表示点数
摘自:小呆呆 https://www.acwing.com/solution/content/6337/
初始化:for (int i = 1; i <= n; i ++ )for (int j = 1; j <= n; j ++ )if (i == j) d[i][j] = 0;else d[i][j] = INF;// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{for (int k = 1; k <= n; k ++ )for (int i = 1; i <= n; i ++ )for (int j = 1; j <= n; j ++ )d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
朴素版prim算法
模板题 AcWing 858. Prim算法求最小生成树
时间复杂度是 O(n2+m), n 表示点数,m 表示边数
int n; // n表示点数
int g[N][N]; // 邻接矩阵,存储所有边
int dist[N]; // 存储其他点到当前最小生成树的距离
bool st[N]; // 存储每个点是否已经在生成树中// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
int prim()
{memset(dist, 0x3f, sizeof dist);int res = 0;for (int i = 0; i < n; i ++ ){int t = -1;for (int j = 1; j <= n; j ++ )if (!st[j] && (t == -1 || dist[t] > dist[j]))t = j;if (i && dist[t] == INF) return INF; //说明不连通if (i) res += dist[t]; //第一次迭代也就是第一个点的距离不用加st[t] = true;for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);}return res;
}
Kruskal算法
模板题 AcWing 859. Kruskal算法求最小生成树
时间复杂度是 O(mlogm), n 表示点数,m 表示边数
int n, m; // n是点数,m是边数
int p[N]; // 并查集的父节点数组struct Edge // 存储边
{int a, b, w;bool operator< (const Edge &W)const{return w < W.w;}
}edges[M];int find(int x) // 并查集核心操作
{if (p[x] != x) p[x] = find(p[x]);return p[x];
}int kruskal()
{sort(edges, edges + m);for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集int res = 0, cnt = 0;for (int i = 0; i < m; i ++ ){int a = edges[i].a, b = edges[i].b, w = edges[i].w;a = find(a), b = find(b);if (a != b) // 如果两个连通块不连通,则将这两个连通块合并{p[a] = b;res += w;cnt ++ ;}}if (cnt < n - 1) return INF;return res;
}
染色法判别二分图
模板题 AcWing 860. 染色法判定二分图
时间复杂度是 O(n+m), n 表示点数,m 表示边数
int n; // n表示点数
int h[N], e[M], ne[M], idx; // 邻接表存储图
int color[N]; // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{color[u] = c;for (int i = h[u]; i != -1; i = ne[i]){int j = e[i];if (color[j] == -1){if (!dfs(j, !c)) return false; //染色冲突了}else if (color[j] == c) return false;//染色冲突了}return true;
}bool check()
{memset(color, -1, sizeof color);bool flag = true;for (int i = 1; i <= n; i ++ )if (color[i] == -1)if (!dfs(i, 0)){flag = false;break;}return flag;
}
匈牙利算法 (二分图匹配)
模板题 AcWing 861. 二分图的最大匹配
时间复杂度是 O(nm), n 表示点数,m 表示边数
int n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N]; // 表示第二个集合中的每个点是否已经被遍历过bool find(int x)
{for (int i = h[x]; i != -1; i = ne[i]){int j = e[i];if (!st[j]){st[j] = true;if (match[j] == 0 || find(match[j])){match[j] = x;return true;}}}return false;
}// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ )
{memset(st, false, sizeof st);if (find(i)) res ++ ;
}
第三章 搜索与图论 【完结】相关推荐
- 人工智能:第三章 搜索推理技术
第三章 搜索推理技术 教学内容:本章在上一章知识表示的基础上研究问题求解的方法,是人工智能研究的又一核心问题.内容包括早期搜索推理技术,如图搜索策略和消解原理:以及高级搜索推理技术,如规则演绎系统.产 ...
- Acwing《算法基础课》第3章 搜索与图论
Acwing<算法基础课>第3章 搜索与图论 文章目录 Acwing<算法基础课>第3章 搜索与图论 深度优先遍历DFS 宽度优先搜索BFS 拓扑排序 dijkstra算法 朴 ...
- 第二章 搜索 【未完结】
目录 Flood Fill 1097. 池塘计数[难度: 简单 / 知识点: 基本板子] 1098. 城堡问题 [难度: 一般 / 知识点: 二进制取位] 1106. 山峰和山谷[难度: 中 / 知识 ...
- 算法基础知识总结(搜索与图论)
三.搜索与图论 1.树与图的深度优先遍历 1.基本思想:利用深度优先搜素 2.树与图的存储与时间复杂度: (1)邻接矩阵:O(∣V∣2)O(|V|^2)O(∣V∣2) (2)邻接表:O(∣V∣+∣E∣ ...
- 电子科技大学《图论及其应用》复习总结--第三章 图的连通性
第三章 图的连通性 一.割边.割点和块 (一).割边及其性质 定义1 边e为图G的一条割边,如果 w(G−e)>w(G)w(G-e)>w(G)w(G−e)>w(G) 定理1 边 e ...
- 算法竞赛入门经典(第二版)第三章习题
声明:作者水平有限,只是会基础C语言的小菜,C++还未入门.作者仅根据算法竞赛入门经典(第二版)书上第三章习题所述题意而编写,并未严格按照原题的输入输出编写,代码仅经过个人测试(OJ网站太慢了).代码 ...
- 第三章 垃圾收集器与内存分配策略
第三章 垃圾收集器与内存分配策略 前言: 3.1 概述 3.2 对象已死? 3.2.1 引用计数算法 3.2.2 可达性分析算法 3.2.3 再谈引用,四种引用类型 3.2.4 生存还是死亡 3.3 ...
- 计算机应用基础第三章操作步骤,最新江西三校生计算机应用基础模拟操作题集锦(超实用!)...
第二章 Windows XP 操作系统 ? [2011高考] 将计算机系统时间修改为2011年9月1日10:30.请写出操作步骤. 答:双击系统托盘中时间图标(或双击"控制面板"窗 ...
- DOS批处理高级教程:第三章 FOR命令中的变量(转)
DOS批处理高级教程:第一章 批处理基础 DOS批处理高级教程:第二章 DOS循环for命令详解 DOS批处理高级教程:第三章 for命令中的变量 DOS批处理高级教程:第四章 批处理中的变量 DOS ...
最新文章
- 42所双一流大学校长情况简介(2019年)
- ElasticSearch大批量数据入库
- 这些最常用的Linux命令,每一条都应该学会!
- java.lang.Void 解析与使用
- boost::hana::values用法的测试程序
- file.seek()/tell()-笔记
- c++中类型用new和不用new的区别
- 递归求二叉树的深度_优雅地用堆栈替代递归实现二叉树的深度优先搜索
- 信息学奥赛C++语言:可口可乐
- 领域驱动 开源项目_我如何在开源领域找到工作
- 升级到 Android Studio 3.0 + Gradle 4.1 遇到的一些坑及解决方案
- JavaWeb学习之EL表达式和JSTL标签库
- ghost版32位win10系统,win10系统下载地址
- 技嘉主板更新版BIOS
- symbian 如何在安装时备份sis文件
- Arduino最便宜的模拟量采集
- 这5种计算机视觉技术,刷新你的世界观
- 余杭医保卡指定使用医院和药店
- 谷歌浏览器无法翻译此网页,解决方法?(谷歌浏览器无法翻译成中文,谷歌翻译,最新方法)
- 2 Resnet系列网络模型改进