图论基础 | 邻接多重表原理剖析 + 完整可运行C语言代码(插边/删边/释放)

邻接多重表

文章目录

  • 邻接多重表
    • [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 静候更新 👻👻👻😸😸😸

(有错误欢迎指出) (疑问也是)❤️❤️😍😍💖💖

持续更新中...

相关推荐
学习星球1 小时前
【LeetCode算法题精讲】二分查找精讲
java·数据结构·算法·leetcode·职场和发展·图搜索
民乐团扒谱机1 小时前
【微实验】线性代数之——奇异值分解 SVD:穿透数据迷雾的“X光机”,附 MATLAB 全流程仿真
线性代数·算法·matlab
Eloudy1 小时前
Ubuntu 22.04 从源码编译安装 ROS 2 Jazzy 笔记
linux·笔记·ubuntu
arin8761 小时前
【图论】网络流
算法
小僧景贤1 小时前
嵌入式之队列解析(裸机/RTOS通用原理+实战场景+源码解析)
数据结构·嵌入式·队列·裸机开发·rtos开发·固件架构
_Narcissus_1 小时前
B+树的概念和操作笔记(含完整代码实现)
c语言·数据结构·数据库·c++·笔记·b树·算法
aichitang20241 小时前
快乐泛函每一天!内积空间
c++·python·数学·算法·机器学习·ai·泛函分析
Wang's Blog2 小时前
PostgreSQL笔记33: 索引底层原理、扫描类型与属性体系深度解析
数据库·笔记·postgresql
是上好佳佳佳呀2 小时前
【深度学习|DAY05】卷积神经网络深度学习笔记
笔记·深度学习·cnn