写在前面:由于关节点是这次主角,所以把邻接表存储结构放在了后面,不过你是来学习图的邻接表,也不用担心,后面一段是图的邻接表详细讲解。自己都是把存储结构和其应用放在一起的。没办法,内容有点多,尽请见谅以下!
关节点与重连通分量
一:基本概念
1:关节点
在某图中,若删除顶点v以及v相关的边后,图的一个连通分量分割为两个或两个以上的连通分量,则称顶点V为该图的一个关节点。
2:重连通分量
一个没有关节点的连通图称为重连通图。在重连通图中,任意一对顶点之间至少存在两条路径,则再删去某个顶点即相关各边后也不破坏图的连通性。
3:连通度
若在图的连通图上删去k个节点才能破坏图的连通性,则称K为此图的连通度。显然,K越大,系统越稳定。
4:应用
关节点是一个很重要的概念,直接关系到整个系统的稳定性,比如:运输网络、通信网络、航空网等等。
二:关节点算法
1:关节点的特征
(1):如果在生成树中,根节点有两个或以上的孩子节点,那么根节点为关节点。 这里不防自己称为第一类关节点。
(2):在生成树中某个非叶子节点v,其某颗子树的根和子树的其他结点均没有指向v的祖先的回边。这里不防自己称为第二类关节点。
这里解释一下(1),(2)两点的含义:
连通图G5的关节点是A、B、D、G
2:实现思路
总体基础:DFS遍历图
第一类关节点:比较简单。DFS回来发现并没有遍历完所有结点,那么根节点必是关节点。
第二类关节点:比较复杂,这里拆分几个步骤。
1:这里需要全局变量count记录遍历的访问顺序,即visited[v],visited[v]是DFS进入的时候就有了。
2:low[w]是孩子结点的min,是DFS退出的时候才有的。有点像后序遍历出来的。
3:visited[k],回边(包括双亲),就是回边祖先的visited[v],应为这是visited[k]、low[w]都是倒着出来的。
4:low[w] >= visited[v],v必是第二类关节点,为什么是这样呢?因为 孩子 >= 双亲,不就是孩子没有回边,就是没有回边的原因,孩子low[w] 才会大于双亲visited[v]。(这里注意下:这里是大于等于,不仅仅是大于)
分析流程:
相关代码:
/*************************************************************************************
深度优先寻找关节点
** low(v) = min{DFSvisted[v], low[w], DFSvisted[k]} 从本身,孩子,双亲,回边选最小的
** DFSvisted[v]: 深度优先各结点的访问次序
** low[w]: 孩子
** DFSvisted[w]: 双亲,回边
**************************************************************************************/
int DFSvisted[20]; //这里定义最多有20个结点
int count = 1; //全局变量,对访问计数
void FindArticul(ALGraph &G)
{
DFSvisted[0] = 1; //设定邻接表的0号是生成树的根
for (int i = 1; i < G.vexnum; i++)
{
DFSvisted[i] = 0; //其余顶点都没有访问
}
ArxNode *p0 = G.vertices[0].firstarc; //选取节点0(A),作为DFS人口
int v = p0->adjvex;
DFSArticul(G, v); //从v顶点出发深度优先查找关节点
if (count < G.vexnum) { //生成树上至少有两颗子树
printf("Joint Point(Vertex): %c\n", G.vertices[0].data); //输出v顶点是关节点
while (p0->nextarc) {
p0 = p0->nextarc; v = p0->adjvex;
if(DFSvisted[v] == 0)
DFSArticul(G, v); //从新的v顶点出发深度优先查找关节点
}
}
}
int low[20]; //DFSArticul()是递归调用,low[20]定义在外
void DFSArticul(ALGraph &G, int v0)
{
ArxNode *p;
int w;
int min = DFSvisted[v0] = ++count; //v0是第count访问的顶点
for (p = G.vertices[v0].firstarc; p; p = p->nextarc) //沿着一个单链表一个一个往下走,并检查
{
w = p->adjvex; //w是v0的孩子
if (DFSvisted[w] == 0) //W没有访问,是v0的孩子
{
DFSArticul(G, w); //返回前求得low[w]
if (low[w] < min)
min = low[w]; //孩子(w)与min比较
if(low[w] >= DFSvisted[v0])
printf("Joint Point: %c\n", G.vertices[v0].data); //输出v0顶点是关节点
}
else if (DFSvisted[w] < min) //w已访问(回边),那么w以是v0的祖先
{
min = DFSvisted[w];
}
}
low[v0] = min; //保持low数组最小
}
二:主函数与输出
主函数
#include "stdafx.h"
#include "Joint.h"
int main()
{
ALGraph G;
printf("Please enter vexnum and arcnum: ");
scanf("%d %d", &G.vexnum, &G.arcnum); //输入结点数,弧数
CreatALGraph(G); //创建无向图的邻接表
printf("\n无向图的邻接表输出:\n");
PrintALGraph(G); //输出无向图的邻接表
printf("\n关节点输出:\n");
FindArticul(G); //关节点输出
return 0;
}
2:输出
下面提供两组数据供大家测试用。(输出关节点竟多输出一次,自己猜测是邻接表的问题,因为处于同一层次的所有点,谁先访问依据输入的次序。所以之后也是沿着表走的)
图G5:
图G5输出:
图G6:
图G6输出:
图的邻接表
1:图的四种存储
十字链表———-有向图的十字链表存储;深度优先、广度优先遍历
https://blog.csdn.net/weixin_39956356/article/details/80371735
数组表示法——-图的数组表示法(邻接矩阵)与Prim算法
https://blog.csdn.net/weixin_39956356/article/details/80470091
邻接多重表
邻接表————下面就介绍邻接表及创建无(有)向图
在这里介绍图的邻接表
1:基本知识
(1):邻接表用到了两个结构体
一个是顶点数组,包括点的数据(data)和连接此起点的第一条边(firstarc)。
一个是边链表,包括连接此边的终点在数组中的位置(adjvex)和对应之前起点的下一条边(*nextarc)。
这里还是举个例子(无向图G2的,有向图更简单):
无向图G2的邻接表:
存储结构代码:
#define MAX_VERTEX_NUM 20
typedef char VertexType;
//一个顶点的单链表
typedef struct ArxNode {
int adjvex; //弧的位置
struct ArxNode *nextarc; //该弧的下一弧
}ArxNode;
//顶点数组
typedef struct VNode {
VertexType data; //顶点存放的数据(这里用char--A B C···)
ArxNode *firstarc; //指向第一条弧的指针
int locationOfData; //把data数组位置记下来,方便知道位置找data
}VNode, AdjList[MAX_VERTEX_NUM];
//ALGraph的整体信息
typedef struct {
AdjList vertices;
int vexnum, arcnum; //弧的顶点数和弧数
}ALGraph;
(2):邻接表即可以创建有向图,也可以创建无向图。
选取其中一部分就是有向图。无向图两个部分都要有。
(3):邻接表很好的解决了邻接矩阵占用空间较大的问题,当边和信息多的时候,邻接表优势更加明显。
2:创建无向图的邻接表
(1):输入各顶点的data,firstarc指向NULL
里面scanf的%c输入问题在下面文章会更加详细:
https://blog.csdn.net/weixin_39956356/article/details/80371735
(2):输入所有的弧(没有先后顺序)
(3):创建单链表
里面核心两句代码分析:
一定要知道这个链表是倒着建立的
3:输出
图G5:
图G5邻接表:
图G6:
图G6邻接表:
感谢与源代码
(1):感谢以下文章对我的帮助
https://blog.csdn.net/dwenxue/article/details/72831234
(2):源代码(VS2017)
链接: https://pan.baidu.com/s/1Y_L6axWEH1RFJpqWyjnQrQ 密码: np4t