(二)图的存储结构及图的构建、释放——精选万字长文

专栏:图论专栏
专栏导航:专栏导航

图是一种数据结构,既然是数据结构,那么就需要存储。所以这一篇文章我们来讨论图的存储结构。

二维数组邻接矩阵(适用于稠密图)

定义二维数组: i n t G [ N ] [ N ] ( N 表示结点的数量 ) int G[N][N](N表示结点的数量) intG[N][N](N表示结点的数量)
G [ i ] [ j ] G[i][j] G[i][j]表示一条从结点 i i i 到结点 j j j 的有向边的权值,定义如下:
{ 1 或权值 当结点 i 到结点 j 两点之间有边或弧 0 或 ∞ 当结点 i 到结点 j 两点之间没有边和弧 \begin{cases} 1或权值 & 当结点i到结点j两点之间有边或弧 \\ 0或∞ & 当结点i到结点j两点之间没有边和弧 \end{cases} {1或权值0当结点i到结点j两点之间有边或弧当结点i到结点j两点之间没有边和弧
一般来说,当两个节点没有边相连时,若该程序是用于求最小值的,则设为 ∞ ∞ ;若用于求最大值,则设为 0 0 0(没有负权值边)或者 − ∞ -∞ (有负权值边)。因为这样反其道而行之,通过这条边则一定是不划算的,以此便免去了判断的功夫。(在后续计算的时候需要假设图是一个完全图,方便计算,后面会提到)

无向无权图的邻接矩阵表示方法

如下图,这是一个无向无权图(边没有方向和权值):
演示邻接矩阵表示方法-无向无权图

上图对应的邻接矩阵表示如下:
G ( a ) = [ 0 1 1 0 1 0 1 1 1 1 0 1 0 1 1 0 ] G(a)= \begin{bmatrix} 0 & 1 & 1 & 0 \\ 1 & 0 & 1 & 1 \\ 1 & 1 & 0 & 1 \\ 0 & 1 & 1 & 0 \end{bmatrix} G(a)= 0110101111010110
文字表示方法如下:
G ( a ) = [ 1 − 1 不通 1 − 2 通 1 − 3 通 1 − 4 不通 2 − 1 通 2 − 2 不通 2 − 3 通 2 − 4 通 3 − 1 通 3 − 2 通 3 − 3 不通 3 − 4 通 4 − 1 不通 4 − 2 通 4 − 3 通 4 − 4 不通 ] G(a)= \begin{bmatrix} 1-1不通 & 1-2通 & 1-3通 & 1-4不通 \\ 2-1通 & 2-2不通 & 2-3通 & 2-4通 \\ 3-1通 & 3-2通 & 3-3不通 & 3-4通 \\ 4-1不通 & 4-2通 & 4-3通 & 4-4不通 \end{bmatrix} G(a)= 11不通213141不通1222不通3242132333不通4314不通243444不通
首先,该图中没有出现“自环”的情况(即有一条边的起点终点都为同一个结点),所以所有表示“自环”的都设为0。
这是一个无向图,所以只要点A和点B之间存在一条边,就既能从点A通往点B,也能从点B通往点A。图中,结点1和结点4并没有相连的边,即既不能从结点1直接通往结点4,也不能从结点4直接通往结点1。
由于这是一个无权图,所以这个邻接矩阵仅仅用来表示两点之间是否相通。

有向无权图的邻接矩阵表示方法

如下图,这是一个有向无权图(边有方向,但是没有权值):
演示邻接矩阵表示方法-有向无权图

上图对应的邻接矩阵表示如下:
G ( a ) = [ 0 1 1 0 0 1 0 1 0 ] G(a)= \begin{bmatrix} 0 & 1 & 1 \\ 0 & 0 & 1 \\ 0 & 1 & 0 \end{bmatrix} G(a)= 000101110
文字表示方法如下:
G ( a ) = [ 1 − 1 不通 1 − 2 通 1 − 3 通 2 − 1 不通 2 − 2 不通 2 − 3 通 3 − 1 不通 3 − 2 通 3 − 3 不通 ] G(a)= \begin{bmatrix} 1-1不通 & 1-2通 & 1-3通 \\ 2-1不通 & 2-2不通 & 2-3通 \\ 3-1不通 & 3-2通 & 3-3不通 \end{bmatrix} G(a)= 11不通21不通31不通1222不通32132333不通
首先,该图中也没有出现“自环”的情况(即有一条边的起点终点都为同一个结点),所以所有表示“自环”同样地的都设为0。
这是一个有向图,所以只有点A和点B之间存在一条边,并且方向是 A − B A-B AB才能在矩阵中表示为1。如图中,结点1可以到结点2、结点3,但是结点2、结点3并不能通往结点1。
这是一个无权图,所以这个邻接矩阵仅仅用来表示点A、点B是否有可以从点A到点B或者从点B到点A的边。

有向有权有自环图的邻接矩阵表示方法

如下图,这是一个有向有权有自环图(边有方向、权值和自环):
演示邻接矩阵表示方法-有向有权有自环图

上图对应的邻接矩阵表示如下:
G ( a ) = [ ∞ 5 11 ∞ ∞ 6 ∞ 25 33 ] G(a)= \begin{bmatrix} ∞ & 5 & 11 \\ ∞ & ∞ & 6 \\ ∞ & 25 & 33 \end{bmatrix} G(a)= 52511633
文字表示方法如下:
G ( a ) = [ 1 − 1 不通 1 − 2 通,权值 5 1 − 3 通,权值 11 2 − 1 不通 2 − 2 不通 2 − 3 通,权值 6 3 − 1 不通 3 − 2 通,权值 25 3 − 3 通,权值 33 ] G(a)= \begin{bmatrix} 1-1不通 & 1-2通,权值5 & 1-3通,权值11 \\ 2-1不通 & 2-2不通 & 2-3通,权值6 \\ 3-1不通 & 3-2通,权值25 & 3-3通,权值33 \end{bmatrix} G(a)= 11不通21不通31不通12通,权值522不通32通,权值2513通,权值1123通,权值633通,权值33
我们假设该图是用于求解最小值问题的,所以为了方便求解,我们应当把不能通过的边设为 ∞ ∞
该图中出现了“自环”的情况(即有一条边的起点终点都为同一个结点),所以表示结点3自环的一项(3-3)应当标记为它的权值33。
这是一个有向图,所以只有点A和点B之间存在一条边,并且方向是 A − B A-B AB才能在矩阵中表示为1。如图中,结点1可以到结点2、结点3,但是结点2、结点3并不能通往结点1。
这是一个有权图,所以这个邻接矩阵用来表示点A与点B两个方向的边的权值(若无,用 ∞ ∞ ,上文提及过原因)

建立二维数组邻接表参考程序

该代码适用于C语言和C++

#include <stdio.h>  //包含函数scanf和printf
#include <string.h> //包含函数memset 
#define N (100)     //该程序最多只能输入100个点 
int g[N + 1][N + 1],n,k,from,to,w ;
int main(){
	scanf("%d",&n) ;
	memset(g,0,sizeof(g)) ;//全部清零 
	//memset(g,0x7f,sizeof(g)) ;//全部设为一个很大的数 
	//memset(g,0xaf,sizeof(g)) ;//全部设为一个很小的数 
	scanf("%d",&k) ;
	while(k --){
		scanf("%d%d%d",&from,&to,&w) ;//输入边连接的顶点的序号及边的权值 
		g[from][to] = w ;             //若无输入权值,设为1即可 
		g[to][from] = w ;             //由于无向图的对称性,需要镜像操作
									  //如果是有向图,则不需要这句代码  
	}
	/*
	接下来的操作...... 
	接下来的操作...... 
	接下来的操作...... 
	*/
	return 0 ;
}

邻接表(适用于稀疏图)

图转化为邻接表

邻接表包含了n个单向链表,第i个单向链表存储的是以结点i为起点的边。每次增添新的边时,都是从链表头部插入的。如下:

无向无权图的邻接表表示方式

无向无权图
它的邻接表如下:
G ( a ) = [ 3 2 4 3 1 4 2 1 3 2 ] G(a)= \begin{bmatrix} 3 & 2 \\ 4 & 3 & 1 \\ 4 & 2 & 1 \\ 3 & 2 \end{bmatrix} G(a)= 3443232211
它的文字表示方式是:
G ( a ) = [ 有从结点 1 到结点 2 、 3 的边 有从结点 2 到结点 1 、 3 、 4 的边 有从结点 3 到结点 1 、 2 、 4 的边 有从结点 4 到结点 2 、 3 的边 ] G(a)= \begin{bmatrix} 有从结点1到结点2、3的边 \\ 有从结点2到结点1、3、4的边 \\ 有从结点3到结点1、2、4的边 \\ 有从结点4到结点2、3的边 \end{bmatrix} G(a)= 有从结点1到结点23的边有从结点2到结点134的边有从结点3到结点124的边有从结点4到结点23的边

有向无权图的邻接表表示方式

有向无权图的邻接表表示方式
它的邻接表如下:
G ( a ) = [ 3 2 3 2 ] G(a)= \begin{bmatrix} 3 & 2 \\ 3 \\ 2 \end{bmatrix} G(a)= 3322
它的文字表示方式是:
G ( a ) = [ 有从结点 1 到结点 2 、 3 的边 有从结点 2 到结点 3 的边 有从结点 3 到结点 2 的边 ] G(a)= \begin{bmatrix} 有从结点1到结点2、3的边 \\ 有从结点2到结点3的边 \\ 有从结点3到结点2的边 \end{bmatrix} G(a)= 有从结点1到结点23的边有从结点2到结点3的边有从结点3到结点2的边

有向有权有自环图的邻接表表示方式

有向有权有自环图的邻接表表示方式
它的邻接表如下:
G ( a ) = [ 3 2 3 3 2 ] G(a)= \begin{bmatrix} 3 & 2 \\ 3 \\ 3 & 2 \end{bmatrix} G(a)= 33322
它的文字表示方式是:
G ( a ) = [ 有从结点 1 到结点 2 、 3 的边,权值分别为 5 、 11 有从结点 2 到结点 3 的边,权值分别为 6 有从结点 3 到结点 2 、 3 的边,权值分别为 25 、 33 ] G(a)= \begin{bmatrix} 有从结点1到结点2、3的边,权值分别为5、11 \\ 有从结点2到结点3的边,权值分别为6 \\ 有从结点3到结点2、3的边,权值分别为25、33 \end{bmatrix} G(a)= 有从结点1到结点23的边,权值分别为511有从结点2到结点3的边,权值分别为6有从结点3到结点23的边,权值分别为2533

借助STL库实现邻接表

类型定义

struct Point ;             //声明顶点结构体 
typedef struct Edge{       //定义边结构体 
	int v,dis ;            //表示这条边的终点结点编号和权值 
} Edge ;                   //用typedef语句,使下次使用时不用加struct 
typedef struct Point{      //定义顶点结构体 
	forward_list<Edge> e ; //用于存储起点为自身的边 
} Point ;                  //用typedef语句,使下次使用时不用加struct 

结构体 E d g e Edge Edge P o i n t Point Point 分别是边和点的结构体。一个顶点可能会有多条以自身为起点的边,且数量不确定,所以我们就可以使用STL中的单向链表进行存储这些边(因为在这过程中我们只需要边的遍历,而不需要边的回溯,所以使用单向链表更加合适)。而每条边有两个要存储的信息,终点顶点编号,长度。因为边是存储在起点顶点的链表里的,所以就不用存储起点顶点的编号了。

初始化

顶点的初始化

由于使用了STL库,所以初始化非常简单。我们使用STL库中的vector存储顶点,所以只需要用resize函数预留 n + 1 n+1 n+1 个顶点的内存即可。这样可使用的范围是0~n,但是我们只需要使用1~n即可。
代码如下:

a.resize(n + 1) ;
//预留足够的内存 
边的初始化

使用STL库中的forward_list可以构建一个单向链表。我们只需要在起点顶点的链表使用push_front函数增添边节点即可。

for(int i = 1;i <= n;++ i){
	scanf("%d%d%d",&u,&v,&d) ;
	//输入点数、边数、权值 
	a[u].e.push_front({v,d}) ;
	//增加这条边 
}

完整代码

#include <stdio.h>         //包含printf和scanf 
#include <forward_list>    //包含forward_list 
#include <vector>          //包含vector 
using std::forward_list ;  //声明要使用STL库中的forward_list 
using std::vector ;        //声明要使用STL库中的vector 
struct Point ;             //声明顶点结构体 
typedef struct Edge{       //定义边结构体 
	int v,dis ;            //表示这条边的终点结点编号和权值 
} Edge ;                   //用typedef语句,使下次使用时不用加struct 
typedef struct Point{      //定义顶点结构体 
	forward_list<Edge> e ; //用于存储起点为自身的边 
} Point ;                  //用typedef语句,使下次使用时不用加struct 
vector<Point> a ;          //用于存储顶点 
int n,m,u,v,d ;            //点数、边数、起点、终点、权值 
int main(){
	scanf("%d%d",&n,&m) ;
	//输入点数、边数 
	a.resize(n + 1) ;
	//预留足够的内存 
	for(int i = 1;i <= n;++ i){
		scanf("%d%d%d",&u,&v,&d) ;
		//输入点数、边数、权值 
		a[u].e.push_front({v,d}) ;
		//增加这条边 
	}
	/*
		执行接下来的代码...... 
		执行接下来的代码...... 
		执行接下来的代码...... 
	*/
	return 0 ;
} 

数组模拟链表实现邻接表

注:因为是数组模拟链表,所以这种方法基于上一种方法的原理。最好先学习第一种方法再来学习此方法。

类型定义

struct Edge{             //定义边结构体 
	int next,to,dis ;    //下一个边结点,这条边的终点结点编号,
						 //这条边的权值 
} ;

这个结构体用于存储边。

变量定义

vector<Edge> edge ;      //定义边动态数组edge,节省内存 
int head[N + 5],n,m,u,v,d ;
//存储顶点边链表头节点的数组,顶点数,边数,起点顶点,终点顶点,权值 

使用动态数组来存储边,边按照编号来使用。head数组用于存储结点所对应的边的链表的头节点,用于插入结点和遍历结点。
n,m,u,v,d分别是顶点数,边数,起点顶点,终点顶点,权值 。

初始化

初始化顶点

这种方法不需要初始化顶点。

初始化边

首先,为了方便使用,我们先往边动态数组中增加一个无效数据,使得有效数据的下标从零开始。
随后输入m条边的起点顶点,终点顶点,权值 。在动态数组中增添一个新的边结点。注意,这个边结点插入在链表的头结点处,所以我们需要把它的 n e x t next next 设为原来的头节点的编号,这样就可以完成跳转到原来头节点的操作。最后,把 h e a d [ u ] head[u] head[u] 设为当前结点的编号,表示这个顶点的边链表头节点变更为了现在的这个结点。

完整代码

#include <stdio.h>       //包含函数scanf和printf
#include <vector>        //包含vector 
using std::vector ;      //声明要使用STL库中的vector  
#define N (100)          //最多只能有100个点 
#define M (N * (N - 1))  //计算最多会有几条边 
struct Edge{             //定义边结构体 
	int next,to,dis ;    //下一个边结点,这条边的终点结点编号,
						 //这条边的权值 
} ;
vector<Edge> edge ;      //定义边动态数组edge,节省内存 
int head[N + 5],n,m,u,v,d ;
//存储顶点边链表头节点的数组,顶点数,边数,起点顶点,终点顶点,权值 
void add_edge(int from,int to,int dis){
	//参数:起点顶点,终点顶点,权值 
	edge.push_back({head[from],to,dis}) ;
	//增加新结点,next指针指向原来的头节点 
	head[from] = edge.size() - 1 ;
	//新结点成为新的头节点  
	return ;
}
int main(){
	scanf("%d%d",&n,&m) ;
	//输入顶点数、边数 
	edge.push_back({-1,-1,-1}) ;
	//提前加入一个无用的边,仅仅用于让边的编号能从一开始 
	for(int i = 1;i <= m;++ i){
		//输入边 
		scanf("%d%d%d",&u,&v,&d) ;
		//输入起点顶点,终点顶点,权值 
		add_edge(u,v,d) ;
		//增添边 
	}
	/*
		执行接下来的代码...... 
		执行接下来的代码...... 
		执行接下来的代码...... 
	*/
	return 0 ;
}

自己实现单向链表,用于实现邻接表

注:该部分内容涉及指针、动态内存分配、链表。

类型定义

邻接表中的“表”就是链表,所以我们使用链表来实现,顶点和边分别使用下面的两个结构体:

struct Point ;          //声明顶点结构体 
typedef struct Edge{    //定义边结构体 
	struct Point *v ;   //这条边的终点节点
	struct Edge *next ; //这条边的下一条边
	int dis ;           //这条边的权值
} Edge ;                //使用typedef语句,使下次使用时不用加struct 
typedef struct Point{   //定义顶点结构体 
	Edge *e ;           //边链表的的头节点
} Point ;               //使用typedef语句,使下次使用时不用加struct 

结构体 E d g e Edge Edge P o i n t Point Point 分别是边和点的结构体。一个顶点可能会有多条以自身为起点的边,且数量不确定,所以我们就可以使用链表进行存储这些边。在结构体 P o i n t Point Point 中有一个成员 e e e ,它指向当前顶点的边的单向链表的头结点。结构体 E d g e Edge Edge 用于构建一个单向链表, n e x t next next 用于指向下一个边结点, v v v 指向这条边的终点顶点, d i s dis dis 存储这条边的权值。那么我们就可以通过这两个结构体构建邻接表。

初始化

首先,我们需要先初始化好所有的点,这样才可以进行边的初始化。

由于点的编号是由1~n的,所以为了使用的方便,我们需要分配一个大小为n+1的顶点数组。并且还需要把这些顶点的边链表初始化好,也就是把链表的头、尾指针初始化为0,避免“野指针”的情况出现(因为动态分配内存时,变量的初始值是不确定的,所以为了区分该指针是否分配了内存,需要设为0表示未分配内存)。代码如下:

scanf("%d%d",&n,&m) ;
//输入点数与边数 
head = new Point[n + 1] ;
//为head指针分配(n+1)个Point结构体的内存
//可以以head[i]的形式访问(i=0~n) 
for(int i = 1;i <= n;++ i)
	//由于只需要1~n,所以直接初始化1~n即可 
	head[i].s = head[i].e = 0 ;
	//初始化s,e为零,避免野指针。 
紧接着,我们可以开始实现边的增添了。为了使用方便,我们可以定义一个函数addEdge。

addEdge函数需要三个参数,分别是起点顶点、终点顶点和边的权值。我们需要在起点顶点的边链表的末尾增添这条边。由于我们使用的是单链表,所以步骤如下:
首先,假设这个函数的参数为 P o i n t ∗ 变量 f r o m , P o i n t ∗ 变量 t o , i n t 变量 d i s Point*变量from,Point*变量to,int变量dis Point变量from,Point变量to,int变量dis,表示的意义如上。
(1)分配一个新的边结构体。这个边结构体有三个成员变量, P o i n t ∗ Point* Point 类型成员 v v v 表示这条边的终点结点,所以把它指向 t o to to E d g e Edge Edge 类型指针 n e x t next next 指针指向该边链表结点的下一个结点,由于我们只保存下了链表的头结点,所以只能够在链表头部插入节点。那么这个新结点就会是新的头结点,所以它的 n e x t next next 指针需要指向原来的头节点。代码如下:

Edge *ed = new Edge ;
//分配一个边结构体 
(ed -> v) = to ;
//表示这条边的终点是to顶点 
(ed -> dis) = dis ;
//表示这条边的权值是dis 
(ed -> next) = (from -> e) ;
//因为只存储了链表的头结点,只能在链表头部插入结点,
//则将新结点的后继指针指向原来的头结点 

(2)处理结点之间连接的问题。这个问题同样非常好解决,由于这个新结点成为了头结点,所以我们直接把头结点指针 e e e 指向这个新结点即可。
代码如下:

(from -> e) = ed ;
//新结点成为头节点 

释放内存

这篇文章仅仅讨论图的存储结构,所以跳过算法部分,直接进入最后一部分——内存的释放。

释放内存分为两步:
(1)释放每个顶点的边链表的内存。对于顶点的遍历,由于顶点使用数组存储,所以我们只需要通过下标遍历即可。我们需要遍历顶点的边链表,从而释放所有边结点的内存。对于链表的遍历并释放内存,我们需要使用两个指针 n o w now now n e x t next next,类型都是 E d g e ∗ Edge* Edge,前者用于存储当前要释放的结点,后者用于存储当前结点的下一个结点。这是因为在一个结点释放掉之后,我们就不能再使用它了,所以需要提前把下一个结点保存下来。在当前结点释放完之后,把 n o w now now 指针指向 n e x t next next,就可以进行下一个结点的访问,这样才能正常地遍历节点。最后,当 n o w now now 指针为 0 0 0 的时候,就表示遍历、释放完毕了,可以进行下一个结点的遍历了。
(2)释放顶点的内存。我们可以直接使用 d e l e t e [ ] delete[] delete[] 语句将所有顶点所占用的内存释放掉。
代码如下:

//作用:释放顶点数组和边链表使用的内存 
void del(Point* head,int from,int to){
	//参数:顶点数组,
	//使用到的第一个的顶点和最后一个顶点的编号 
	if(!head)return ;
	//如果顶点数组为空,直接返回 
	while(from <= to){
		if(!head[from].e){ ++ from ; continue ; }
		//如果边链表为空,不进行操作 
		Edge *now = head[from].e,*next ;
		//存储当前结点和当前结点的下一个节点 
		while(now){
			//重复直到到达尾结点 
			next = now -> next ;
			//先保存下一个结点 
			delete now ;
			//释放当前结点 
			now = next ;
			//准备释放下一个结点 
		}
		head[from].e = 0 ;
		//指针清零 
		++ from ;
		//遍历下一个结点 
	}
	delete[] head ;
	//释放顶点数组内存 
	return ;
}

完整代码

#include <stdio.h>      //包含函数scanf和printf
struct Point ;          //声明顶点结构体 
typedef struct Edge{    //定义边结构体 
	struct Point *v ;   //这条边的终点节点
	struct Edge *next ; //这条边的下一条边
	int dis ;           //这条边的权值
} Edge ;                //使用typedef语句,使下次使用时不用加struct 
typedef struct Point{   //定义顶点结构体 
	Edge *s,*e ;        //边链表的的头节点和尾结点
} Point ;               //使用typedef语句,使下次使用时不用加struct 
Point *head ;           //顶点指针,用于动态分配顶点,数量可以不确定 
void addEdge(Point* from,Point* to,int dis) ;
//声明函数addEdge,用于增加一条由from顶点到to顶点,权值为dis的边 
void del(Point* head,int from,int to) ;
//声明函数del,用于释放顶点数组和边链表使用的内存 
int n,m,u,v,d ;
//点数,边数,边的起点、终点、权值。 
int main(){
	scanf("%d%d",&n,&m) ;
	//输入点数与边数 
	head = new Point[n + 1] ;
	//为head指针分配(n+1)个Point结构体的内存
	//可以以head[i]的形式访问(i=0~n) 
	for(int i = 1;i <= n;++ i)
		//由于只需要1~n,所以直接初始化1~n即可 
		head[i].s = head[i].e = 0 ;
		//初始化s,e为零,避免野指针。 
	for(int i = 1;i <= m;++ i){
		//初始化m条边 
		scanf("%d%d%d",&u,&v,&d) ;
		//输入边的起点、终点的编号及边的权值 
		addEdge(head + u,head + v,d) ;
		//给点u增添一条到点v、长度为d的边 
	}
	/*
		执行接下来的代码...... 
		执行接下来的代码...... 
		执行接下来的代码...... 
	*/
	del(head,1,n) ;
	//释放顶点数组和边链表使用的内存 
	return 0 ;
}
//作用:向点from的边链表中增加一条有向从from到to长度为dis的边。 
void addEdge(Point* from,Point* to,int dis){
	//参数:边的起点、终点顶点的指针及边的权值 
	Edge *ed = new Edge ;
	//分配一个边结构体 
	(ed -> v) = to ;
	//表示这条边的终点是to顶点 
	(ed -> dis) = dis ;
	//表示这条边的权值是dis 
	(ed -> next) = 0 ;
	//表示这条边是链表最后一个结点,
	//暂时没有后继节点,所以将next指针指向0 
	if(from -> s)(from -> e -> next) = ed ;
	//如果在这之前已经有其他结点了,
	//则将已存在最后一个结点的next指针指向当前结点 
	else from -> s = ed ;
	//否则,这就是第一个结点,直接把链表指针s指向该结点 
	from -> e = ed ;
	//指针e永远指向最后一个结点 
	return ;
}
//作用:释放顶点数组和边链表使用的内存 
void del(Point* head,int from,int to){
	//参数:顶点数组,
	//使用到的第一个的顶点和最后一个顶点的编号 
	if(!head)return ;
	//如果顶点数组为空,直接返回 
	while(from <= to){
		if(!head[from].s){ ++ from ; continue ; }
		//如果边链表为空,不进行操作 
		Edge *now = head[from].s,*next ;
		//存储当前结点和当前结点的下一个节点 
		while(now != head[from].e){
			//重复直到到达尾结点 
			next = now -> next ;
			//先保存下一个结点 
			delete now ;
			//释放当前结点 
			now = next ;
			//准备释放下一个结点 
		}
		delete head[from].e ;
		//最后一个节点,即尾结点,直接释放 
		head[from].s = head[from].e = 0 ;
		//指针清零 
		++ from ;
		//遍历下一个结点 
	}
	delete[] head ;
	//释放顶点数组内存 
	return ;
}

尾声

如果你认真看到了这里——恭喜你,通过一篇万字好文透彻地学习了图的存储结构及图的构建、释放!你的编程水平又提升了一个阶梯。
想要学习更多关于“图论”的知识。
专栏:图论专栏
专栏导航:专栏导航

  • 39
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值