邻接多重表
文章目录
- 邻接多重表
-
- [1. 概述](#1. 概述)
- [2. 十字链表的表示](#2. 十字链表的表示)
- [3. 代码实现](#3. 代码实现)
-
- [3.1 头文件部分](#3.1 头文件部分)
- [3.2 函数实现](#3.2 函数实现)
-
- [3.2.1 产生n个节点的邻接多重表](#3.2.1 产生n个节点的邻接多重表)
- [3.2.2 释放邻接多重表](#3.2.2 释放邻接多重表)
- [3.2.3 初始化邻接多重表的顶点值](#3.2.3 初始化邻接多重表的顶点值)
- [3.2.4 插入边](#3.2.4 插入边)
- [3.2.5 显示无向图](#3.2.5 显示无向图)
- [3.2.6 删除边](#3.2.6 删除边)
- [3.3 main 函数](#3.3 main 函数)
- [3.4 输出结果](#3.4 输出结果)
1. 概述
- 如果使⽤邻接表存储结构,但是对边的操作⽐较频繁,怎么办?
如果我们在⽆向图的应⽤中,关注的重点是顶点的话,那么邻接表是不错的选择,但如果我们更关注的是边的操作,⽐如对已经访问过的边做标记,或者删除某⼀条边等操作,邻接表的确显得不那么⽅便了(下图中删除红⾊边)

- 邻接表对边的操作显然很不⽅便,因此,我们可以仿照⼗字链表的⽅式,对边表结构进⾏改装
2. 十字链表的表示
- 边

- 顶点结构

- 其中 iVex 和 jVex 是与某条边依附的两个顶点在顶点表中的下标
- iLink指向依附顶点iVex的下⼀条边,jLink指向依附顶点jVex的下⼀条边


- 这样删除⼀个边就很容易了
3. 代码实现
3.1 头文件部分
c
/* 邻接多重表,适用于无向图
* 无向图如果使用邻接表存储,一条边会被处理2次,删除较为复杂 */
// 邻接多重表的边结构
typedef struct amlEdge
{
int iVex; // 边的顶点i编号
struct amlEdge *iNext; // 顶点i编号的下一条边
int jVex; // 边的顶点j编号
struct amlEdge *jNext; // 点j编号的下一条边
int weight; // 权值
int mark; // 是否已经显示图的边
}MultiListEdge;
// 邻接多重表的顶点结构
typedef struct
{
int no; // 顶点编号
char *show; // 顶点显示值
MultiListEdge *firstEdge; // 该顶点的边头节点
}MultiListVertex;
// 邻接多重表
typedef struct
{
MultiListVertex *nodes; // 顶点空间
int vertexNum; // 约束顶点空间的数量
int edgeNum; // 图中边的个数
}AdjacencyMultiList;
//1.产生n个节点的邻接多重表
AdjacencyMultiList *createMultiList(int n);
//2.释放邻接多重表
void releaseMultiList(AdjacencyMultiList *graph);
//3.初始化邻接多重表的顶点值
void initMultiList(AdjacencyMultiList *graph, int n, char *names[]);
//4.插入边
int insertMultiListEdge(AdjacencyMultiList *graph, int a, int b, int w);
//5.显示无向图
void showMultiList(AdjacencyMultiList *graph);
//6.删除边
int deleteMultiListEdge(AdjacencyMultiList *graph, int a, int b);
3.2 函数实现
3.2.1 产生n个节点的邻接多重表
c
/* 产生邻接多重表的图 */
AdjacencyMultiList *createMultiList(int n)
{
//申请表
AdjacencyMultiList *multiList = malloc(sizeof(AdjacencyMultiList));
if (multiList == NULL)
{
fprintf(stderr, "malloc failed!\n");
return NULL;
}
//申请顶点
multiList->nodes = (MultiListVertex *) malloc(sizeof(MultiListVertex) * n);
if (multiList->nodes == NULL)
{
fprintf(stderr, "nodes failed!\n");
free(multiList);
return NULL;
}
multiList->vertexNum = n;
multiList->edgeNum = 0;
//返回该表
return multiList;
}
3.2.2 释放邻接多重表
c
/*这个逻辑和3.2.6的删边逻辑是差不多的 主要去看3.2.6那里*/
/* 删除边节点,必须找到这个边的前一个节点,
*/
void releaseMultiList(AdjacencyMultiList *graph)
{
if (!graph) return;
int count = 0;
// 遍历所有顶点
for (int i = 0; i < graph->vertexNum; ++i)
{
// 反复取当前顶点的链表头(因为每次都会摘除并释放,直到链表空)
while (graph->nodes[i].firstEdge)
{
MultiListEdge *edge = graph->nodes[i].firstEdge;
// ----- 从当前顶点 i 的链表中摘除(头删) -----
if (edge->iVex == i)
{
graph->nodes[i].firstEdge = edge->iNext;
} else { // edge->jVex == i
graph->nodes[i].firstEdge = edge->jNext;
}
// ----- 从另一个顶点 other 的链表中摘除 -----
int other = (edge->iVex == i) ? edge->jVex : edge->iVex;
MultiListEdge *prev = NULL; //用来时刻备份 cur 的前一步的位置
MultiListEdge *cur = graph->nodes[other].firstEdge;
// 在 other 链表中查找 edge 及其前驱
while (cur && cur != edge)
{
prev = cur;
if (cur->iVex == other)
{
cur = cur->iNext;
} else { // cur->jVex == other
cur = cur->jNext;
}
}
// 如果找到了,则摘除
if (cur == edge) {
if (prev == NULL)
{
// edge 是 other 链表的头节点
if (edge->iVex == other) {
graph->nodes[other].firstEdge = edge->iNext;
} else {
graph->nodes[other].firstEdge = edge->jNext;
}
} else //edge 不是 other链表的头节点
{
if (prev->iVex == other && edge->iVex == other) //前驱边i段属于other 待删边i端属于other
{
prev->iNext = edge->iNext;
} else if (prev->iVex == other && edge->jVex == other) { //前驱边i段属于other 待删边j端属于other
prev->iNext = edge->jNext;
} else if (prev->jVex == other && edge->iVex == other) { //前驱边j段属于other 待删边i端属于other
prev->jNext = edge->iNext;
} else { // prev->jVex == other && edge->jVex == other //前驱边j段属于other 待删边j端属于other
prev->jNext = edge->jNext;
}
}
}
// 释放边
free(edge);
count++;
}
}
// 释放顶点数组和图结构
free(graph->nodes);
free(graph);
printf("release edge: %d\n", count);
}
3.2.3 初始化邻接多重表的顶点值
c
/* 初始化邻接多重表的顶点信息 */
void initMultiList(AdjacencyMultiList *graph, int n, char *names[])
{
for (int i = 0; i < n; ++i)
{
graph->nodes[i].no = i;
graph->nodes[i].show = names[i];
graph->nodes[i].firstEdge = NULL;
}
}
3.2.4 插入边
c
/* 向邻接多重表中,插入a节点编号和b节点编号的边*/
int insertMultiListEdge(AdjacencyMultiList *graph, int a, int b, int w)
{
if (a < 0 || b < 0)
return -1;
// 产生这条边
MultiListEdge *edge = malloc(sizeof(MultiListEdge));
if (edge == NULL)
{
fprintf(stderr, "insert malloc failed!\n");
return -1;
}
edge->weight = w;
// 处理a节点的连接关系,使用头插法
edge->iVex = a;
edge->iNext = graph->nodes[a].firstEdge;
graph->nodes[a].firstEdge = edge;
// 处理b节点的连接关系,使用头插法
edge->jVex = b;
edge->jNext = graph->nodes[b].firstEdge;
graph->nodes[b].firstEdge = edge;
graph->edgeNum++;
return 0;
}
3.2.5 显示无向图
c
/* 显示邻接多重表的边关系*/
static void initMark(AdjacencyMultiList *graph)
{
for (int i = 0; i < graph->vertexNum; ++i)
{
MultiListEdge *edge = graph->nodes[i].firstEdge;
while (edge)
{
edge->mark = 0;
edge = edge->iNext;
}
}
}
void showMultiList(AdjacencyMultiList *graph)
{
//初始化边为未访问
initMark(graph);
for (int i = 0; i < graph->vertexNum; ++i)
{
MultiListEdge *edge = graph->nodes[i].firstEdge;
while (edge && (edge->mark == 0)) // 边存在,且还未被访问
{
printf("<%s ---- %s>\n", graph->nodes[edge->iVex].show,
graph->nodes[edge->jVex].show);
edge->mark = 1;
edge = edge->iNext;
}
}
}
3.2.6 删除边
c
/* 删除节点编号a到节点编号b的边
* 删除边节点,必须找到这个边的前一个节点,
* */
int deleteMultiListEdge(AdjacencyMultiList *graph, int a, int b)
{
// 找到a编号的前一个边节点
MultiListEdge *aPreEdge = NULL; //用来备份aCurEdge
MultiListEdge *aCurEdge = graph->nodes[a].firstEdge;
while (aCurEdge &&
!((aCurEdge->iVex == a && aCurEdge->jVex == b) || //只要不是目标边,就继续循环往下走
(aCurEdge->jVex == a && aCurEdge->iVex == b))) //a--b b--a
{
aPreEdge = aCurEdge; //aPreEdge 时刻备份 ACurEdge 的前一步的位置
if (aCurEdge->iVex == a) //i 是a 后继走 iNext
{
aCurEdge = aCurEdge->iNext;
} else //j 是a 后继走 jNext
{
aCurEdge = aCurEdge->jNext;
}
}
if (aCurEdge == NULL) //没找到
return -1;
//循环结束后 APreEdge 指向的就是A编号的前一个边节点
// 找到b编号的前一个边结构
MultiListEdge *bPreEdge = NULL; //用来备份aCurEdge
MultiListEdge *bCurEdge = graph->nodes[b].firstEdge;
while (bCurEdge &&
!((bCurEdge->iVex == a && bCurEdge->jVex == b) || //只要不是目标边,就继续循环往下走
(bCurEdge->iVex == b && bCurEdge->jVex == a))) //b--a a--b
{
bPreEdge = bCurEdge; //bPreEdge 时刻备份 BCurEdge 的前一步的位置
if (bCurEdge->iVex == b)
{
bCurEdge = bCurEdge->iNext;
} else
{
bCurEdge = bCurEdge->jNext;
}
}
if (bCurEdge == NULL) //没找到
return -1;
//循环结束后 BPreEdge 指向的就是B编号的前一个边节点
//把目标边从 a 顶点的链表摘除
if (aPreEdge == NULL) // 说明头节点指向的边就是要删除的边,处理a编号的边
{
if (aCurEdge->iVex == a)
{ // 当前边从i出发是a
graph->nodes[a].firstEdge = aCurEdge->iNext;
} else { // 当前边从j出发是a
graph->nodes[a].firstEdge = aCurEdge->jNext;
}
} else //要删除的边在链表中间
{
if (aPreEdge->iVex == a && aCurEdge->iVex == a)
{ // 前驱边i端属于a,待删边i端属于a
aPreEdge->iNext = aCurEdge->iNext;
} else if (aPreEdge->iVex == a && aCurEdge->jVex == a){ // 前驱边i端属于a,待删边j端属于a
aPreEdge->iNext = aCurEdge->jNext;
} else if (aPreEdge->jVex == a && aCurEdge->iVex == a) { //前驱边j端属于a,待删边i端属于a
aPreEdge->jNext = aCurEdge->iNext;
} else { //aPreEdge->jVex == a && aCurEdge->jVex == a // 前驱边j端属于a,待删边j端属于a
aPreEdge->jNext = aCurEdge->jNext;
}
}
//把目标边从 b 顶点的链表摘除
if (bPreEdge == NULL)
{
if (bCurEdge->iVex == b) {
graph->nodes[b].firstEdge = bCurEdge->iNext;
} else {
graph->nodes[b].firstEdge = bCurEdge->jNext;
}
} else
{
if (bPreEdge->iVex == b && bCurEdge->iVex == b) {
bPreEdge->iNext = bCurEdge->iNext;
} else if (bPreEdge->iVex == b && bCurEdge->jVex == b){
bPreEdge->iNext = bCurEdge->jNext;
} else if (bPreEdge->jVex == b && bCurEdge->iVex == b) {
bPreEdge->jNext = bCurEdge->iNext;
} else {
bPreEdge->jNext = bCurEdge->jNext;
}
}
//释放边
free(aCurEdge);
graph->edgeNum--;
return 0;
}
3.3 main 函数
c
int main()
{
int n = 5;
char *nodeNames[] = {"V1", "V2", "V3", "V4", "V5"};
AdjacencyMultiList *graph = createMultiList(n);
if (graph == NULL)
{
return -1;
}
//初始化 和 加边
initMultiList(graph, n, nodeNames);
insertMultiListEdge(graph, 0, 1, 1);
insertMultiListEdge(graph, 0, 3, 1);
insertMultiListEdge(graph, 1, 2, 1);
insertMultiListEdge(graph, 1, 4, 1);
insertMultiListEdge(graph, 2, 3, 1);
insertMultiListEdge(graph, 2, 4, 1);
//删边
deleteMultiListEdge(graph, 1, 4);
printf("insert %d edges!\n", graph->edgeNum);
//显示
showMultiList(graph);
//释放
releaseMultiList(graph);
return 0;
}
3.4 输出结果
c
insert 5 edges!
<V1 ---- V4>
<V1 ---- V2>
<V2 ---- V3>
<V3 ---- V5>
<V3 ---- V4>
release edge: 5
- 测试案例可参考这张图

嘻嘻嘻嘻 邻接多重表 部分到此结束😆😆
Kruskal 静候更新 👻👻👻😸😸😸
(有错误欢迎指出) (疑问也是)❤️❤️😍😍💖💖
持续更新中...