目录
[4.1 邻接表添加边](#4.1 邻接表添加边)
一、图基础知识
图的数据结构:描述多对多的复杂关系
简单图 : 若不存在顶点到其自身的边,且同一条边不重复出现
无向图:边关系用(Vi,Vj)表示
有向图:边的关系用<Vi,Vj>
表示
有向无环图:一个有向图,从任一顶点出发无法经过若干条边回到该顶点,不存在有向环
混合图:边可能有向,可能无向
完全图:图中每两个顶点之间,都存在一条边
完全有向图:有n*(n-1)条边
完全无向图:有n*(n-1)/2条边
顶点的度:在无向图中,顶点所具有的边的数目
入度、出度:(有向图)
子图:设有两个图G = (V,E) 和 G1` = (V`,E`) ,若V`是V的子集,E' 是 E 的子集,则称G`是G的子图
回路或环:一条路径上开始点与结束点为同一个顶点的
欧拉环路:如果经过图中各边一次且恰好一次的环路,其长度恰好等与图中边的总数(欧拉环路相当于旅游,每条路都走一遍)
哈密尔顿环路:经过图中各顶点恰好一次的环路,其长度等于构成环路的边数(哈密尔顿相当于你去打工,到对应顶点打卡就行)
连通图(无向图):图中任意两点存在直接或间接路径可相互抵达,为连通图,否则为非连通图

连通图只有一个极大连通子图(它本身)
非连通图(无直接或间接路径可抵达)有多个极大连通子图(其极大连通子图被称为连通分量,每个分量都是连通图,极大是因为再加一个非图中的点,就会导致它不再连通)
强连通图 (有向图):图中任意i、j两个顶点,从顶点i 到顶点j 和从j 到i都存在路径,则该图是强连通图,其只有一个极大强连通子图(它本身,也被称为极大强连通分量)
非强连通图有多个极大强连通子图(即强连通分量)
二、图的存储
邻接矩阵:有向图、无向图都可,适合无向稠密图
(有向)计算度:遍历一行(到谁) 入度:遍历一列(谁到)
(无向)计算度:遍历一行或一列(是对称矩阵)
邻接表:有向图、无向图都行,适合有向稀疏图
例:
正邻接(代表v1节点能到达哪些元素):
逆邻接(代表哪些节点能到达v1节点):
三、邻接矩阵遍历



3.1邻接矩阵DFS
visited数组:已访问数组用于标记元素有无被访问到(已经打印出来的,已经处理的)

C
static int isEdge(int weight)
{
if (weight > 0 && weight < INF)
{
return 1; // 该边存在
}
return 0; // 该边不存在
}
void DFS_MGraph(MGraph* graph, int v)
{
visitMGraphNode(&graph->vex[v]);
// 深搜:第一个元素标记为1
MGraphVisited[v] = 1;
for (int i = 0; i < graph->nodeNum; ++i)
{
// 以v为起始,遍历出每个与v相邻的元素 && 未被访问的
if (isEdge(graph->edges[v][i]) && MGraphVisited[i] == 0)
{
// 以该点i,为起始点再继续深搜
DFS_MGraph(graph,i);
}
}
}
这里的**visitMGraphNode数组,**标识为1的元素已经被访问过,标识为0的元素尚未被访问过
3.2邻接矩阵BFS

C
void BFS_MGraph(MGraph* graph, int v)
{
// 队列
int que[MaxNodeNum];
int rear = 0,front = 0;int cur;
// rear先后移一位
rear = (rear + 1) % MaxNodeNum;
// v存在此时rear对应位置中
que[rear] = v;
// 如果已经进入队列,则将对应元素标记为1(表示已经进入队列)
MGraphVisited[v] = 1;
while (front != rear)
{
front = (front + 1) % MaxNodeNum;
// 第一回:取出第一个元素
cur = que[front];
// 输出该元素
visitMGraphNode(&graph->vex[cur]);
for (int i = 0; i < graph->nodeNum; ++i)
{
// 如果存在与cur元素相邻的元素,且未进入队列
if (isEdge(graph->edges[cur][i]) && !MGraphVisited[i])
{
// rear先后移一位
rear = (rear + 1) % MaxNodeNum;
// 将对应元素i存进队列此时rear的位置
que[rear] = i;
// 标记元素已经进入队列
MGraphVisited[i] = 1;
}
}
}
}
这里的**visitMGraphNode数组,**标识为1的元素已经进入队列,标识为0的元素尚未进入队列
四、邻接表遍历


4.1 邻接表添加边
C
static ArcEdge *createArcEdge(int v,int w)
{
// 申请的是一条边(编号no,权重weight,edge->next指向)
ArcEdge *edge = malloc(sizeof(ArcEdge));
if (edge == NULL)
{
return NULL;
}
edge->no = v;
edge->weight = w;
edge->next = NULL; // 因为是头插法,这句不是非得写,而是更稳妥的编码习惯
return edge; // 创建边成功
}
void addAGraph(AGraph* graph, int x, int y, int w)
{
// 0-n-1范围外排除
if (x < 0 || x >= graph->nodeNum || y < 0 || y >= graph->nodeNum)
{
return;
}
if (x == y)return; // 自己到自己,邻接表实现有向图不用管自环
// edge 为节点为y,出边权重w
ArcEdge *edge = createArcEdge(y,w);
edge->next = graph->nodes[x].firstEdge; //edge->next = graph->nodes[i].firstEdge = NULL
// 头插法
graph->nodes[x].firstEdge = edge;
graph->edgeNum++;
// 无向图
if (graph->directed == 0) // 表示没有方向,一条边代表两条边
{
// x->y->NULL 有向就变成: y->x->NULL
edge = createArcEdge(x,w);
edge->next = graph->nodes[y].firstEdge;
graph->nodes[y].firstEdge = edge;
graph->edgeNum++;
}
}

4.2邻接表DFS
C
void DFS_AGraph(AGraph* graph,int v)
{
ArcEdge *p; // 辅助指针
graph->visited[v] = 1;
visitAGraphNode(&graph->nodes[v]); // 访问完v
p = graph->nodes[v].firstEdge; // p = v的下一个节点
while (p)
{
if (graph->visited[p->no] == 0) // eg.原本遍历0,后来发现2,没被访问过开始DFS_AGraph(graph,2);
{
DFS_AGraph(graph, p->no);
} // 发现0 2 3都被访问过,且已经输出,先又有0号的访问p = 0.firstEdge
// p->no == 2节点,已经被访问,不进入if语句,于是p = p->next,于是p == 4;
p = p->next;
}
}

4.3邻接表BFS
C
void BFS_AGraph(const AGraph* graph,int v)
{
// 不知道多少个,所以动态的而不是数组
int *que = malloc(sizeof(int) * graph->nodeNum);
int front = 0,rear = 0;
ArcEdge *p;
int cur;
rear = (rear + 1) % graph->nodeNum;
que[rear] = v;
// 表示已经放入队列了,终有一天会被访问的
graph->visited[v] = 1;
while (rear != front)
{
front = (front + 1) % graph->nodeNum;
cur = que[front];
// 一个一个访问
visitAGraphNode(&graph->nodes[cur]);
p = graph->nodes[cur].firstEdge;
while (p)
{
// 遇见没访问的就存进队列
if (graph->visited[p->no] == 0)
{
rear = (rear + 1) % graph->nodeNum;
que[rear] = p->no;
// 表示已经放入队列了
graph->visited[p->no] = 1;
}
// 下一个
p = p->next;
}
}
free(que);
}

能做完这个学习小结是因为一句话"今天放自己一马,明天放自己一马,你是放马的吗"
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

