01数据结构-十字链表和多重邻接表

发布于:2025-08-12 ⋅ 阅读:(16) ⋅ 点赞:(0)

前言

邻接矩阵:很全面包括带权/无权,有向/无向都使用,但是对于顶点较多边很少的稀疏图来讲,空间利用率低。所以人们提出了邻接表

邻接表:主要用于存有向图,正邻接表计算出度简单,但计算入度难,逆邻接表计算入度简单,但计算出度难,要想计算入度出度都简单,人们就把正邻接表和逆邻接表结合起来了,就有了我们今天讲的十字链表

十字链表:主要用于存有向图,计算出度,入度简单,代码量可能会多一点

邻接多重表:主要用于存无向图,删除和插入一条边(如果我们用邻接表存无向图图1,要删除V1和V2这条边,我们是看作V1可以出度到V2,V2可以出度到V1,所以删除的时候我们要删除两次,但是用邻接多重表我们只用删除一次,只是这一次要把V1和V2关系理清,后面我们会讲到)

由上可以看到凡是带表字的都是边利用了链表的方法存储,带矩阵的边利用了数组的方式存储。
图1
图1

1.十字链表

1.1十字链表逻辑分析

图的十字链表法结构定义:
十字链表(Orthogonal List)是一种用于存储有向图和有向网的链式数据结构,它结合了邻接表和逆邻接表的优点,能够高效地处理顶点的出度和入度查询。

基本概念:
十字链表通过将邻接表和逆邻接表结合起来,使得同一条弧在两种链表中共享同一个结点,从而节省存储空间并提高操作效率。在有向图的十字链表表示中,每条弧对应一个弧结点,每个顶点对应一个顶点结点。

如图2:
定义节点结构(顶点结构):有三个域:顶点信息,如顶点名称或编号,firstIn指向以该顶点为弧头的第一条弧(入边表头指针),firstOut指向以该顶点为弧尾的第一条弧(出边表头指针)

定义弧结构(边结构):有四个域:弧尾顶点在顶点数组中的位置(索引),指向弧尾相同的下一条弧(出边链表指针),弧头顶点在顶点数组中的位置(索引),指向弧头相同的下一条弧(入边链表指针)。

其实很简单,这就相当于两条链表,一条是顶点的出度链表,一条是顶点的入度链表,由上一篇《01数据结构-图的邻接表的深搜和广搜代码实现》的基础很容易理解

先来看V0,V0有一条出到V3的边,于是顶点V0的firstOut指向以该顶点为弧尾的第一条弧,这条弧的head是V3,因为只有一条出度边,没有指向弧尾相同的下一条弧了,所以把这条弧的tNext设为NULL;由于是V0出度到V3,反过来看就是V3的入度,所以操作V3的firstIn指向以该顶点为弧头的第一条弧,这条弧的head是V3,tail是0,由于V3只有一条入度边,所以把这条弧的hNext设为NULL。V3没有初出度,把V3的firstOut设为NULL。

再来看V1,V1可以出度到V0,V0也可以入度到V1,由于V1有两条入度边,所以以V1为tail,V0为head的那条弧的hNext还应该指向以V2为tail,V0为head的那条弧,然后把以V2为tail,V0为head的这条弧的hNext设为NULL。V1还可以出度到V2,所以以V1为tail,V0为head的那条弧的tNext还要指向以V1为tail,V2为head的弧,然后把以V1为tail,V2为head的弧的tNext设为NULL。

同样的道理分析V2是一样,我就不赘述了。

总结,在写代码接口add的时候,出度和入度时的顶点的firstOut,firstIn都要进行操作,然后如果你想一直计算出度就一直操作弧的tNext,如果你想一直计算入度就一直操作弧的hNext。
图2
图2

1.2十字链表代码实现

十字链表数据结构的定义:

// 十字链表的边结构
typedef struct arcBox {
    int tailVertex;					// 弧尾编号,tailVertex作为顶点的出度信息
    struct arcBox *tailNext;		// 下一个以tailVertex作为弧尾的下一条边
    int headVertex;					// 弧头编号,以headVertex作为顶点的入度信息
    struct arcBox *headNext;		// 下一个以headVertex作为弧头的下一条边
    int weight;						// 弧权值
} ArcBox;

// 十字链表的顶点结构
typedef struct {
    int no;
    const char *show;
    ArcBox *firstIn;				// 该节点的入度
    ArcBox *firstOut;				// 该节点的出度
} CrossVertex;

// 利用十字链表的结构实现图的结构
typedef struct {
    CrossVertex *nodes;             //顶点集
    int numVertex;                  //顶点总数
    int numEdge;                    //边总数
}CrossGraph;

和前面讲的一样,只是用代码表示了出来

创建十字链表:CrossGraph * createCrossGraph(int n) ;

CrossGraph * createCrossGraph(int n) {
    CrossGraph * graph = malloc(sizeof(CrossGraph));
    if (graph==NULL) {
        fprintf(stderr, "g malloc error\n");
        return NULL;
    }
    graph->nodes=malloc(sizeof(CrossVertex)*n);
    if (graph->nodes == NULL) {
        free(graph);
        return NULL;
    }
    graph->numVertex=n;
    graph->numEdge=0;
    return graph;
}

初始化十字链表:

void initCrossGraph(CrossGraph *graph, const char *names[], int num) {
    for (int i = 0; i < num; ++i) {
        graph->nodes[i].show=names[i];
        graph->nodes[i].firstIn=graph->nodes[i].firstOut=NULL;
        graph->nodes[i].no=i;
    }
}

增加边:void addCrossArc(CrossGraph *graph, int tail, int head, int w);

void addCrossArc(CrossGraph *graph, int tail, int head, int w) {
    ArcBox *arc=malloc(sizeof(ArcBox));
    if (arc==NULL) {
        fprintf(stderr, "arc malloc error\n");
        return;
    }
    arc->weight=w;
    // 从出度关系上进入插入(头插法)
    arc->tailVertex = tail;
    arc->tailNext = graph->nodes[tail].firstOut;
    graph->nodes[tail].firstOut = arc;
    // 从入度关系上进入插入(头插法)
    arc->headVertex = head;
    arc->headNext = graph->nodes[head].firstIn;
    graph->nodes[head].firstIn = arc;
    graph->numEdge++;
}

这里和前面《01数据结构-图的邻接表的深搜和广搜代码实现》中边的添加一样,只不过要从两个角度添加,一个出度一个入度。

顶点的出度,入度的计算:

int inDegreeCrossGraph(const CrossGraph* graph, int no) {
    int count = 0;
    ArcBox *arc = graph->nodes[no].firstIn;
    while (arc) {
        count++;
        arc = arc->headNext;
    }
    return count;
}

int outDegreeCrossGraph(const CrossGraph* graph, int no) {
    int count = 0;
    ArcBox *arc = graph->nodes[no].firstOut;
    while (arc) {
        count++;
        arc = arc->tailNext;
    }
    return count;
}

不断地往后走,走一个count++,直到为空。

释放图:void releaseCrossGraph(CrossGraph* graph);

void releaseCrossGraph(CrossGraph* graph) {
    int numEdges = 0;	//最后释放了多少条边
    if (graph) {
        if (graph->nodes) {
            //从每一个顶点开始free边
            for (int i = 0; i < graph->numVertex; i++) {
                ArcBox *box = graph->nodes[i].firstOut;
                ArcBox *tmp;
                while (box) {
                    tmp = box;
                    box = box->tailNext;
                    free(tmp);
                    numEdges++;
                }
            }
            printf("released %d edges\n", numEdges);
            free(graph->nodes);
        }
        free(graph);
    }
}

我们要从每一个顶点开始free边,把边都free完了再free顶点最后free(graph)。

最后来测一下:

#include <stdio.h>
#include <stdlib.h>
#include "crossLinkGraph.h"

void setupCrossGraph(CrossGraph *graph) {
    char *nodeName[] = {"V0", "V1", "V2", "V3"};
    initCrossGraph(graph, nodeName, 4);
    addCrossArc(graph, 0, 3, 1);
    addCrossArc(graph, 1, 0, 1);
    addCrossArc(graph, 1, 2, 1);
    addCrossArc(graph, 2, 0, 1);
    addCrossArc(graph, 2, 1, 1);
}

int main() {
    int n = 4;
    CrossGraph *graph = createCrossGraph(n);
    if (graph == NULL) {
        return -1;
    }
    setupCrossGraph(graph);
    printf("V0的入度:%d\n", inDegreeCrossGraph(graph, 2));
    printf("V0的出度:%d\n", outDegreeCrossGraph(graph, 2));

    releaseCrossGraph(graph);

    return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\03_GraphStruct\CrossLinkGraph.exe
V0的入度:1
V0的出度:2
released 5 edges

进程已结束,退出代码为 0

2.多重邻接表

2.1多重邻接表逻辑实现

邻接多重表(Adjacency Multilist)是一种专门用于存储无向图的链式存储结构,其核心设计目标是解决邻接表中每条边需存储两次的问题,从而提高存储效率并简化边操作(如标记或删除)。以下是其结构定义的详细说明:

​​1. 顶点结构​​
​​数据域(data)​​:存储顶点的信息(如名称、权值等)。
​​指针域(firstedge)​​:指向该顶点的第一条边的边表结点(EBox),即该顶点边链表的头结点。

2.边结构

每条边的信息通过一个边表结点表示,结点包含以下域:

​​顶点位置(ivex, jvex)​​:存储该边两个顶点在顶点表中的下标。
​​链域(ilink, jlink)​​:
ilink:指向下一条依附于顶点ivex的边。
jlink:指向下一条依附于顶点jvex的边。
​​数据域(info)​​(可选):存储边的附加信息(如权值)。

如图3:V1和V2有边就把V1顶点结构中的firstEdge指向以V1为iVex,V2为jVex的边(由于多重邻接表存的是无向表,两者也可以交换V1和V2也可以在边中也交换位置),然后看iLink,由于V1和V4 也有边,所以把以V1为iVex,V2为jVex的边中的iLink指向以V1为iVex,V4为jVex的边,由于V1只有这两条边所以把以V1为iVex,V4为jVex的边的iLink设为NULL

因为是无向图,所以顶点V2的firstEdge应该指向以V1为iVex,V2为jVex的边,然后看jLink,由于V2与V5,V3也有边,所以以V1为iVex,V2为jVex的边中的jLink应该指向以V3为iVex,V2为jVex的边,然后以V3为iVex,V2为jVex的边中的jLink应该指向以V5为iVex,V2为jVex的边,然后这条边的jLink指向为NULL

其他顶点也是如此分析,我就不赘述了。

总结:由于是无向图,如果在添加边或者删除边的时候虽然只有一条边但是要从iVex和jVex两头开始找起,需要维护好两边之间的关系
图3
图3

2.2多重邻接表代码实现

多重邻接表和十字链表的部分代码很像,我就不一一讲了,只是把代码放在这里大家自己看。

#ifndef ADJACENCY_MULTI_LIST_H
#define ADJACENCY_MULTI_LIST_H
/* 邻接多重表,适用于无向图
 * 无向图如果使用邻接表存储,一条边会被处理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;

AdjacencyMultiList *createMultiList(int n);				// 产生n个节点的邻接多重表
void releaseMultiList(AdjacencyMultiList *graph);
void initMultiList(AdjacencyMultiList *graph, int n, char *names[]);	// 初始化邻接多重表的顶点值
// 插入边
int insertMultiListEdge(AdjacencyMultiList *graph, int a, int b, int w);
// 显示无向图
void showMultiList(AdjacencyMultiList *graph);
// 删除边
int deleteMultiListEdge(AdjacencyMultiList *graph, int a, int b);
#endif

多重邻接表的边结构MultiListEdge中的int mark是用来表示边有没有被访问过,因为是无向边,a能到b,b也能到a,如果没有这个标记位,同一条边会被遍历两边。

创建,初始化邻接多重表,给多重邻接表添加边:

#include <stdio.h>
#include <stdlib.h>
#include "adjacencyMultiList.h"
/* 产生邻接多重表的图 */
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;
}
/* 初始化邻接多重表的顶点信息 */
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;
	}
}
/* 向邻接多重表中,插入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;
}

这里和十字链表的代码逻辑基本一样。

显示无向图:void showMultiList(AdjacencyMultiList *graph);

/* 显示邻接多重表的边关系*/
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;
		}
	}
}

在static void initMark(AdjacencyMultiList *graph)中把所有边的标记为设为0,即没有被访问到的。在void showMultiList(AdjacencyMultiList *graph)中开始遍历访问,当边存在,且还未被访问打印出这条边的信息,并把这条边的标记位设位1。

删除边:int deleteMultiListEdge(AdjacencyMultiList *graph, int a, int b);

/* 删除节点编号a到节点编号b的边
 * 删除边节点,必须找到这个边的前一个节点,
 * */
int deleteMultiListEdge(AdjacencyMultiList *graph, int a, int b) {
	// 找到a编号的前一个边节点
	MultiListEdge *aPreEdge = NULL;
	MultiListEdge *aCurEdge = graph->nodes[a].firstEdge;
	while (aCurEdge &&
		!((aCurEdge->iVex == a && aCurEdge->jVex == b) || (aCurEdge->jVex == a && aCurEdge->iVex == b))) {
		aPreEdge = aCurEdge;
		if (aCurEdge->iVex == a) {
			aCurEdge = aCurEdge->iNext;
		} else {
			aCurEdge = aCurEdge->jNext;
		}
	}
	if (aCurEdge == NULL)
		return -1;
	// 找到b编号的前一个边结构
	MultiListEdge *bPreEdge = NULL;
	MultiListEdge *bCurEdge = graph->nodes[b].firstEdge;
	while (bCurEdge &&
		!((bCurEdge->iVex == a && bCurEdge->jVex == b) || (bCurEdge->iVex == b && bCurEdge->jVex == a))) {
		bPreEdge = bCurEdge;
		if (bCurEdge->iVex == b) {
			bCurEdge = bCurEdge->iNext;
		} else {
			bCurEdge = bCurEdge->jNext;
		}
	}
	if (bCurEdge == NULL)
		return -1;
	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节点的
			aPreEdge->iNext = aCurEdge->iNext;
		} else if (aPreEdge->iVex == a && aCurEdge->jVex == a){		// 是通过j出发找到a节点的
			aPreEdge->iNext = aCurEdge->jNext;
		} else if (aPreEdge->jVex == a && aCurEdge->iVex == a) {
			aPreEdge->jNext = aCurEdge->iNext;
		} else {
			aPreEdge->jNext = aCurEdge->jNext;
		}
	}
	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;
}

在《01数据结构-单向链表的操作及实现》中要删除链表中的元素,需要定义两个辅助指针一个要找到待删除位置的前一个元素,一个要找到待删除的位置,这里多重邻接表的思路其实和链表删除的思路是一样的,只是由于a可能在iVex也可能在jVex上,我们需要分多种情况讨论。

// 找到a编号的前一个边节点
	MultiListEdge *aPreEdge = NULL;
	MultiListEdge *aCurEdge = graph->nodes[a].firstEdge;
	while (aCurEdge &&
		!((aCurEdge->iVex == a && aCurEdge->jVex == b) || (aCurEdge->jVex == a && aCurEdge->iVex == b))) {
		aPreEdge = aCurEdge;
		if (aCurEdge->iVex == a) {
			aCurEdge = aCurEdge->iNext;
		} else {
			aCurEdge = aCurEdge->jNext;
		}
	}
	if (aCurEdge == NULL)
		return -1;
	// 找到b编号的前一个边结构
	MultiListEdge *bPreEdge = NULL;
	MultiListEdge *bCurEdge = graph->nodes[b].firstEdge;
	while (bCurEdge &&
		!((bCurEdge->iVex == a && bCurEdge->jVex == b) || (bCurEdge->iVex == b && bCurEdge->jVex == a))) {
		bPreEdge = bCurEdge;
		if (bCurEdge->iVex == b) {
			bCurEdge = bCurEdge->iNext;
		} else {
			bCurEdge = bCurEdge->jNext;
		}
	}
	if (bCurEdge == NULL)
		return -1;

上面这个部分就是分别从a和b开始找到a编号的前一个边结构和b编号的前一个边结构,具体循环的逻辑,拿找到a编号的前一个边节点举例,在while循环中循环条件当边存在时并且1。并且当前边不是从a到b或从到a的边时,进入循环体,更新aPreEdge到aCurEdge,如果找到了aCurEdge->iVex == a,根据多重邻接表的性质,一直aCurEdge = aCurEdge->iNext;,就一定会找到ab边的。如果没有找到aCurEdge->iVex == a,那么就aCurEdge = aCurEdge->jNext;如果都没有找到,那就退出。

	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节点的
			aPreEdge->iNext = aCurEdge->iNext;
		} else if (aPreEdge->iVex == a && aCurEdge->jVex == a){		// 是通过j出发找到a节点的
			aPreEdge->iNext = aCurEdge->jNext;
		} else if (aPreEdge->jVex == a && aCurEdge->iVex == a) {
			aPreEdge->jNext = aCurEdge->iNext;
		} else {
			aPreEdge->jNext = aCurEdge->jNext;
		}
	}
	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;
		}
	}

上面这段代码用于处理两边的关系了,拿从a边开始找的举例,如果aPreEdge == NULL说明没有进入循环体,头节点指向的边就是要删除的边,处理a编号的边当前边从i出发是a,如果aCurEdge->iVex == a,那就graph->nodes[a].firstEdge = aCurEdge->iNext;,因为 aCurEdge->iNext存的是顶点i编号的下一条边,如果aCurEdge->jVex == a,那就graph->nodes[a].firstEdge = aCurEdge->jNext;,因为 aCurEdge->jNext存的是顶点j编号的下一条边。

如果删除的不是头节点指向的边,由于是无向图,a和b可以在i,j的任意位置,所以处理逻辑的时候需要分四个情况讨论

  • aPreEdge->iVex == a && aCurEdge->iVex == a
    • aPreEdge->iNext = aCurEdge->iNext;
  • aPreEdge->iVex == a && aCurEdge->jVex == a
    • aPreEdge->iNext = aCurEdge->jNext;
  • aPreEdge->jVex == a && aCurEdge->iVex == a
    • aPreEdge->jNext = aCurEdge->iNext;
  • aPreEdge->jVex == a && aCurEdge->jVex == a
    • aPreEdge->jNext = aCurEdge->jNext;

始终记住如果a是存在的iVex,那么iNext存的是a边的下一条边,如果a是存在的jVex,那么jNext存的是a边的下一条边,想清楚这一点就能知道是aPreEdge的iNext还是aPreEdge的jNex该指向aCurEdge的iNext还是aCurEdge的jNext。

注意此时只处理要a和b的关系,我只说了处理a的时候代码逻辑,处理b的代码逻辑和处理a的时候代码逻辑一样就不做分析。

	free(aCurEdge);
	graph->edgeNum--;
	return 0;

最后这点直接free掉就行,free的时候只需要free一次,只是处理关系时两边都要处理。

最后free的代码我和删除的代码大同小异,注意的是一条边不能被free两次,所以我只free当i是较小的顶点编号时释放边,避免重复释放


void releaseMultiList(AdjacencyMultiList *graph) {
	if (graph == NULL) {
		return;
	}

	// 遍历所有节点,释放每条边(确保每条边只被释放一次)
	for (int i = 0; i < graph->vertexNum; i++) {
		MultiListEdge *current = graph->nodes[i].firstEdge;
		// 保存下一个边的指针(当前边释放后就无法访问了)
		MultiListEdge *next = NULL;
		while (current != NULL) {
			if (current->iVex == i) {
				next = current->iNext;
			} else {
				next = current->jNext;
			}
			// 只在i是较小的顶点编号时释放边,避免重复释放
			int otherVex = (current->iVex == i) ? current->jVex : current->iVex;
			if (i < otherVex) {
				free(current);
			}
			current = next;
		}
		// 清空节点的边指针
		graph->nodes[i].firstEdge = NULL;
	}

	// 释放节点数组(如果是动态分配的)
	if (graph->nodes != NULL) {
		free(graph->nodes);
		graph->nodes = NULL;
	}

	// 释放图结构本身
	free(graph);
}

最后测一下:

#include <stdio.h>
#include "adjacencyMultiList.h"

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;
}

结果:

D:\work\DataStruct\cmake-build-debug\03_GraphStruct\AdjacencyMultiList.exe
insert 5 edges!
<V1 ---- V4>
<V1 ---- V2>
<V2 ---- V3>
<V3 ---- V5>
<V3 ---- V4>

进程已结束,退出代码为 0

这里的代码大家能写尽量写,其实能看懂就行,后面我们讲算法基本还是用的前面邻接矩阵,邻接表的思路。大概先写这些吧,今天的博客就先写到这,谢谢您的观看。


网站公告

今日签到

点亮在社区的每一天
去签到