笔者通过纯C语言实现图的邻接表表示法
(单向有权图)
涉及操作有:
图顶点的 增 删 改 查
图边的 增 删 改 查
打印邻接表
迪杰斯特拉算法 求最短路径(包括中转节点的记录)
部分原理限于篇幅会做介绍,但关键在于自己搜寻资料学习
此博客目的在于分享心得
源代码在文章最后,直接可用
图的原理构造(篇末源代码也是复现如此结构)如下:
注意:此结构进行过改动,会与部分图书有出入。
因为涉及图顶点(节点)的增删改查,故对顶点的结构进行改动,从原来的数组实现改为单链表实现。
基于数组实现的邻接表也可以实现图节点的增删改查,但过于麻烦,笔者没有采用
源代码结构体说明(typedef用法自行百度):
顶点(Vertex)的结构体
包含顶点的名字 name
指向下一顶点的指针域 next_vertex
指向出边的指针域 out
typedef struct vertex
{
char name;
struct vertex *next_vertex;
struct arc *out;
}Vertex;
边(Arc)的结构体
包含边的权重 weight
指向下一条边的指针域 next_arc
指向终点的指针域 adjvex
typedef struct arc
{
int weight;
struct vertex * adjvex;
struct arc *next_arc;
}Arc;
图(Graph)的结构体
包含图的顶点数量 vertex_num
图的边的数量 arc_next
邻接表的头节点 head
typedef struct graph
{
int vertex_num;
int arc_num;
struct vertex * head;
}Graph;
栈的结构体 迪杰斯特拉算法记录中转节点要用到
typedef struct stack
{
int top;
int save[100];
}Stack;
功能函数声明:
Vertex * Creat_Vertex(); // 开辟节点空间并初始化
Arc * Creat_Arc(); // 开辟边空间并初始化
Graph * Creat_Graph(); // 开辟图空间并初始化
Vertex * Serch_Vertex(Graph * g , char name); // 返回指定名字节点的地址 节点不存在则返回空
Arc * Serch_Arc(Graph *g , char beg , char end); // 返回指定名字边的地址 边不存在则返回空
void Change_Vertex(Graph *g , char name,char name_new); // 改变节点的名字
void Change_Arc(Graph *g , char beg , char end , int weight); // 改变边的权重
void Del_Arc(Graph *g ,char beg ,char end ); // 删除边
void Del_Vertex(Graph *g ,char name); // 删除节点(包括于节点有关的所有的边的删除)
void Add_Vertex(Graph *g , char name); // 添加节点
void Add_Arc(Graph *g , char beg , char end , int weight); // 添加边 包括 beg->end 和 end->beg
void Display(Graph *g); // 打印邻接表
部分函数说明:
开辟空间函数
为指定类型的结构体动态分配空间,并初始化
Vertex * Creat_Vertex()
{
Vertex * v = (Vertex*)malloc(sizeof(Vertex));
v->out = NULL; // 指向出边
v->next_vertex = NULL; //指向下一顶点
return v;
}
Arc * Creat_Arc()
{
Arc * a = (Arc*)malloc(sizeof(Arc));
a->weight = 0;
a->adjvex = NULL;
a->next_arc = NULL;
return a;
}
Graph * Creat_Graph()
{
Graph * g = (Graph*)malloc(sizeof(Graph));
g->vertex_num = 0;
g->arc_num = 0;
g->head = NULL;
return g;
}
查找函数的实现
顶点的查找只需要遍历顶点链表即可
若存在返回其所在地址,若不存在返回NULL
而边的查找,则需要起点(beg)和终点(end)
通过 Serch_Vertex函数 查找起点所在的位置,通过遍历起点的所有出边,即可定位到指定的边。
Vertex * Serch_Vertex(Graph * g , char name)
{
Vertex * v = g->head;
for(v=g->head ; v ; v=v->next_vertex)
{
if(v&&v->name==name)
return v;
}
return NULL;
}
Arc * Serch_Arc(Graph *g , char beg , char end)
{
Arc * a=NULL;
Vertex * beg_v, *end_v;
beg_v = Serch_Vertex(g,beg); //获取起点位置
end_v = Serch_Vertex(g,end); // 获取终点位置
if(beg_v&&end_v) // 仅当起点 终点同时存在时继续
{
for(a=beg_v->out ; a ; a=a->next_arc) // 通过起点的出边 查找指向终点 的边
{
if(a->adjvex->name == end)
return a;
}
return NULL;
}
else
{
return NULL;
}
}
节点名称和边权重的修改
修改的操作基于查找就能简单实现
注意
修改A->B的线路长度时 也同时 要修改B->A的线路长度(因为是单向有权图)
void Change_Vertex(Graph *g , char name,char name_new) // name是要修改节点的名称 new_name是修改后新的名称
{
Vertex * v=NULL,*v1=NULL;
v=Serch_Vertex(g,name);
v1=Serch_Vertex(g,name_new);
if( v&& v1==NULL)
{
v->name = name_new;
}
else
{
printf("Pease check repeat or dose it exist!\n");
return ;
}
}
void Change_Arc(Graph *g , char beg , char end , int weight)
{
Arc * a=NULL;
if(a=Serch_Arc(g,beg,end))
{
a->weight = weight;
a = Serch_Arc(g,end,beg);
a->weight = weight;
return ;
}
else
{
printf("arc is not exist!\n");
return ;
}
}
顶点和边的删除
边的删除也要删除A->B 和 B->A的线路
顶点的删除较为麻烦
首先 邻接表中所有指向顶点的边都要删除(通过边的删除实现,即删除终点为顶点的所有边)
其次 要从顶点链表中删除顶点(删除该顶点的同时,所有起点为该顶点的边全部删除)
void Del_Arc(Graph *g ,char beg ,char end ) //删除边也是 A->B 和 B->A都要同时删除
{
Arc * a1=NULL , *a2=NULL;
Vertex * v,*v1;
v = Serch_Vertex(g,beg);
a1 = Serch_Arc(g,beg,end);
if(a1)
{
for(a2=v->out ; a2 ; a1=a2 , a2=a2->next_arc)
{
if(a2->adjvex->name==end)
{
if(a2==v->out) // 头节点特判
{
v->out = a2->next_arc;
free(a2);
}
else
{
a1->next_arc = a2->next_arc;
free(a2);
}
}
}
g->arc_num -= 1;
Del_Arc(g ,end ,beg );
}
else
{
//printf("arc is not exist!\n");
return ;
}
}
void Del_Vertex(Graph *g ,char name) // 删除顶点需要删除与顶点有关的所有边
{
Vertex *v1=NULL,*v2=NULL;
Arc *a=NULL;
int count=0;
for(v1=g->head ; v1 ; v1=v1->next_vertex) // 删除终点为顶点的边 和顶点所有的出边
{
if(v1->name!=name)
Del_Arc(g,v1->name,name);
}
v1=Serch_Vertex(g,name);
if(v1) // 删除起点的顶点(起点的消失,导致所有起点为顶点的边也消失)
{
if(v1==g->head)
{
g->head = v1->next_vertex;
free(v1);
}
else
for(v2=g->head ; v2->next_vertex ; v2=v2->next_vertex)
{
if(v2->next_vertex==v1)
{
v2->next_vertex = v1->next_vertex;
free(v1);
break;
}
}
g->vertex_num -= 1;
}
else
{
printf("vertex not exist!\n");
return ;
}
}
顶点和边的添加
添加实现较为容易,本质就是单链表指定位置的插入
同时注意一些错误机制 类似于重复添加的检查机制
void Add_Vertex(Graph *g , char name) // 基于单链表的头插法实现
{
Vertex * v=NULL;
v = Serch_Vertex(g,name);
if(!v)
{
v = Creat_Vertex();
v->name = name;
v->next_vertex = g->head;
g->head = v;
g->vertex_num += 1; // 顶点数+1
return ;
}
else
{
printf("vertex is repeat !\n");
return ;
}
}
void Add_Arc(Graph *g , char beg , char end , int weight) // 同样两条边都要添加 操作基于顶点的查找函数实现
{
Arc *a=NULL;
Vertex * v=NULL,*v1=NULL;
a = Serch_Arc(g,beg,end);
v = Serch_Vertex(g,beg);
v1 = Serch_Vertex(g,end);
if(!v) // 如果指定顶点不存在 就将其添加至邻接表中
Add_Vertex(g,beg);
if(!v1)
Add_Vertex(g,end);
if(!a)
{
v = Serch_Vertex(g,beg); // 开辟边的空间
v1 = Serch_Vertex(g,end);
a = Creat_Arc(); // 添加 beg->end
a->weight = weight;
a->next_arc = v->out;
v->out = a;
a->adjvex = v1;
a = Creat_Arc(); // 添加end->beg
a->weight = weight;
a->next_arc = v1->out;
v1->out = a;
a->adjvex = v;
g->arc_num += 2;
return ;
}
else
{
printf("arc is exist !");
return ;
}
}
获取权重
int Get_weight(Graph *g ,char beg , char end )
{
if(beg == end)
return 0;
Arc * a = Serch_Arc(g,beg,end);
if(a)
{
return a->weight;
}
else
return INF;
}
迪杰斯特拉算法
迪杰斯特拉算法基于图的邻接矩阵实现(大部分)
笔者这里通过邻接表实现了
所以进行了些许修改,需要用 list 数组记录节点的地址(并记录起点 终点)
实现代码核心不会有太大变化,其中获取两个点的距离用get_weight函数实现
中转节点的记录用save数组记录,但由于是逆序的,所以用栈stack的操作将之顺序
void Dijkstra(Graph *g , char beg , char end )
{
if(!Serch_Vertex(g,beg)||!Serch_Vertex(g,end))
{
printf("Wrong\n");
return ;
}
Vertex ** list = (Vertex**)malloc(g->vertex_num*sizeof(Vertex*));
Vertex * ver;
int index_beg,index_end;
int i,j,k,v,u;
Stack st;
st.top = -1;
for(ver=g->head, i=1 ; i<=g->vertex_num ; i++) // 将节点地址记录到list数组中 并标记起点 终点 在数组的下标
{
list[i] = ver;
if(ver->name==beg) index_beg = i;
if(ver->name==end) index_end = i;
ver = ver->next_vertex;
}
for(i=1; i<=g->vertex_num ;i++)
{
dis[i] = Get_weight(g,beg,list[i]->name); // get_weight 获取起点到终点的长度
mask[i] = 0;
save[i] = -1; // 记录中转城市的save数组初始化
}
//Dijkstra 核心语句
mask[index_beg] = 1; //标记起点
for(i=0 ; i<=g->vertex_num ; i++)
{
int min = INF;
for(j=1 ; j<=g->vertex_num ; j++)
{
if(mask[j]==0 && dis[j]<min)
{
min = dis[j];
u = j;
}
}
mask[u] = 1;
for(v=1 ; v<=g->vertex_num ; v++)
{
if(Get_weight(g,list[u]->name,list[v]->name)<INF)
{
if(dis[v] > dis[u]+Get_weight(g,list[u]->name,list[v]->name))
{
dis[v] = dis[u]+Get_weight(g,list[u]->name,list[v]->name);
save[v] = u;
}
}
}
}
i = index_end;
while(save[i]!=-1)
{
st.top ++;
st.save[st.top] = save[i];
i = save[i];
}
if(dis[index_end]==INF)
printf("没有联通的路\n");
else
{
printf("经计算 %c->%c 最短路径为:%d \n",beg,end,dis[index_end]);
printf("路径为:\n");
printf("%c->",list[index_beg]->name); //打印起点
while(st.top != -1)
{
printf("%c->",list[st.save[st.top]]->name); // 打印中转城市
st.top -= 1;
}
printf("%c",list[index_end]->name); //打印终点
printf("\n");
printf("\n");
}
}
邻接表的打印
打印邻接表
void Display(Graph *g)
{
Vertex *c = g->head;
for(c ; c ; c=c->next_vertex)
{
printf("%c",c->name);
if(c->out)
{ printf("-->");
Arc *p = c->out;
for(p ; p ;p=p->next_arc)
{
printf("%c",p->adjvex->name);
if(p->next_arc)
printf("-->");
}
printf("\n");
}
else
printf("\n");
}
printf("Vertex_num :%d Arc_num :%d\n",g->vertex_num,g->arc_num);
}
测试用main函数
int main()
{
Graph *g = Creat_Graph();
int i,j;
char s[]={"ABCDEFG"};
for(i=0 ; i<7 ; i++)
{
Add_Vertex(g,s[i]);
}
// 插入边
Add_Arc(g,'A','B',12);
Add_Arc(g,'A','R',19);
Add_Arc(g,'C','F',8);
Add_Arc(g,'B','R',3);
// 删除节点 A
//Del_Vertex(g,'A');
Dijkstra(g,'A','R'); // A 到 R 的最短路
Display(g);
return 0;
}
源代码:
#include<stdio.h>
#include<stdlib.h>
#define INF 99999
int dis[100]={0};
int mask[100]={0};
int save[100]={0};
typedef struct vertex
{
char name;
struct vertex *next_vertex;
struct arc *out;
}Vertex;
typedef struct arc
{
int weight;
struct vertex * adjvex;
struct arc *next_arc;
}Arc;
typedef struct graph
{
int vertex_num;
int arc_num;
struct vertex * head;
}Graph;
typedef struct stack
{
int top;
int save[100];
}Stack;
Vertex * Creat_Vertex(); // 开辟节点空间并初始化
Arc * Creat_Arc(); // 开辟边空间并初始化
Graph * Creat_Graph(); // 开辟图空间并初始化
Vertex * Serch_Vertex(Graph * g , char name); // 返回指定名字节点的地址 节点不存在则返回空
Arc * Serch_Arc(Graph *g , char beg , char end); // 返回指定名字边的地址 边不存在则返回空
void Change_Vertex(Graph *g , char name,char name_new); // 改变节点的名字
void Change_Arc(Graph *g , char beg , char end , int weight); // 改变边的权重
void Del_Arc(Graph *g ,char beg ,char end ); // 删除边
void Del_Vertex(Graph *g ,char name); // 删除节点(包括于节点有关的所有的边的删除)
void Add_Vertex(Graph *g , char name); // 添加节点
void Add_Arc(Graph *g , char beg , char end , int weight); // 添加边 包括 beg->end 和 end->beg
int Get_weight(Graph *g , char beg , char end); // 获取边的权值
void Dijkstra(Graph *g , char beg,char end); // 迪杰斯特拉最短路算法 包括打印路径
void Display(Graph *g); // 打印邻接表
Vertex * Creat_Vertex()
{
Vertex * v = (Vertex*)malloc(sizeof(Vertex));
v->out = NULL;
v->next_vertex = NULL;
return v;
}
Arc * Creat_Arc()
{
Arc * a = (Arc*)malloc(sizeof(Arc));
a->weight = 0;
a->adjvex = NULL;
a->next_arc = NULL;
return a;
}
Graph * Creat_Graph()
{
Graph * g = (Graph*)malloc(sizeof(Graph));
g->vertex_num = 0;
g->arc_num = 0;
g->head = NULL;
return g;
}
Vertex * Serch_Vertex(Graph * g , char name)
{
Vertex * v = g->head;
for(v=g->head ; v ; v=v->next_vertex)
{
if(v&&v->name==name)
return v;
}
return NULL;
}
Arc * Serch_Arc(Graph *g , char beg , char end)
{
Arc * a=NULL;
Vertex * beg_v, *end_v;
beg_v = Serch_Vertex(g,beg);
end_v = Serch_Vertex(g,end);
if(beg_v&&end_v)
{
for(a=beg_v->out ; a ; a=a->next_arc)
{
if(a->adjvex->name == end)
return a;
}
return NULL;
}
else
{
return NULL;
}
}
void Change_Vertex(Graph *g , char name,char name_new)
{
Vertex * v=NULL,*v1=NULL;
v=Serch_Vertex(g,name);
v1=Serch_Vertex(g,name_new);
if( v&& v1==NULL)
{
v->name = name_new;
}
else
{
printf("Pease check repeat or dose it exist!\n");
return ;
}
}
void Change_Arc(Graph *g , char beg , char end , int weight)
{
Arc * a=NULL;
if(a=Serch_Arc(g,beg,end))
{
a->weight = weight;
a = Serch_Arc(g,end,beg);
a->weight = weight;
return ;
}
else
{
printf("arc is not exist!\n");
return ;
}
}
void Del_Arc(Graph *g ,char beg ,char end ) //删除边也是 A->B 和 B->A都要同时删除
{
Arc * a1=NULL , *a2=NULL;
Vertex * v,*v1;
v = Serch_Vertex(g,beg);
a1 = Serch_Arc(g,beg,end);
if(a1)
{
for(a2=v->out ; a2 ; a1=a2 , a2=a2->next_arc)
{
if(a2->adjvex->name==end)
{
if(a2==v->out) // 头节点特判
{
v->out = a2->next_arc;
free(a2);
}
else
{
a1->next_arc = a2->next_arc;
free(a2);
}
}
}
g->arc_num -= 1;
Del_Arc(g ,end ,beg );
}
else
{
//printf("arc is not exist!\n");
return ;
}
}
void Del_Vertex(Graph *g ,char name) // 删除顶点需要删除与顶点有关的所有边
{
Vertex *v1=NULL,*v2=NULL;
Arc *a=NULL;
int count=0;
for(v1=g->head ; v1 ; v1=v1->next_vertex) // 删除终点为顶点的边 和顶点所有的出边
{
if(v1->name!=name)
Del_Arc(g,v1->name,name);
}
v1=Serch_Vertex(g,name);
if(v1) // 删除起点的顶点(起点的消失,导致所有起点为顶点的边也消失)
{
if(v1==g->head)
{
g->head = v1->next_vertex;
free(v1);
}
else
for(v2=g->head ; v2->next_vertex ; v2=v2->next_vertex)
{
if(v2->next_vertex==v1)
{
v2->next_vertex = v1->next_vertex;
free(v1);
break;
}
}
g->vertex_num -= 1;
}
else
{
printf("vertex not exist!\n");
return ;
}
}
void Add_Vertex(Graph *g , char name) // 基于单链表的头插法实现
{
Vertex * v=NULL;
v = Serch_Vertex(g,name);
if(!v)
{
v = Creat_Vertex();
v->name = name;
v->next_vertex = g->head;
g->head = v;
g->vertex_num += 1; // 顶点数+1
return ;
}
else
{
printf("vertex is repeat !\n");
return ;
}
}
void Add_Arc(Graph *g , char beg , char end , int weight) // 同样两条边都要添加 操作基于顶点的查找函数实现
{
Arc *a=NULL;
Vertex * v=NULL,*v1=NULL;
a = Serch_Arc(g,beg,end);
v = Serch_Vertex(g,beg);
v1 = Serch_Vertex(g,end);
if(!v) // 如果指定顶点不存在 就将其添加至邻接表中
Add_Vertex(g,beg);
if(!v1)
Add_Vertex(g,end);
if(!a)
{
v = Serch_Vertex(g,beg); // 开辟边的空间
v1 = Serch_Vertex(g,end);
a = Creat_Arc(); // 添加 beg->end
a->weight = weight;
a->next_arc = v->out;
v->out = a;
a->adjvex = v1;
a = Creat_Arc(); // 添加end->beg
a->weight = weight;
a->next_arc = v1->out;
v1->out = a;
a->adjvex = v;
g->arc_num += 2;
return ;
}
else
{
printf("arc is exist !");
return ;
}
}
void Display(Graph *g)
{
Vertex *c = g->head;
for(c ; c ; c=c->next_vertex)
{
printf("%c",c->name);
if(c->out)
{ printf("-->");
Arc *p = c->out;
for(p ; p ;p=p->next_arc)
{
printf("%c",p->adjvex->name);
if(p->next_arc)
printf("-->");
}
printf("\n");
}
else
printf("\n");
}
printf("Vertex_num :%d Arc_num :%d\n",g->vertex_num,g->arc_num);
}
int Get_weight(Graph *g ,char beg , char end )
{
if(beg == end)
return 0;
Arc * a = Serch_Arc(g,beg,end);
if(a)
{
return a->weight;
}
else
return INF;
}
void Dijkstra(Graph *g , char beg , char end )
{
if(!Serch_Vertex(g,beg)||!Serch_Vertex(g,end))
{
printf("Wrong\n");
return ;
}
Vertex ** list = (Vertex**)malloc(g->vertex_num*sizeof(Vertex*));
Vertex * ver;
int index_beg,index_end;
int i,j,k,v,u;
Stack st;
st.top = -1;
for(ver=g->head, i=1 ; i<=g->vertex_num ; i++) // 将节点地址记录到list数组中 并标记起点 终点 在数组的下标
{
list[i] = ver;
if(ver->name==beg) index_beg = i;
if(ver->name==end) index_end = i;
ver = ver->next_vertex;
}
for(i=1; i<=g->vertex_num ;i++)
{
dis[i] = Get_weight(g,beg,list[i]->name); // get_weight 获取起点到终点的长度
mask[i] = 0;
save[i] = -1; // 记录中转城市的save数组初始化
}
//Dijkstra 核心语句
mask[index_beg] = 1; //标记起点
for(i=0 ; i<=g->vertex_num ; i++)
{
int min = INF;
for(j=1 ; j<=g->vertex_num ; j++)
{
if(mask[j]==0 && dis[j]<min)
{
min = dis[j];
u = j;
}
}
mask[u] = 1;
for(v=1 ; v<=g->vertex_num ; v++)
{
if(Get_weight(g,list[u]->name,list[v]->name)<INF)
{
if(dis[v] > dis[u]+Get_weight(g,list[u]->name,list[v]->name))
{
dis[v] = dis[u]+Get_weight(g,list[u]->name,list[v]->name);
save[v] = u;
}
}
}
}
i = index_end;
while(save[i]!=-1)
{
st.top ++;
st.save[st.top] = save[i];
i = save[i];
}
if(dis[index_end]==INF)
printf("没有联通的路\n");
else
{
printf("经计算 %c->%c 最短路径为:%d \n",beg,end,dis[index_end]);
printf("路径为:\n");
printf("%c->",list[index_beg]->name); //打印起点
while(st.top != -1)
{
printf("%c->",list[st.save[st.top]]->name); // 打印中转城市
st.top -= 1;
}
printf("%c",list[index_end]->name); //打印终点
printf("\n");
printf("\n");
}
}
int main()
{
Graph *g = Creat_Graph();
int i,j;
char s[]={"ABCDEFG"};
for(i=0 ; i<7 ; i++)
{
Add_Vertex(g,s[i]);
}
// 插入边
Add_Arc(g,'A','B',12);
Add_Arc(g,'A','R',19);
Add_Arc(g,'C','F',8);
Add_Arc(g,'B','R',3);
// 删除节点 A
//Del_Vertex(g,'A');
Dijkstra(g,'A','R'); // A 到 R 的最短路
Display(g);
return 0;
}
运行截图:
笔者后续会更新 DFS BFS 图的邻接表的算法实现
谢谢观看~