40. 数据结构笔记之四十图的邻接多重链表表示实现

本文介绍了一种用于存储无向图的邻接多重表数据结构,并提供了详细的结构定义及其实现代码。邻接多重表能更方便地进行图的遍历和其他操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

40. 数据结构笔记之四十图的邻接多重链表表示实现

“一个人的价值 , 应当看他贡献什么 , 而不应当看他取得什么。-- 爱因斯坦”

1.  邻接多重表

邻接多重表(AdjacencyMultilist)主要用于存储无向图。因为,如果用邻接表存储无向图,每条边的两个边结点分别在以该边所依附的两个顶点为头结点的链表中,这给图的某些操作带来不便。例如,对已访问过的边做标记,或者要删除图中某一条边等,都需要找到表示同一条边的两个结点。因此,在进行这一类操作的无向图的问题中采用邻接多重表作存储结构更为适宜。

    邻接多重表的存储结构和十字链表类似,也是由顶点表和边表组成,每一条边用一个结点表示,其顶点表结点结构和边表结点结构如图1 所示。

图2

其中,顶点表由两个域组成,vertex域存储和该顶点相关的信息firstedge 域指示第一条依附于该顶点的边;边表结点由六个域组成,mark 为标记域,可用以标记该条边是否被搜索过;ivex 和jvex 为该边依附的两个顶点在图中的位置;ilink 指向下一条依附于顶点ivex的边;jlink 指向下一条依附于顶点jvex 的边,info 为指向和边相关的各种信息的指针域。

如下图3邻接多重表。

在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,则每个边结点同时链接在两个链表中。可见,对无向图而言,其邻接多重表和邻接表的差别,仅仅在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。因此,除了在边结点中增加一个标志域外,邻接多重表所需的存储量和邻接表相同。在邻接多重表上,各种基本操作的实现亦和邻接表相似。

 

2. 代码实现

2.1      定义结构

/*定义图的邻接多重表结构*/

typedefstructebox

{

           int      ivex,jvex;//该边依附的两个顶点位置

           structebox*ilink,*jlink;//分别指向依附这两个顶点的下一条边

}ebox;

 

typedefstructvexbox

{

           chardata[20];

           ebox*firstedge;//指向第一条依附该顶点的边

}vexbox;

 

typedefstruct

{

           vexboxvexes[max_vertex_num];

           intvexnum,edgenum;//无向图的当前顶点数和边数

}amlgraph;

 

2.2      Main函数

调用函数creategraph函数创建图。

输入遍历的起点,得到起点的位置。

调用dfstraverse函数输出深度优先遍历,

然后调用bfstraverse输出广度优先遍历。

关于具体如何实现深度遍历和广度遍历,看下回分析。

实现如下图4

 

2.3      LocateVex

返回是第几个点。

如果不是顶点中的其中一个点则返回-1.

2.4      creategraph

代码的主要函数

采用邻接多重表存储结构,构造无向图G

先打开cmnet.txt文件进行读取,如果打开失败则退出。

先读取图的顶点个数,边的数量。

然后根据顶点数量,读入每个顶点的数据,并设置该顶点相关的第一个边为NULL。

然后根据边的数量,读入边的两个点。

然后创建一个边表节点,将ivex指向当前边的第一个点,jvex指向当前边的第二个点。

然后将该边两头的点ilink和jlink都插在各自的表头。

最后关闭数据文件。

 

void creategraph(amlgraph&G)

{     

           charva[20],vb[20];

           inti,j,k;

           ebox*p;

           FILE*graphlist;

 

           graphlist=fopen("cmnet.txt","r");//打开数据文件,并以graphlist表示

           if(graphlist == NULL )

           {

                     printf("errorcan not read cmnet.txt." );

                     getchar();

                     exit(0);

           }

 

           fscanf(graphlist,"%d",&G.vexnum);//读入顶点个数

           fscanf(graphlist,"%d",&G.edgenum);//读入边数

 

           for(k=1;k<=G.vexnum;++k) //初始化顶点

           {

                     fscanf(graphlist,"%s",&G.vexes[k].data);

                     G.vexes[k].firstedge=NULL;

           }

 

           for(k=1;k<=G.edgenum;++k) //从文件读入边构造图

           {

                     fscanf(graphlist,"%s%s",&va,&vb);

 

                     p=(ebox*)malloc(sizeof(ebox));

                     i=LocateVex(G,va);

                     p->ivex=i;

                     j=LocateVex(G,vb);

                     p->jvex=j;

                     p->ilink=G.vexes[i].firstedge;//插在表头

                     G.vexes[i].firstedge=p;

                     p->jlink=G.vexes[j].firstedge;//插在表头

                     G.vexes[j].firstedge=p;                    

           }

           fclose(graphlist);//关闭数据文件

 

}

 

2.5      cmnet.txt文件测试内容

5

5

1

2

3

4

5

1

2

1

3

2

3

2

4

3

4

3. 源码

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define max_vertex_num 30
#define OK 1
#define ERROR 0
typedef int status;

/*定义图的邻接多重表结构*/
typedef struct ebox
{
int      ivex,jvex;//该边依附的两个顶点位置
struct ebox *ilink,*jlink;//分别指向依附这两个顶点的下一条边
}ebox;

typedef struct vexbox
{
char data[20];
ebox *firstedge;//指向第一条依附该顶点的边
}vexbox;

typedef struct
{
vexbox vexes[max_vertex_num];
int vexnum,edgenum;//无向图的当前顶点数和边数
}amlgraph;

/*若G中存在顶点u,则返回该顶点在图中位置*/
int LocateVex(amlgraph G, char u[20])
{
int i;
for(i=1;i<=G.vexnum;++i)
{
if(strcmp(u,G.vexes[i].data)==0)
return i;
}
return -1;
}


/* 采用邻接多重表存储结构,构造无向图G */
void creategraph(amlgraph &G)
{     
char va[20],vb[20];
int i,j,k;
ebox *p;
FILE *graphlist;

graphlist=fopen("cmnet.txt","r"); // 打开数据文件,并以graphlist表示
if( graphlist == NULL )
{
printf( "error can not read cmnet.txt." );
getchar();
exit(0);
}

fscanf(graphlist,"%d",&G.vexnum);//读入顶点个数
fscanf(graphlist,"%d",&G.edgenum);//读入边数

for( k=1;k<=G.vexnum;++k) //初始化顶点
{
       fscanf(graphlist,"%s",&G.vexes[k].data);
G.vexes[k].firstedge=NULL;
}

for( k=1;k<=G.edgenum;++k) //从文件读入边构造图
{
       fscanf(graphlist,"%s%s",&va,&vb);

p=(ebox*)malloc(sizeof(ebox));
i=LocateVex(G,va);
p->ivex=i;
j=LocateVex(G,vb);
p->jvex=j;
p->ilink=G.vexes[i].firstedge; // 插在表头
G.vexes[i].firstedge=p;
p->jlink=G.vexes[j].firstedge; // 插在表头
G.vexes[j].firstedge=p;                    
}
    fclose(graphlist); // 关闭数据文件

}

/*寻找V的第一个邻接点W*/
int firstadjvex(amlgraph G,int v)

if(v<0) //G中不存在顶点v
return -1;
if(G.vexes[v].firstedge) //v有邻接顶点
if(G.vexes[v].firstedge->ivex==v)
return G.vexes[v].firstedge->jvex;
else
return G.vexes[v].firstedge->ivex;
else
return -1;
}

/*返回V的下一个邻接点(相对于W)*/
int nextadjvex(amlgraph G,int v,int w)
{

ebox *p;
if(v<0||w<0) // v或w不是G的顶点
return -1;
p=G.vexes[v].firstedge; // p指向顶点v的第1条边
while(p)
if(p->ivex==v&&p->jvex!=w) // 不是邻接顶点w(情况1)
p=p->ilink; // 找下一个邻接顶点
else if(p->jvex==v&&p->ivex!=w) // 不是邻接顶点w(情况2)
p=p->jlink; // 找下一个邻接顶点
else // 是邻接顶点w
break;
if(p&&p->ivex==v&&p->jvex==w) // 找到邻接顶点w(情况1)
{
p=p->ilink;
if(p&&p->ivex==v)
return p->jvex;
else if(p&&p->jvex==v)
return p->ivex;
}
if(p&&p->ivex==w&&p->jvex==v) // 找到邻接顶点w(情况2)
{
p=p->jlink;
if(p&&p->ivex==v)
return p->jvex;
else if(p&&p->jvex==v)
return p->ivex;
}
return -1;
}

/*标志数组*/
int visitedvexes[max_vertex_num+1];

/*访问顶点*/
void visitvex(amlgraph G,int v)
{
printf("%s ",G.vexes[v].data);
}

/*从第V个顶点出发递归地深度优先遍历图*/
void dfs(amlgraph G,int v)
{
int w,p=1;
visitedvexes[v]=1;
visitvex(G,v); //输出顶点的值

for(w=firstadjvex(G,v);w>0;w=nextadjvex(G,v,w))
if(!visitedvexes[w]) //对没有访问过的定点调用dfs
dfs(G,w);
}

/*深度优先遍历图G*/
void dfstraverse(amlgraph G,int v)
{
int i;
for(i=1;i<=G.vexnum;i++)
visitedvexes[i]=0; //访问标志数组元素初始化为0
dfs(G,v);
}

/*队列的链式存储结构*/
typedef struct qnode
{
int    data;
struct qnode *next;
}qnode,*queueptr;

typedef struct
{ queueptr front;
queueptr rear;/*队头、队尾指针*/
}linkqueue;

/*构造空队列*/
status initqueue(linkqueue &q)
{
q.front=q.rear=(queueptr)malloc(sizeof(qnode));
if(!q.front)
exit(0);
q.front->next=NULL;
return OK;
}

/*q为空返回TRUE,否则返回FALSE*/
status queueempty(linkqueue q)
{
if(q.front==q.rear)
return OK;
else
return ERROR;
}

/*插入元素e为q的新队尾元素*/
status enqueue(linkqueue &q,int e)
{
queueptr p=(queueptr)malloc(sizeof(qnode));
if(!p)//存储分配失败
exit(0);
p->data=e;
p->next=NULL;
q.rear->next=p;
q.rear=p;
return OK;
}

/*若队列不空,删除q的队头元素,用e返回其值*/
status dequeue(linkqueue &q)
{ int e;
queueptr p;
if(q.front==q.rear)
return ERROR;
p=q.front->next;
e=p->data;
q.front->next=p->next;
if(q.rear==p)
q.rear=q.front;
free(p);
return e;
}

/*销毁队列Q(无论空否均可)*/
status DestroyQueue(linkqueue &Q)
{
while(Q.front)
{
Q.rear=Q.front->next;
free(Q.front);
Q.front=Q.rear;
}
return OK;


/*广度优先遍历*/
void bfstraverse(amlgraph G,int v)

int i=1,u,w;
linkqueue q;
for(i=1;i<=G.vexnum;i++)
visitedvexes[i]=0;
initqueue(q);//置空的队列

visitedvexes[v]=1; //把v的标志置1
visitvex(G,v); //访问v
enqueue(q,v);//v入队列q
while(!queueempty(q))
{
u=dequeue(q);//队头元素出队并置为u
for(w=firstadjvex(G,u);w>0;w=nextadjvex(G,u,w))
if(!visitedvexes[w])//w为u的尚未访问的邻接顶点
{
visitedvexes[w]=1;
visitvex(G,w);
enqueue(q,w);
} //if
} //while
}

void main()
{
int v;
    char vd[20];
amlgraph G;

creategraph(G);
   printf("交通图创建完毕\n  ");

    printf(" \n\n请输入遍历的起点:  ");
    scanf("%s",&vd);

    v=LocateVex(G,vd);//返回输入顶点的位置

printf("\n\n深度优先遍历: \n");
    dfstraverse(G,v);
    printf("\n\n广度优先遍历:\n");
bfstraverse(G,v);
}

 

 

### 多重的概念 多重是一种允许存在多条边连接同一对顶点的结构。与简单不同的是,在简单中任意两个顶点之间最多只有一条边,而多重允许多条平行边或自环的存在[^1]。 ### 多重数据结构实现 为了支持多重的特性,可以采用以下几种常见的数据结构表示: #### 1. **邻接矩阵** 对于多重而言,传统的布尔型邻接矩阵无法满足需求,因为其仅能记录是否存在一条边。因此,可以通过整数类型的二维数组代替布尔类型,其中每个元素代表对应两点之间的边的数量。如果需要进一步扩展功能,则可以用更复杂的对象替代简单的数值,比如列表或者字典形式存储每条边的具体属性。 ```c++ #include <vector> using namespace std; // 使用 vector<int> 表示多个权重 class MultiGraph { private: int V; vector<vector<int>> adjMatrix; public: MultiGraph(int vertices) : V(vertices), adjMatrix(V, vector<int>(V, 0)) {} void addEdge(int u, int v) { adjMatrix[u][v]++; adjMatrix[v][u]++; } int getNumberOfEdgesBetweenVertices(int u, int v) const { return adjMatrix[u][v]; } }; ``` 上述代码展示了如何通过增加计数器的方式构建一个能够处理多重关系的邻接矩阵类 `MultiGraph`。 #### 2. **邻接表** 相比起邻接矩阵,邻接表更适合稀疏以及动态变化频繁的情况。针对多重的特点,可以在节点对应的链表里重复加入相同的邻居项;或者是保存额外的信息表明某次插入操作所关联的实际物理连线编号或其他特征参数。 ```python from collections import defaultdict class MultiGraphAdjList: def __init__(self): self.graph = defaultdict(list) def add_edge(self, src, dest): # 添加多次相同的目标到源结点上 self.graph[src].append(dest) def edges_between(self, nodeA, nodeB): count = sum(1 for neighbor in self.graph[nodeA] if neighbor == nodeB) return count ``` 这里提供了一个基于 Python 的例子说明怎样利用默认字典创建并维护一个多连通性的邻接表版本 `MultiGraphAdjList`。 ### 关于时间复杂度讨论 当涉及到遍历整个表时,无论是查找还是更新过程都可能面临较高的计算成本。例如,在最坏情况下访问所有潜在链接组合可能会达到平方级增长趋势 \(O(n^2)\),这取决于具体使用的底层表现方式及其优化程度[^2]。 ### 总结 综上所述,虽然标准意义上的单向无权值网络已经足够应对很多场景下的分析任务,但在某些特殊领域如社交互动研究等领域内确实需要用到更加灵活强大的模型即所谓的“多重”。这些高级别的抽象不仅保留了传统拓扑学上的优势同时还引入了一些新的挑战和机遇值得深入探索下去[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值