在刷题的过程中经常会碰到一些关于图论的问题,下面我将根据自己刷题的经验来对这些问题做一个总结。

  • 图的表示方法

  解决图论中的问题首先要解决的问题就是图的表示方法这一问题,图的表示方法主要有两种,一种使用链接表的方法来表示,另一种是用链接矩阵的方法来表示。在解题的过程中我用的几乎都是用邻接矩阵的方法来表示。用邻接矩阵来表示的话既可以用二维数组也可以用二维向量来表示。用二维向量来表示的好处是,如果题目中没有给出边的权重,只是让我们求解图的连通性问题的话,我们可以不指定vector数组的大小,例如:vector[i].push_back(x),以此来表示i与x相连。这样来表示的话又有一些链接表的感觉。

  • 图论中涉及到的一些算法

  就PAT考察的范围,以及刷题的经验来看,这里面涉及到的算法主要包括DFS、BFS、并查集、Dijkstra(最短路径)、Kruskal(最小生成树)以及求连通分量的问题。

  今天复习数据结构刚好复习了 图论这一章,所以把图论中的一些算法总结一下,毕竟有些算法在PAT中还是经常考到的。

  两种搜索算法BFS + DFS:

  这两种算法应该是图论中最基本的两种算法了,其他的算法在实现的时候都或多或少的用到了这两种算法。BFS实现的时候可以用一个队列来维护,以当前的结点为中心将于之相连的结点依次放入队列中,如果存在回路的话可以使用一个visited[]来标记当前结点是否已经被访问过。

  BFS的代码实现:

const int MAX_VERTEX_NUM = 0x7fffffff;
bool visited[MAX_VERTEX_NUM];
void BFSTraverse(Graph G) {
for (int i = 0; i < G.vexnum; ++i) visited[i] = false;
InitQueue(Q);
for (int i = 0; i < G.vexnum; ++i)
if (!visited[i]) BFS(G, i);
} void BFS(Graph G, int v) {
visit(v);
visited[v] = true;
Enqueue(Q, v);
while (!isEmpty(Q)) {
DeQueue(Q, v);
for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w))
if (!visited[w]) {
visit(w);
visited[w] = true;
EnQueue(Q, w);
}
}
}

  DFS就是从一个结点开始一直往下走,直到走到尽头,然后跳出递归返回上一层继续寻找没有遍历过的结点,直至将所有的结点都遍历完毕为止。实现的时候可以用一个栈来实现。

  DFS的代码实现如下所示:

 1 bool visited[MAX_VERTEX_NUM];
2 void DFSTraverse(Graph G) {
3 for (int v = 0; v < G.vexnum; ++v)
4 visited[v] = false;
5 for (int v = 0; v < G.vexnum; ++v)
6 if (!visited[v])
7 DFS(G, v);
8 }
9 void DFS(Graph G, int v) {
10 visit(v);
11 visited[v] = true;
12 for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w))
13 if (!visited[w])
14 DFS(G, w);
15 }

  使用BFS可以解决无权图的单源最短路问题,具体代码如下所示:

 1 void BFS_MIN_Distance(Graph G, int u) {
2 for (int i = 0; i < G.vexnum; ++i)
3 d[i] = inf;
4 visited[u] = true;
5 d[u] = 0;
6 EnQueue(Q, u);
7 while (!isEmpty(Q)) {
8 DeQueue(Q, u);
9 for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, u, w))
10 if (!visited[w]) {
11 visited[w] = true;
12 d[w] = d[u] + 1;
13 EnQueue(Q, w);
14 }
15 }
16 }

  除了这些东西以外,还有两个重要的概念就是广度优先生成树和深度优先生成树。如果要用代码实现的话,我会这样来实现:通过在多开一个数组,来记录那条边是在生成树中存在的。通过这些边的信息应该就能将这棵树输出。

  区别:

  BFS:这是一种基于队列这种数据结构的搜索方式,它的特点是由每一个状态可以扩展出许多状态,然后再以此扩展,直到找到目标状态或者队列中头尾指针相遇,即队列中所有状态都已处理完毕。

  DFS:基于递归的搜索方式,它的特点是由一个状态拓展一个状态,然后不停拓展,直到找到目标或者无法继续拓展结束一个状态的递归。

  优缺点:

  BFS:对于解决最短或最少问题特别有效,而且寻找深度小,但缺点是内存耗费量大(需要开大量的数组单元用来存储状态)。

  DFS:对于解决遍历和求所有问题有效,对于问题搜索深度小的时候处理速度迅速,然而在深度很大的情况下效率不高 总结:不管是BFS还是DFS,它们虽然好用,但由于时间和空间的局限性,以至于它们只能解决数据量小的问题。

  最小生成树:

  最小生成树可以使用两种算法来实现,一种叫Prime算法,另一种叫Kruskal算法。

  1. Prim算法

  Prim算法的核心思想就是“找点”,根据已经遍历过的点,来找出与这些点相连的边中最短的那一条,从而将该边另一个端点上的结点收入已经遍历过的结点之中。这个算法在刷题的过程中用到的次数不多。

  伪代码实现如下所示:

1 void Prim(G, T) {
2 T != {}; // 初始化空树
3 U = {w}; // 将一个结点放入以遍历结点的集合
4 while ((V - U) != {}) {
5 设(u, v)是使u∈U与v∈(V-U),且权值最小的边;
6 T = T ∪ {(u, v)};
7 U = U ∪ {v};
8 }
9 }

  Prim算法的思想:归并顶点,与边数无关,适用于稠密图。

  2. Kruskal算法

  Kruskal算法的核心思想就是不断地去找图中边长最小的边,然后判断该边所连接的两个端点是否在同一棵生成子树中(这一部分可通过使用并查集来判断),如果不在同一棵生成子树中则将该边加入到连通分量中,如此重复直到将所有的结点都加入到最小生成树中。

  伪代码实现如下所示:

 1 void Kruskal(V, T) {
2 T = V; // 初始化树
3 numS = n; // 连通分量数
4 while (numS > 1) {
5 if (v和u属于T中不同的连通分量) {
6 T = T ∪ {(v, u)}; //将此边加入到生成树中
7 numS--;
8 }
9 }
10 }

  Kruskal算法:归并边,适用于稀疏图。

  最小生成树Prim与Kruskal算法的比较

  Prim算法是依赖于点的算法。它的基本原理是从当前点寻找一个离自己(集合)最近的点然后把这个点拉到自己家来(距离设为0),同时输出一条边,并且刷新到其他点的路径长度。俗称,刷表。

  根据Prim算法的特性可以得知,它很适合于点密集的图。通常在教材中,对Prim算法进行介绍的标程都采用了邻接矩阵的储存结构。这种储存方法空间复杂度N^2,时间复杂度N^2。对于稍微稀疏一点的图,其实我们更适合采用邻接表的储存方式,可以节省空间,并在一定条件下节省时间。

  Kruskal算法是依赖边的算法。基本原理是将边集数组排序,然后通过维护一个并查集来分清楚并进来的点和没并进来的点,依次按从小到大的顺序遍历边集数组,如果这条边对应的两个顶点不在一个集合内,就输出这条边,并合并这两个点。

  根据Kruskal算法的特性可得,在边越少的情况下,Kruskal算法相对Prim算法的优势就越大。同时,因为边集数组便于维护,所以Kruskal在数据维护方面也较为简单,不像邻接表那样复杂。从一定意义上来说,Kruskal算法的速度与点数无关,因此对于稀疏图可以采用Kruskal算法。

  最短路径:

  上面提到用BFS来求无权图的最短路径问题,但是当我们遇到有权图的时候这种方法将不再适用。对于有权图我们有两种方法来求最短路问题,一种是使用Dijkstra算法来求单源最短路,另一种,是使用Floyd算法来求任意两点之间的最短路问题。Dijkstra算法在PAT中出现的次数还是比较多的,所以我们有必要弄清楚该算法的实现原理。

  1. Dijkstra算法

  基本思想:维护一个已经访问过的结点的集合通过向这个集合中不断地添加结点,并且更新与已添加结点相邻的结点到起始点的距离,以便下次找到一个到起始点距离最小的结点。最后将所有的结点都加入到这个集合中,算法执行完毕。

  辅助数组:

  visited[]:标记已经计算完成的顶点。

  dist[]:记录从源点v0到其他各顶点当前的最短路径长度。

  path[]:记录从最短路径中顶点的前驱顶点,即path[i]为v到vi最短路径上vi的前驱顶点。

  基本步骤:

  • 初始化数组,令集合S初始为{Ø};
  • 从集合V-S中选出vj,满足dist[j] = Min{dist[i] + vj | vj∈V-S},vj就是当前求得的最短路径的终点,并令S ∪ {j};
  • 修改此时从v0出发到集合V-S上任一顶点vk最短路径的长度:
    if dist[j] + arcs[j][k] < dist[k]
    dist[k] = dist[j] + arcs[j][k];
    path[k] = j;
  • 重复2)、3)操作n-1次,直到S中包含全部顶点。

  代码实现:

 1 // Dijkstra
2 bool visited[1005];
3 int path[1005];
4 int dist[1005];
5 int grap[1005][1005];
6 void Dijkstra(int start) {
7 for (int i = 0; i < 1005; ++i) {  // 初始化
8 visited[i] = false;
9 path[i] = 0;
10 dist[i] = inf;
11 }
12 visited[start] = true;
13 dist[start] = 0;
14 for (int i = 0; i < 1005; ++i) {
15 int u = -1, minn = inf;
16 for (int j = 0; j < 1005; ++j) {
17 if (visited[j] == false && dist[i] < minn) {
18 u = i;
19 minn = dist[i];
20 }
21 }
22 if (u == -1) break; visited[u] = true;
23 for (int j = 0; j < 1005; ++j) {
24 if (visited[j] == false && grap[u][j] != inf) {
25 if (dist[j] > dist[u] + grap[u][j]) {
26 dist[j] = dist[u] + grap[u][j];
27 path[j] = u;
28 }
29 }
30 }
31 }
32 }

  时间复杂度O(|V|2

  Dijkstra算法不能够求解权重为负的图。

  2. Floyd算法

  Floyd算法能够求解任意两点之间的最短路问题,如果有N个结点那么就需要N个二维数组来存储绕行结点k时任意两顶点间的最短路径,虽然比较繁琐,但是代码实现起来还是比较简单的。

  代码实现:

 1 // Floyd
2 for (int k = 0; k < n; ++k) { // 考虑以Vk作为中转点
3 for (int i = 0; i < n; ++i) { // 遍历整个矩阵,i为行号,j为列号
4 for (int j = 0; j < n; ++j) {
5 if (A[i][j] > A[i][k] + A[k][j]) { // 以Vk为中转点的路径更短
6 A[i][j] = A[i][k] + A[k][j]; // 更新最短路径
7 path[i][j] = k; // 中转点
8 }
9 }
10 }
11 }

  Floyd算法可以解决带有负权重的图,其时间复杂度为O(|V|3

  并查集:

  并查集问题也是图论中常见的内容,因为刷题的过程中有涉及到,所以有必要对这类问题进行总结。并查集中有一个重要的辅助数组fa[i]用来表示i的祖先是谁,如果是在同一个连通分量中,那么该连通分量中的每一个结点的祖先结点一定是相同的。初始化的时候我们将每一个结点的祖先结点指向其自身。

  并查集中有两个重要的函数,这两个函数也是并查集的核心,一个是寻找某个结点的祖先结点(另外,也会在这个过程中实现路径压缩),另一个函数,是将两个结点进行合并。他们的实现代码分别如下:

  寻找某个结点的祖先结点:

 1 int findFather(int x) {
2 int a = x;
3 while (x != fa[x]) {
4 x = fa[x];
5 }
6 while (a != fa[a]) {
7 int z = a;
8 a = fa[a];
9 fa[z] = x;
10 }
11 return x;
12 }

  将两个结点进行合并:

1 void Union(int x, int y) {
2 int faX = findFather(x);
3 int faY = findFather(y);
4 if (faX != faY) fa[faY] = faX;
5 }

  拓扑排序:

  在刷题的过程中也遇到过这种类型的题目,题目要求我们输出拓扑排序的结果。如果明白输出的结果都是入度为0的结点的话,用代码实现起来也不是太难。

  代码实现:

 1 bool TopologicalSort(Grap G) {
2 InitStack(S);
3 for (int i = 0; i < G.vexnum; ++i) {
4 if (indegree[i] == 0)
5 Push(S, i);
6 }
7 int count = 0;
8 while (!IsEmpty(S)) {
9 Pop(S, i);
10 print[count++] = i;
11 for (p = G.vertices[i].firstarc; p; p = p->nextarc) {
12 v = p->adjvex;
13 if (!(--indegree[v]))
14 Push(S, v);
15 }
16 }
17 if (count < G.vexnum)
18 return false;
19 else
20 return true;
21 }

2020-07-06 21:27:25

PAT归纳总结——关于图的一些总结的更多相关文章

  1. PAT归纳总结——一些容易记混的概念

    在刷题的过程中,有时候会遇到一些数据结构中的一些概念,如果对这些概念理解不清楚,甚至理解有误的话,就很可能把题目做错.所以,专门找出在刷题过程中出现的一些概念,以免考试的时候用到想不起来. 拓扑排序 ...

  2. PAT归纳总结——关于模拟类问题的一些总结

    关于时间的模拟问题 在刷题的过程中碰到了一些关于时间先后顺序的模拟题目,刚开始做的时候确实挺麻烦的,今天把这类问题的解题思路来整理一下. 比较典型的有: 1017 Queueing at Bank 1 ...

  3. PAT归纳总结——关于二叉树的一些总结

    今天是6月26日到下个月的这个时候已经考过试了,为了让自己考一个更高的分数,所以我打算把PAT的相关题型做一个总结.目前想到的方法就是将相关的题型整理到一起然后,针对这种题型整理出一些方法. 二叉树的 ...

  4. PAT归纳总结——关于C++输入输出格式问题的一些总结

    自从使用了C++就不再想使用C语言来刷题了,C++便捷的输入输出方式,以及一些STL库函数的使用都要比使用C语言方便的多.但是使用的时候还有一些需要注意的地方,在这篇博客中写一下.(更好的教程可以参看 ...

  5. PAT甲级 图的遍历 相关题_C++题解

    图的遍历 PAT (Advanced Level) Practice 图的遍历 相关题 目录 <算法笔记>重点摘要 1021 Deepest Root (25) 1076 Forwards ...

  6. RDBMS vs. NoSQL 合作还是竞争

    欢迎转载,转载请注明出处,徽沪一郎. 由于近期手头的工作和数据库的选型相关,纠结于是否使用一款NoSQL数据库来替换已有的MySQL数据库.在这个过程中随着学习研究的深入,对于二者的异同有了一些初步的 ...

  7. Spring数据访问和事务

    1.模型 2.解耦 3.实现 3.1 核心接口 3.2 代码分析 3.2.1 事务管理 3.2.2 数据访问 4.使用 4.1 编程模式 4.2 配置模式 4.2.1 声明式配置方式 4.2.2 注解 ...

  8. 使用Minifly打造基于视觉感知的跟踪无人机

    前言:无人机和人工智能现在是非常热门的话题,将两者结合起来是一个比较好的创意,本文介绍一种可行的解决方案来实现基于视觉感知的跟踪无人机.从零开始搭建无人机系统工作量和难度(以及钱)都是非常大的,所以在 ...

  9. Graph Embedding:

    https://blog.csdn.net/hy_jz/article/details/78877483 基于meta-path的异质网络Embedding-metapath2vec metapath ...

随机推荐

  1. Java基础语法:运算符

    Java 运算符(operator)根据功能分类: 算术运算符:+,-,*,/,%,++,-- 赋值运算符:= 关系运算符:>,<,>=,<=,==,!=,instanceof ...

  2. 后端程序员之路 5、.conf、libconfig

    .conf在linux里随处可见,作用基本跟windows的.ini差不多 libconfighttp://www.hyperrealm.com/libconfig/libconfig_manual. ...

  3. C语言相关的基础字符串函数

    C语言中没有专门的字符串类型,所以就用字符数组和字符指针形式表示 1 char arr[]="abcdef"; //字符数组表示的字符串 2 char*arr="abce ...

  4. 剑指 Offer 31. 栈的压入、弹出序列 + 入栈顺序和出栈顺序的匹配问题

    剑指 Offer 31. 栈的压入.弹出序列 Offer_31 题目详情: 解析: 这里需要使用一个栈来模仿入栈操作. package com.walegarrett.offer; /** * @Au ...

  5. 日志文件迁移至OSS

    一台服务器在用阿里云ECS,因为穷,磁盘空间有限,服务器日志文件每天都在增长,需要定期清理释放磁盘空间,想到几种解决方案: 写任务定时备份到本地服务器 直接下载到本地用移动硬盘备份 备份到阿里云OSS ...

  6. shell脚本,mysql数据库的备份-2[mysqldump]

    # 数据库IPIP=127.0.0.1# 数据库端口PORT=3306# 数据库用户USER=root# 数据库密码PASSWORD=****# 要备份的数据库TARGET_DB=database_n ...

  7. Apache配置 10. 访问控制-禁止解析PHP

    (1)简述 对于使用PHP语言编写的网站,有一些目录是有需求上传文件的.如果网站代码有漏洞,让黑客上传了一个用PHP写的木马,由于网站可以执行PHP程序,最终会让黑客拿到服务器权限. 为了避免这种情况 ...

  8. Apache配置 2.用户认证

    1.用户认证用来对某些目录中的网页进行访问控制,当用户访问这些页面的时候需要输入用户名和密码进行认证. 2. 配置: # vim /usr/local/apache2.4/conf/extra/htt ...

  9. Jmeter性能常见问题集锦

    1. java.net.BindException: Address already in use: connect 开始以为是单机运行脚本运行不过来,所以另加了一台负载机同时运行脚本 分布式环境部署 ...

  10. SpringBoot自动配置探究

    @SpringBootApplication @SpringBootApplication表示SpringBoot应用,标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就 ...