图论是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 = 5005;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 = 1; v <= n; v++)if (edg[u][v] != -1 && !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, -1, sizeof edg);memset(visited, false, sizeof visited);for (int u, v, w, i = 1; i <= m; i++)cin >> u >> v >> w, edg[u][v] = w, odeg[u]++, ideg[v]++;//出度和入度 for (int i = 1; i <= n; i++)cout << ideg[i] << " " << odeg[i] << endl;for (int i = 1; i <= n; i++)if (!visited[i]) travel(i, 0);
}/*
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 = 5005;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, 0, sizeof head);for (int u, v, w, i = 1; i <= m; i++)cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++;for (int i = 1; i <= n; i++)cout << ideg[i] << " " << odeg[i] << endl;for (int i = 1; i <= n; i++)if (!visited[i]) travel(i, 0);
}/*
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 = 5005;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 = 0;memset(visited, false, sizeof visited);memset(head, 0, sizeof head);for (int u, v, w, i = 1; i <= m; i++)cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++;for (int i = 1; i <= n; i++)cout << ideg[i] << " " << odeg[i] << endl;for (int i = 1; i <= n; i++)if (!visited[i]) travel(i, 0);
}/*
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 = 5005;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 = 0; 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 = 0;memset(visited, false, sizeof visited);for (int u, v, w, i = 1; i <= m; i++)cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++;for (int i = 1; i <= n; i++)cout << ideg[i] << " " << odeg[i] << endl;for (int i = 1; i <= n; i++)if (!visited[i]) travel(i, 0);
}/*
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的做法是,我们有很多连通块,然后第一轮的时候对于每一个连通块找到和它相连的最短的边,就把这两个集合连接起来

转载于:https://www.cnblogs.com/this-is-M/p/10806609.html

QBXT Day 5图论相关相关推荐

  1. 图论相关算法理解和总结

    晚上学习了一些图论相关算法: 单源最短路径算法: Bellman-Ford 算法: Bellman-Ford 算法是一种用于计算带权有向图中单源最短路径(SSSP:Single-Source Shor ...

  2. 【算法总结】图论相关

    [最近公共祖先] [模板代码] [倍增算法] 1 void dfs(int k) 2 { 3 for(int i=1;(1<<i)<=deep[k];i++) 4 x[k][i]=x ...

  3. 图论学习NO2.图论相关内容了解

    一.图论基础知识 图论相关概念 :https://wenku.baidu.com/view/a65d646858fafab069dc024c.html?sxts=1563948479936 二.Flo ...

  4. 图论相关题-pta-个人整理-含有解析

    图基础 #mermaid-svg-LMb171qOymmKEHRx {font-family:"trebuchet ms",verdana,arial,sans-serif;fon ...

  5. 图论相关的基本定义(自用)

    基本定义 在图论中,二分图(bipartite graph)是一个图,它的点可以被划分为两个不相交的独立集. split图(split graph)是一个图,它的点可以被划分为团和独立集. 弦图(ch ...

  6. 离散数学图论相关考点

    一.图的基本概念 定义1: 图分为有向图和无向图 定义2: 在图G=<V,E>中,与结点v(vV)关联的边数,称作是该节点的度数,记作deg(v).   注:约定每个环在其对应结点上度数增 ...

  7. 各种图论模型及其解答(转)

    原文转自Jelline blog http://blog.chinaunix.net/uid-9112803-id-411340.html 摘要: 本文用另一种思路重新组织<图论及其应用> ...

  8. 想了解概率图模型?你要先理解图论的基本定义与形式

    图论一直是数学里十分重要的学科,其以图为研究对象,通常用来描述某些事物之间的某种特定关系.而在机器学习的世界里,我们希望从数据中挖掘出隐含信息或模型.因此,如果我们将图中的结点作为随机变量,连接作为相 ...

  9. 握手引理_图论中的握手引理–握手定理

    握手引理 Hello Everyone, 大家好, Today we will see Handshaking lemma associated with graph theory. Before s ...

最新文章

  1. 别把个人信息“玩”丢了
  2. ffmpeg多线程转码
  3. Swift标准库源码阅读笔记 - Array和ContiguousArray
  4. Java-Redis 热部署问题
  5. python实现录音小程序 界面_小程序如何实现录音 播放功能
  6. android html 启动app,Android js交互 与 Html启动App
  7. FPGA双沿采样之Verilog HDL实现
  8. Debian 配置Bind9 DNS服务器
  9. QT谷歌拼音输入法的移植
  10. 使用Bitvise SSH Server的一些设定
  11. php自带常量_php中的常量是什么 - php完全自学手册 - php中文网手册
  12. vb.net下打印清单示例(连续纸和固定纸张打印)
  13. android屏幕刷新显示机制 前肩 后肩的解释
  14. .NET MVC同页面显示从不同数据库(mssql、mysql)的数据
  15. 小成开发日记----物联网项目LoveTv实现web网页传输数据到单片机-表白女朋友(技术栈涉及web前端,php后端,c/c++ socket,嵌入式前后端)
  16. redis解除(删除)主从关系
  17. Windows删除文件的打开方式
  18. Android必会的自动化测试
  19. 嵌入式C/C++面试题
  20. Linux中级——“驱动” 控制硬件必须学会的底层知识

热门文章

  1. 基于Windows 7旗舰版搭建WinCE6.0开发环境的过程
  2. ASP.NET 2.0 中实现模板中的数据绑定系列(2)
  3. 被问到有没有内核开发经验_一个人就是一个开发团队!成电硬核毕业生自制迷你电脑走红!...
  4. 笔记本电脑怎么清理灰尘_手机声音越用越小怎么办?一段黑科技音波就能清理扬声器灰尘...
  5. java ean13 条形码_【教程】Spire.Barcode 教程:如何在C#中创建EAN-13条码
  6. mysql怎么制作柱状图_从数据库中取出最近三十天的数据并生成柱状图
  7. 远程教育英语和计算机没过怎么办,网络教育英语统考能考几次 没考过怎么办?...
  8. c++ 分页展示_分合相宜 Excel透视报表生成分页和汇总报表
  9. 用imspost制作catia后处理_新产品开发需要做原型验证,怎么样成型制作才省钱?...
  10. linux文件分别打包命令,Linux文件打包命令