C语言|图的邻接表实现

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


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值