C语言|图的邻接表实现
本来想写一篇完整的文章的,但编着编着发现好多示例都没有……身边也没有什么参考资料,细讲还要花图可能还画得不好看。总之直接贴代码了,vs2012可运行、Dev c++可运行,非100%原创,一半代码都是现成的,不过我还是全都理解了,如果有疑问请在评论区留言
代码如下
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_NAME 3 // 顶点字符串的最大长度+1
#define MAX_VERTEX_NUM 20
typedef char infochar[2];
typedef int InfoType; // 存放网的权值
typedef char VertexType[MAX_NAME]; // 字符串类型
int visited[MAX_VERTEX_NUM]; // 訪问标志数组
void(*VisitFunc)(char* v); // 定义一个函数指针变量
typedef struct ArcNode
{
int adjvex;
struct ArcNode *nextarc;
InfoType *info;
}ArcNode; // 表结点
typedef struct VNode
{
VertexType data;
char weight; //为顶点添加一个存放权重的数据域
ArcNode *firstarc;
}VNode, AdjList[MAX_VERTEX_NUM];
typedef struct
{
AdjList vertices;
int vexnum, arcnum;
int kind;
}ALGraph;
typedef int QElemType;//队列类型
typedef struct QNode
{//队列为链队
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct
{
QueuePtr front,rear;//队头指针,指针域指向队头元素。队尾指针,指向队尾元素
}LinkQueue;
// 若 G 中存在顶点 u,则返回该顶点在图中位置;否则返回-1。
int LocateVex(ALGraph &G,VertexType u)
{
int i;
for(i=0;i<G.vexnum;++i)
if(strcmp(u,G.vertices[i].data)==0) //返回值 < 0,前者小于后者。返回值 > 0,前者大于后者。返回值 = 0,前者等于后者,头文件:string.h
return i;
return -1;
}
void CreateGraph(ALGraph &G)
{
int i,j,k;
int weight;
VertexType va,vb;
ArcNode *p;
printf("请输入图的类型(有向图:0, 有向网:1, 无向图:2, 无向网:3): \n");
scanf("%d",&G.kind);
printf("请输入顶点数和弧数:\n");
scanf("%d %d",&G.vexnum,&G.arcnum);
printf("请输入各顶点的值:\n");
for(i=0;i<G.vexnum;i++)
{//建立头结点,并为指针域赋初值
scanf("%s",&G.vertices[i].data);
G.vertices[i].weight='\0'; //给权值数据域赋空值,以初始化
G.vertices[i].firstarc=NULL;
}
if(G.kind==1 || G.kind==3) //网,多输入权值
printf("请分别输入权值、弧头和弧尾(或边):\n");
else //图,只用输入弧头和弧尾
printf("请输入弧头和弧尾(或边):\n");
for(k=0;k<G.arcnum;k++)
{
if(G.kind==1 || G.kind==3) //网多输入权值
scanf("%d %s%s",&weight,va,vb);
else //图只输入弧头和弧尾
scanf("%s%s",va,vb);
i=LocateVex(G,va); //弧头
j=LocateVex(G,vb); //弧尾
p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=j;
if(G.kind==1 || G.kind==3) //对于网多赋权值
{
p->info = (int *)malloc(sizeof(int)); //为权重数据域动态分配存储空间
*(p->info) =weight;
}
else //对于图,不赋权值,初始化info数据域
p->info=NULL;
p->nextarc=G.vertices[i].firstarc;//当建立的链表是第一个,则赋初值NULL,并由i指向表结点
G.vertices[i].firstarc=p;
if(G.kind==2 || G.kind==3) //对于无向图/网,再建立一条对应的边
{
p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=i;
if(G.kind==3) //对于无向网,还要给对应边赋权值
{
p->info = (int *)malloc(sizeof(int)); //为权重数据域动态分配存储空间
*(p->info) =weight;
}
else//对于无向图,不赋权值,初始化info数据域
p->info=NULL;
p->nextarc=G.vertices[j].firstarc;
G.vertices[j].firstarc=p;
}
}
}
void DestroyGraph(ALGraph &G)
{//销毁图
int i;
ArcNode *p,*q;
for(i=0;i<G.vexnum;++i)
{
p= G.vertices[i].firstarc;
while(p)
{//释放顶点i的所有链表
q=p->nextarc;
if(G.kind%2) // 网
free(p->info);
free(p);
p=q;
}
G.vertices[i].firstarc=NULL;
}
G.vexnum=0;
G.arcnum=0;
}
VertexType* GetVex(ALGraph G,int v)
{//辅助型函数。返回序号v的值
VertexType *temp;
if(v>=G.vexnum||v<0)
exit(0);
else
{
temp=&G.vertices[v].data;
return temp;
}
}
int PutVex(ALGraph &G,VertexType v, VertexType value)
{//辅助型函数。对序号为v的顶点赋新的值value
int i;
i=LocateVex(G,v);
if(i> -1)
{
strcpy(G.vertices[i].data,value);
return 1;
}
return 0;
}
int FirstAdjVex(ALGraph G,VertexType v)
{//辅助型函数。返回 v 的第一个邻接顶点的序号。若顶点在 G 中没有邻接顶点,则返回-1。
ArcNode *p;
int v1;
v1= LocateVex(G,v);
p=G.vertices[v1].firstarc;
if(p)
return p->adjvex;
else
return -1;
}
int NextAdjVex(ALGraph G,VertexType v, VertexType w)
{//辅助型函数。返回 v 的(相对于 w 的)下一个邻接顶点的序号。若 w 是 v 的最后一个邻接点, 则返回-1。
ArcNode *p;
int v1,w1;
v1=LocateVex(G,v);
w1=LocateVex(G,w);
p=G.vertices[v1].firstarc;
while(p&&p->adjvex!=w1)
p=p->nextarc;
if(!p||!p->nextarc)
return -1;
else
return p->nextarc->adjvex;
}
void InsertVex(ALGraph &G,VertexType v)
{//功能型函数。在图 G 中增添新顶点 v(不增添与顶点相关的弧,留待 InsertArc()去做)。
strcpy(G.vertices[G.vexnum].data,v);
G.vertices[G.vexnum].firstarc=NULL;
G.vexnum++;
}
int DeleteVex(ALGraph &G,VertexType v)
{// 删除 G 中顶点 v 及其相关的弧/边。
int i,j;
ArcNode *p,*q;
j=LocateVex(G,v);//返回v在图中的顶点序号
if(j<0)
{
printf("%s不是图的顶点\n",v);
return 0;
}
p=G.vertices[j].firstarc;//p指向v顶点的相关弧/边,删除这些弧/边
while(p)
{
q=p;
p=p->nextarc;
if(G.kind%2)//对于网,额外释放权值指针域
free(q->info);
free(q);
G.arcnum--;//弧/边减一
}
G.vexnum--;//顶点数减一
for(i=j;i<G.vexnum;i++)
G.vertices[i]=G.vertices[i+1];
for(i=0;i<G.vexnum;i++)
{
p=G.vertices[i].firstarc;
while(p)
{
if(p->adjvex==j)//是以 v 为入度的边
{
if(p==G.vertices[i].firstarc)
{
G.vertices[i].firstarc=p->nextarc;
if(G.kind % 2)//对于网,额外释放权值指针域
free(p->info);
free(p);
p=G.vertices[i].firstarc;
if(G.kind<2)//弧/边减一
G.arcnum--;
}
else
{
q->nextarc=p->nextarc;
if(G.kind%2)//对于网,额外释放权值指针域
free(p->info);
free(p);
p = q->nextarc;
if(G.kind < 2)//对于有向图/网,弧/边减一
G.arcnum--;
}
}
else
{
if(p->adjvex>j)
p->adjvex--;// 改动表结点的顶点位置值(序号)
q=p;
p=p->nextarc;
}
}
}
return 1;
}
int InsertArc(ALGraph &G,VertexType v,VertexType w)
{// 在 G 中增添弧<v,w>,若 G 是无向的,则还增添对称弧<w,v>。
ArcNode *p;
int w1,i,j;
i=LocateVex(G,v); //获取弧尾/边的序号
j=LocateVex(G,w); //获取弧头/边的序号
if(i<0||j<0)
return 0;
G.arcnum++; //图 G 的弧/边加一
if(G.kind%2) //对于网,还要输入权值
{
printf("请输入弧(边)%s→%s的权值:",v,w);
scanf("%d",&w1);
}
p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=j;
if(G.kind%2)
{ //对于网,还要赋权值
p->info=(int*)malloc(sizeof(int));
*(p->info)=w1;
}
else
p->info=NULL;
p->nextarc=G.vertices[i].firstarc;//将节点插在表头,若为第一个结点,则为初始化nextarc数据域
G.vertices[i].firstarc=p;
if(G.kind>=2) //对于无向图/网,生成对于的边
{
p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=i;
if(G.kind==3)
{//对于无向网,额外赋权值
p->info=(int*)malloc(sizeof(int));
*(p->info)=w1;
}
else
p->info=NULL;
p->nextarc=G.vertices[j].firstarc; //将节点插在表头,若为第一个结点,则为初始化nextarc数据域
G.vertices[j].firstarc=p;
}
return 1;
}
// 在 G 中删除弧<v,w>,若 G 是无向的,则还删除对称弧<w,v>。
int DeleteArc(ALGraph &G,VertexType v,VertexType w)
{
ArcNode *p,*q;
int i,j;
i = LocateVex(G,v); // i 是顶点 v(弧尾)的序号
j = LocateVex(G,w); // j 是顶点 w(弧头)的序号
if(i < 0 || j < 0 || i == j)
return 0;
p=G.vertices[i].firstarc; // p 指向顶点 v 的第一条出弧
while(p&&p->adjvex!=j) // p 不空且所指之弧不是待删除弧<v,w>
{ // p 指向下一条弧
q=p; p=p->nextarc;
}
if(p&&p->adjvex==j) // 找到弧<v,w>
{
if(p==G.vertices[i].firstarc) // p 所指是第 1 条弧
G.vertices[i].firstarc=p->nextarc; // 指向下一条弧
else
q->nextarc=p->nextarc; // 指向下一条弧
if(G.kind%2) // 网
free(p->info);
free(p); // 释放此结点
G.arcnum--; // 弧或边数减 1
}
if(G.kind>=2) // 无向,删除对称弧<w,v>
{
p=G.vertices[j].firstarc; // p 指j的第一条出弧
while(p&&p->adjvex!=i) // p 不空且所指之弧不是待删除弧<w,v>
{ // p 指向下一条弧
q=p;
p=p->nextarc;
}
if(p&&p->adjvex==i) // 找到弧<w,v>
{
if(p==G.vertices[j].firstarc) // p 所指是第 1 条弧
G.vertices[j].firstarc=p->nextarc; // 指向下一条弧
else
q->nextarc=p->nextarc; // 指向下一条弧
if(G.kind%2) // 网
free(p->info);
free(p); // 释放此结点
}
}
return 1;
}
// 从第 v 个顶点出发递归地深度优先遍历图 G。
void DFS(ALGraph G,int v)
{
int w;
VertexType v1,w1;
strcpy(v1,*GetVex(G,v));
visited[v] = 1; // 设置訪问标志为 1(已訪问)
VisitFunc(G.vertices[v].data); // 訪问第 v 个顶点
for(w = FirstAdjVex(G,v1); w >= 0;
w = NextAdjVex(G,v1,strcpy(w1,*GetVex(G,w))))
if(!visited[w])
DFS(G,w);// 对 v 的尚未訪问的邻接点 w 递归调用 DFS
}
// 对图 G 作深度优先遍历。深度优先搜索DFS。类似于树的先序遍历。假设图中所有顶点未被访问,则深度优先遍历是从某个顶点v开始,访问完后,接着访问v的未被访问的邻接点,直至遍历完。
void DFSTraverse(ALGraph G,void(*Visit)(char*))
{
int v;
// 使用全局变量 VisitFunc,使 DFS 不必设函数指针參数
VisitFunc = Visit;
for(v = 0; v < G.vexnum; v++)
visited[v] = 0; // 訪问标志数组初始化
for(v = 0; v < G.vexnum; v++)
if(!visited[v])
DFS(G,v); // 对尚未訪问的顶点调用 DFS
printf("\n");
}
// 构造一个空队列 Q。
int InitQueue(LinkQueue *Q)
{
(*Q).front = (*Q).rear = (QueuePtr)malloc(sizeof(QNode)); //动态分配一个空间
if(!(*Q).front)
exit(0);
(*Q).front->next = NULL;//队头指针指向空。无数据域,这样构成了一个空队列
return 1;
}
// 插入元素 e 为 Q 的新的队尾元素。
int EnQueue(LinkQueue *Q, QElemType e)
{
QueuePtr p =(QueuePtr)malloc(sizeof(QNode));
if( !p ) // 存储分配失败
exit(0);
// 生成一个以为 e 为数据域的队列元素
p->data = e;
p->next = NULL;
//将该新队列元素接在队尾的后面
(*Q).rear->next = p;
(*Q).rear = p;
return 1;
}
// 若队列不空,删除 Q 的队头元素,用 e 返回其值,并返回 1,否则返回 0。
int DeQueue(LinkQueue *Q,QElemType *e)
{
QueuePtr p;
if((*Q).front==(*Q).rear)
return 0;
p=(*Q).front->next;//队头元素
*e=p->data;
(*Q).front->next=p->next;
if((*Q).rear==p)
(*Q).rear=(*Q).front;
free(p);
return 1;
}
// 若 Q 为空队列,则返回 1,否则返回 0。
int QueueEmpty(LinkQueue Q)
{
if(Q.front == Q.rear)
return 1;
else
return 0;
}
//按广度优先遍历图 G。使用辅助队列 Q 和訪问标志数组 visited。广度优先搜索BFS。类似于树的层次遍历。假设图中所有顶点未被访问,则广度优先遍历是从某个顶点v开始,访问后,接着访问v的未被访问的邻接点,然后分别从这些邻接点开始依次访问它们的邻接点(因此用到了队列),直至遍历完。
void BFSTraverse(ALGraph G,void(*Visit)(char*))
{
int v,u,w;
VertexType u1,w1;
LinkQueue Q;
for(v = 0; v < G.vexnum; ++v)
visited[v]=0; // 置初值
InitQueue(&Q); // 置空的辅助队列 Q
for(v = 0; v < G.vexnum; v++) // 假设是连通图,仅仅 v=0 就遍历全图
if(!visited[v]) // v 尚未訪问
{
visited[v]=1;
Visit(G.vertices[v].data);
EnQueue(&Q,v); // v 入队列
while(!QueueEmpty(Q)) // 队列不空
{
DeQueue(&Q,&u); // 队头元素出队并置为 u
strcpy(u1,*GetVex(G,u));
for(w = FirstAdjVex(G,u1); w >= 0; w = NextAdjVex(G, u1, strcpy(w1, *GetVex(G,w))))
if(!visited[w]) // w 为 u 的尚未訪问的邻接顶点
{
visited[w] = 1;
Visit(G.vertices[w].data);
EnQueue(&Q,w); // w 入队
}
}
}
printf("\n");
}
void Display(ALGraph &G)
{ //输出图
int i;
ArcNode *p;
switch(G.kind)
{
case 0: printf("有向图\n");
break;
case 1: printf("有向网\n");
break;
case 2: printf("无向图\n");
break;
case 3: printf("无向网\n");
}
printf("%d 个顶点:\n",G.vexnum);
for(i = 0; i < G.vexnum; ++i)
printf("%s ",G.vertices[i].data);
printf("\n");
printf("各顶点赋权值为:\n");
for(i=0;i<G.vexnum;i++)
{
printf("%c ",G.vertices[i].weight);
}
printf("\n%d 条弧(边):\n", G.arcnum);
for(i = 0; i < G.vexnum; i++)
{
p = G.vertices[i].firstarc;
while(p)
{
if(G.kind <= 1) // 有向
{
printf("%s→%s ",G.vertices[i].data,
G.vertices[p->adjvex].data);
if(G.kind==1) // 有向网
printf(":%d ",*(p->info));
}
else // 无向(避免输出两次)
{
if(i < p->adjvex)
{
printf("%s-%s ",G.vertices[i].data,
G.vertices[p->adjvex].data);
if(G.kind == 3) // 无向网
printf(":%d ",*(p->info));
}
}
p=p->nextarc;
}
printf("\n");
}
}
void swith(ALGraph&G)
{//将图改成网
int i,j,k;
int weight; // 权值
infochar n;
VertexType v1,w1;
ArcNode *q,*p;
printf("为顶点赋权值:\n");
for(i=0;i<G.vexnum;i++)
{
scanf(" %c",&G.vertices[i].weight);
}
if(G.kind==2) //无向图改为无向网
{
G.kind=3;
printf("请输入权值与对应的边:\n");
}
else //有向图改为有向网
{
G.kind=1;
printf("请输入权值与对应的顶点(弧头和弧尾):\n");
}
for(k=0;k<G.arcnum;k++)
{
scanf("%d %s%s",&weight,v1,w1);
i=LocateVex(G,v1);
j=LocateVex(G,w1);
q=G.vertices[i].firstarc;
while(q&&q->adjvex!=j) //若顶点表头不为空,且不是所找结点,则q指向下一个结点
q=q->nextarc;
while(!q) //q为空,说明没找到所找结点,要求重新输入,进入循环
{
if(G.kind==3)//没找到正在转换的无向图的边
printf("没有找到边%s-%s,请重新输入!\n",v1,w1);
else//没找到正在转换的有向图的弧
printf("没有找到弧%s->%s,请重新输入!\n",v1,w1);
scanf("%s%s",v1,w1);
i=LocateVex(G,v1);
j=LocateVex(G,w1);
q=G.vertices[i].firstarc;
while(q&&q->adjvex!=j) //若顶点表头不为空,且不是所找结点,则q指向下一个结点
q=q->nextarc;
if(q) //q不为空,说明找到结点,退出循环
break;
}
if(i!=j)
{
q->info = (int *)malloc(sizeof(int)); //为权重数据域动态分配存储空间
*(q->info) =weight;
}
if(i==j)
{
q->info = (int *)malloc(sizeof(int));
*(q->info)=0;
}
if(G.kind==3)
{
p=G.vertices[j].firstarc;
while(p&&p->adjvex!=i)
p=p->nextarc;
p->info = (int *)malloc(sizeof(int)); //为权重数据域动态分配存储空间
*(p->info) =weight;
}
}
}
void print(char *i)
{
printf("%s ",i);
}
int main()
{
ALGraph g;
ArcNode *p;
int select;
int n,k,j;
VertexType v1,v2;
CreateGraph(g);
Display(g);
do
{
printf("1:在图 G 中增添新顶点 v \n");
printf("2:删除 G 中顶点 v 及其相关的弧\n");
printf("3:在 G 中增添弧<v,w>,若 G 是无向的,则还增添对称弧<w,v> \n");
printf("4:在 G 中删除弧<v,w>,若 G 是无向的,则还删除对称弧<w,v> \n");
printf("5:深度优先遍历图 G \n");
printf("6:广度优先遍历图 G \n");
printf("7:将图转换为网 \n");
printf("8:输出图 G \n");
printf("0:退出 \n");
scanf("%d",&select);
switch(select)
{
case 1:printf("插入新顶点。请输入顶点的值: ");
scanf("%s",v1);
InsertVex(g,v1);
break;
case 2:printf("删除顶点及相关的弧或边,请输入顶点的值: ");
scanf("%s",v1);
DeleteVex(g,v1);
break;
case 3:printf("插入与新的弧或边,请先输入弧或边数目: ");
scanf("%d",&n);
for(k=0;k<n;k++)
{
printf("请输入弧或边的值: ");
scanf("%s%s",v1,v2);
printf("对于有向图,请输入v2的方向(0:弧头 1:弧尾): ");//对于无向图,那边做弧头和弧尾都不影响
scanf("%d",&j);
if(j)//v2是弧尾
InsertArc(g,v2,v1);
else//v2是弧头
InsertArc(g,v1,v2);
}
break;
case 4:printf("删除一条边或弧,请输入待删除边或弧的弧尾,弧头:\n");
scanf("%s%s",v1,v2);
DeleteArc(g,v1,v2);
break;
case 5: printf("深度优先搜索的结果:\n");
DFSTraverse(g,print);
printf("\n");
break;
case 6: printf("广度优先搜索的结果:\n");
BFSTraverse(g,print);
printf("\n");
break;
case 7:swith(g);
break;
case 8:Display(g);
break;
case 0:printf("操作结束!\n");
break;
default:printf("输入选择出错!\n");
}
}while(select!=0);
DestroyGraph(g); //销毁图
printf("\n");
return 0;
}