图论是NOIP的一个非常重要的考点,换句话说,没有图论,NOIP的考纲就得少一大半(虽然很NOIP没有考纲)

图论这玩意吧,和数论一样是非常变态的东西,知识点又多又杂,但是好在一个事,他比较直观比较好想

对于一张图而言,我们定义图是一种由边和点构成的的一个玩意(其实是严谨定义我记不住了QWQ,但是不影响学习)

一般来说,图的存储难度主要在记录边的信息
无向图的存储中,只需要将一条无向边拆成两条即可
邻接矩阵:用一个二维数组 edg[N][N] 表示
edg[i][j] 就对应由
i 到 j 的边信息
edg[i][j] 可以记录 Bool,也可以记录边权
缺点:如果有重边有时候不好处理
空间复杂度 O(V^2)

点度等额外信息也是很好维护的

#include <bits/stdc++.h>

using namespace std;

const int N = ;

int ideg[N], odeg[N], n, m, edg[N][N];
bool visited[N]; void travel(int u, int distance)
{
cout << u << " " << distance << endl; visited[u] = true;
for (int v = ; v <= n; v++)
if (edg[u][v] != - && !visited[v])//是否已经访问过
travel(v, distance + edg[u][v]); //if there is an edge (u, v) and v has not been visited, then travel(v)
}
int main()
{
cin >> n >> m;
memset(edg, -, sizeof edg);
memset(visited, false, sizeof visited);
for (int u, v, w, i = ; i <= m; i++)
cin >> u >> v >> w, edg[u][v] = w, odeg[u]++, ideg[v]++;//出度和入度
for (int i = ; i <= n; i++)
cout << ideg[i] << " " << odeg[i] << endl;
for (int i = ; i <= n; i++)
if (!visited[i]) travel(i, );
} /*
Given a graph with N nodes and M unidirectional edges.
Each edge e_i starts from u_i to v_i and weights w_i
Output a travelsal from node 1 and output degree of each node.
*/

其实这个英文注释也还蛮不错的啊

邻接矩阵本质上其实就是一个二维数组,它在存储一个稠密图的时候效率比较好,但是稀松图的话就非常浪费空间

所以我们就没有必要用二维数组记录信息,我们只需要对每一个点记录他的出边就行

这样记的话,复杂度就是他的边数

对每一个点 u 记录一个 List[u],包含所有从 u 出发的边
直接用数组实现 List[u]?读入边之前不知道 List[u] 长度
手写链表(链式前向星)
用 STL 中的 vector 实现变长数组,当然你想要手写指针也没问题
只需要 O(V + E) 的空间就能实现图的存储(边数加点数)

其实写这个链表存储0有很多方式啊,你可以用指针,手写指针,也可以用vector ,还可以用数组毛模拟

我们详细理解一下代码

#include <bits/stdc++.h>

using namespace std;

const int N = ;

struct edge {
int u, v, w; edge *next;//next指针指向
edge(int _u, int _v, int _w, edge *_next):
u(_u), v(_v), w(_w), next(_next) {}
};
edge *head[N]; //List[u] 最前面的节点是谁
int ideg[N], odeg[N], n, m;
bool visited[N]; void add(int u, int v, int w)
{
edge *e = new edge(u, v, w, head[u]);
head[u] = e;
}
void travel(int u, int distance)
{
cout << u << " " << distance << endl; visited[u] = true;
for (edge *e = head[u]; e ; e = e -> next)
if (!visited[e -> v])
travel(e -> v, distance + e -> w); //if there is an edge (u, v) and v has not been visited, then travel(v)
}
int main()
{
cin >> n >> m;
memset(visited, false, sizeof visited);
memset(head, , sizeof head);
for (int u, v, w, i = ; i <= m; i++)
cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++;
for (int i = ; i <= n; i++)
cout << ideg[i] << " " << odeg[i] << endl;
for (int i = ; i <= n; i++)
if (!visited[i]) travel(i, );
} /*
Given a graph with N nodes and M unidirectional edges.
Each edge e_i starts from u_i to v_i and weights w_i
Output a travelsal from node 1 and output degree of each node.
*/

但是我个人是不用指针的,因为可能还是不习惯的原因吧,而且指针的写法并没有什么特别的优点

还有一个数组模拟版本

#include <bits/stdc++.h>

using namespace std;

const int N = ;

struct edge {
int u, v, w, next;
}edg[N];
int head[N]; //List[u] stores all edges start from u
int ideg[N], odeg[N], n, m, cnt; //cnt: numbers of edges
bool visited[N]; void add(int u, int v, int w)
{
int e = ++cnt;
edg[e] = (edge){u, v, w, head[u]};
head[u] = e;
}
void travel(int u, int distance)
{
cout << u << " " << distance << endl; visited[u] = true;
for (int e = head[u]; e ; e = edg[e].next)
if (!visited[edg[e].v])
travel(edg[e].v, distance + edg[e].w); //if there is an edge (u, v) and v has not been visited, then travel(v)
}
int main()
{
cin >> n >> m; cnt = ;
memset(visited, false, sizeof visited);
memset(head, , sizeof head);
for (int u, v, w, i = ; i <= m; i++)
cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++;
for (int i = ; i <= n; i++)
cout << ideg[i] << " " << odeg[i] << endl;
for (int i = ; i <= n; i++)
if (!visited[i]) travel(i, );
} /*
Given a graph with N nodes and M unidirectional edges.
Each edge e_i starts from u_i to v_i and weights w_i
Output a travelsal from node 1 and output degree of each node.
*/

但是数组模拟必然是逃不开浪费时间过多的,这个事就很讨厌了,邻接矩阵以其优秀的可读性以及构造性换来了不少空间,唉

我个人现在是这样的,判断变数和点数的值,如果差别较大,那么出题人可能是想构造菊花树之类的,差别较小就意味着稠密,那么写邻接矩阵更节省时间(前提是你两个都能用)

还有一种写法是用vector

抛去邻接矩阵不讲,如果我们用edg[u][i]表示从u出发的第i条边,这样实际上还是O(n^2)的,所以我们要用一个能够自己改变长度的STL,这样能让空间最大化

#include <bits/stdc++.h>

using namespace std;

const int N = ;

struct edge {
int u, v, w;
};
vector<edge> edg[N]; //edge记录变长数组记录的是什么类型
int ideg[N], odeg[N], n, m, cnt; //cnt: numbers of edges
bool visited[N]; void add(int u, int v, int w)
{
edg[u].push_back((edge){u, v, w});//一个强制类型转换
}
void travel(int u, int distance)
{
cout << u << " " << distance << endl; visited[u] = true;
for (int e = ; e < edg[u].size(); e++)//遍历边
if (!visited[edg[u][e].v])//以u出发的第e条出边
travel(edg[u][e].v, distance + edg[u][e].w); //if there is an edge (u, v) and v has not been visited, then travel(v)
}
int main()
{
cin >> n >> m; cnt = ;
memset(visited, false, sizeof visited);
for (int u, v, w, i = ; i <= m; i++)
cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++;
for (int i = ; i <= n; i++)
cout << ideg[i] << " " << odeg[i] << endl;
for (int i = ; i <= n; i++)
if (!visited[i]) travel(i, );
} /*
Given a graph with N nodes and M unidirectional edges.
Each edge e_i starts from u_i to v_i and weights w_i
Output a travelsal from node 1 and output degree of each node.
*/

要注意的是,c++的STL数组默认都是以0为结尾的、

vector是这样构造的

<>里面写的是变量类型,可以是int 或者float或者结构体

生成树

我们考虑一个联通的无向图,我们考虑找出这个图当中的子图(点的数量是一样的,可以删掉边)

给定一个连通无向图 G = (V; E)
E′ ⊂ E
G′ =
(V; E′) 构成一棵树
G′ 就是 G 的一个生成树

而且我们可以发现生成树不是唯一的,而且我们可以知道的是生成树的数量是指数级别的

那么最小生成树其实就是生成树当中最大边权的值最小

怎么求呢?

Algorithms for Minimal Spanning Tree:
Kruskal
Prim
Kosaraju

Kruskal

克鲁斯卡尔的思想是贪心加上并查集

我们只把所有的边的信息存下来,而不用存图(因为最小生成树只问你最小边权和之类的问题,而不文)

,对于所有的边权进行排序,找到当前边权最小的边 e : (u; v)
如果 u 和 v 已经连通,则直接删除这条边(这里的判断就是用并查集的思想,如果最终并查集的指向指到了一个同一个点,那么就是联通的啊)
如果 u 和 v 已经未连通,将之加入生成树
重复上述过程

这个称为Rigorous proof(消圈算法)

Kruskal的证明方法很迷啊,就感性理解一下就好

毕竟贪心这东西证明正确性还是挺困难的。

Prim的做法是,我们找一个连通块,我们把和这个连通块最短的点加到连通块当中去(这俩都可以用堆优化)

Kosaraju的做法是,我们有很多连通块,然后第一轮的时候对于每一个连通块找到和它相连的最短的边,就把这两个集合连接起来

QBXT Day 5图论相关的更多相关文章

  1. [联赛可能考到]图论相关算法——COGS——联赛试题预测

    COGS图论相关算法 最小生成树 Kruskal+ufs int ufs(int x) { return f[x] == x ? x : f[x] = ufs(f[x]); } int Kruskal ...

  2. 【五一qbxt】day5 图论

    图论 学好图论的基础: 必须意识到图论hendanteng xuehuifangqi(雾 图 G = (V,E) 一般来说,图的存储难度主要在记录边的信息 无向图的存储中,只需要将一条无向边拆成两条即 ...

  3. 图论相关知识(DFS、BFS、拓扑排序、最小代价生成树、最短路径)

    图的存储 假设是n点m边的图: 邻接矩阵:很简单,但是遍历图的时间复杂度和空间复杂度都为n^2,不适合数据量大的情况 邻接表:略微复杂一丢丢,空间复杂度n+m,遍历图的时间复杂度为m,适用情况更广 前 ...

  4. COGS NIOP联赛 图论相关算法总结

    最小生成树 Kruskal+ufs int ufs(int x) { return f[x] == x ? x : f[x] = ufs(f[x]); } int Kruskal() { int w ...

  5. qbxt Day 5 图论一些基础知识

    就是一些感觉比较容易忘的知识 假设根为第0层, 在二叉树的i层上至多有2i个结点,整颗二叉树(深度为k)最多有\(2^{k+1}-1\)个节点 对于任何一棵非空二叉树,如果叶结点个数为\(n_0\), ...

  6. OI省选算法汇总

    copy from hzwer @http://hzwer.com/1234.html 侵删 1.1 基本数据结构 1. 数组 2. 链表,双向链表 3. 队列,单调队列,双端队列 4. 栈,单调栈 ...

  7. Noip2016

    <这篇是以前的,不开新的了,借版面来换了个标题> 高二了 开学一周,每天被文化课作业碾压... 但是仍然阻挡不了想刷题的心情... 对付noip2016的几块:(有点少,以后补) 高精度( ...

  8. codeforces 723E:One-Way Reform

    Description There are n cities and m two-way roads in Berland, each road connects two cities. It is ...

  9. DS实验题 Dijkstra算法

    参考:Dijkstra算法 数据结构来到了图论这一章节,网络中的路由算法基本都和图论相关.于是在拿到DS的实验题的时候,决定看下久负盛名的Dijkstra算法. Dijkstra的经典应用是开放最短路 ...

随机推荐

  1. Jquery复习(十)之$.fn.extend()

    定义和用法 $.fn.extend() 函数为jQuery扩展一个或多个实例属性和方法(主要用于扩展方法). 提示:jQuery.fn是jQuery的原型对象,其extend()方法用于为jQuery ...

  2. 运维ipvsadm配置负载均衡2

    一.什么是lvs1.lvs的定义LVS是Linux Virtual Server的简写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统.是由章文嵩博士开发的一款开源软件,1998年5月发布,是中 ...

  3. phpstudy mysql数据连接不上(#1130)解决办法

    问题:无论输什么密码,都显示#1130,找半天在终于在百度知道找到了,其他帖子都是水贴,暂时不知道为什么要这么加,反正加了重置服务就好了,重新打开phpMyAdmin 输入默认密码root既可 解决办 ...

  4. 2>&1的含义解释

    某次想将adb的help信息保存起来 F:\android系统\AndroidTool_Release_v2.\bin>adb >help.txt 结果 F:\android系统\Andr ...

  5. 认识 android-job

    简评: Android 实现后台任务的最佳实践. 对于现在的应用来说,在应用生命周期之外运行一些后台任务可以说已经是一项必不可少的需求了.这些任务可能是在某个时间点提醒用户什么事情或同步本地数据到服务 ...

  6. react-native启动时红屏报错:Unable to load script.Make sure you're either running a metro server or that ....

    一.报错信息内容 我是在Android Studio中运行启动react-native项目时报的这个错误 1.报错提示:Unable to load script.Make sure you're e ...

  7. redis中如何存储java对象

    根据redis的存储原理,Redis的key和value都支持二进制安全的字符串 1.利用序列化和反序列化的方式 存储java对象我们可以通过对象的序列化与反序列化完成存储于取出,这样就可以使用red ...

  8. springboot 配置quart多数据源

    Springboot版本为2.1.6 多数据源配置使用druid进行配置,数据库使用的为Oracle11g,如果使用的是MySQL,直接将数据库的地址和驱动改一下即可 <parent> & ...

  9. HDU-3416-MarriageMatch4(最大流,最短路)

    链接: https://vjudge.net/problem/HDU-3416 题意: Do not sincere non-interference. Like that show, now sta ...

  10. keep-alive前进没有刷新

    https://segmentfault.com/a/1190000012083511