C语言-图的存储结构(邻接矩阵)

  1. 查找函数(LocateVex查找坐标)
  2. 构建有向图(Directed Graph)
  3. 构建无向图(Undirected Graph)
  4. 构建有向网(Directed Network)
  5. 构建无向网(Undirected Network)
  6. 图的类型选择函数(GraphChoice)
  7. 输出邻接矩阵(print)

有向图、无向图、有向网、无向网的区别

  1. 有向无向的区别:有向的边是单向的,而无向的边是双向的,所以无向图与无向网在矩阵中表示有边的单元是关于主对角线对称的
  2. 的区别:网就是在图的基础上加上了路径的权值w,而此处的权值w的含义是通过此路径所需要付出的代价。比如“A->B的权值为5”,表示由A到B所需要付出的代价是5。(图中的路径权值与树中的权值含义不同)
    在这里插入图片描述

构建邻接矩阵的基本步骤:

  1. 输入顶点数n,边数e
  2. 给顶点数组Vertex[ ]填值
    在这里插入图片描述
  3. 初始化矩阵:
    3.1 若是有向图/无向图,初始化成0
    3.2 若是有向网/无向网,初始化成MaxInt(表示∞)
  4. 输入边(弧)的信息,对应矩阵写入数据

邻接矩阵空间复杂度及适用范围:

  • 有n个顶点就要开辟 n2个空间,故空间复杂度为0(n2);
  • 邻接矩阵适用于存储稠密图,如果用于存储稀疏图会造成巨大的空间浪费,存储稀疏图我们可以使用——邻接表

完整源代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define VertexMax 100 //最大顶点数为100
#define MaxInt 32767 //表示最大整数,表示 ∞

//typedef enum{DG,UDG,DN,UDN}GraphKind;
typedef char VertexType; //每个顶点数据类型为字符型

typedef struct
{
	VertexType Vertex[VertexMax];//存放顶点元素的一维数组
	int AdjMatrix[VertexMax][VertexMax];//邻接矩阵二维数组 
	int vexnum,arcnum;//图的顶点数和边数
	int kind;//当前图的类型(1有向图DG,2无向图UDG,3有向网DN,4无向网UDN) 
}MGraph;

int LocateVex(MGraph *G,VertexType v)//查找元素v在一维数组 Vertex[] 中的下标,并返回下标 
{
	int i;
	
	for(i=0;i<G->vexnum;i++)
	{
		if(v==G->Vertex[i])
		{
			return i; 
		} 
	 } 
	 
	 printf("No Such Vertex!\n");
	 return -1;
}

//1.构建有向图(Directed Graph)
void CreateDG(MGraph *G) 
{
	int i,j;
	//1.输入顶点数和边数
	printf("输入顶点个数和边数(用逗号隔开):"); 
	scanf("%d,%d",&G->vexnum,&G->arcnum);
	printf("\n");
	
	//2.输入顶点元素 
	printf("输入顶点元素(无需空格隔开):");
	scanf("%s",G->Vertex);
	printf("\n");
	//3.矩阵初始化
	for(i=0;i<G->vexnum;i++) 
	 for(j=0;j<G->vexnum;j++) 
	    {
	    	G->AdjMatrix[i][j]=0;
		}
	
	 //4.构建邻接矩阵
	 int n,m;
	 VertexType v1,v2;
	 
	 printf("请输入边的信息:\n");
	 for(i=0;i<G->arcnum;i++)
	 {
	 	scanf(" %c%c",&v1,&v2);
	 	n=LocateVex(G,v1); //获取v1所对应的在Vertex数组中的坐标 
	 	m=LocateVex(G,v2); //获取v2所对应的在Vertex数组中的坐标
	 	
	 	if(n==-1||m==-1)
		 {
		 	printf("NO This Vertex!\n");
		 	return;
		  } 
	
	   G->AdjMatrix[n][m]=1;
	 } 
	 G->kind=1;//表示构建的图类型为有向图DG 
}

//2.构建无向图(Undirected Graph)
void CreateUDG(MGraph *G) 
{
	int i,j;
	//1.输入顶点数和边数
	printf("输入顶点个数和边数(用逗号隔开):"); 
	scanf("%d,%d",&G->vexnum,&G->arcnum);
	printf("\n");
	
	//2.输入顶点元素 
	printf("输入顶点元素(无需空格隔开):");
	scanf("%s",G->Vertex);
	printf("\n");
	//3.矩阵初始化
	for(i=0;i<G->vexnum;i++) 
	 for(j=0;j<G->vexnum;j++)
	    {
	    	G->AdjMatrix[i][j]=0;
		}
	
	 //4.构建邻接矩阵
	 int n,m;
	 VertexType v1,v2;
	 
	 printf("请输入边的信息:\n");
	 for(i=0;i<G->arcnum;i++)
	 {
	 	scanf(" %c%c",&v1,&v2);
	 	n=LocateVex(G,v1); //获取v1所对应的在Vertex数组中的坐标 
	 	m=LocateVex(G,v2); //获取v2所对应的在Vertex数组中的坐标
	 	
	 	if(n==-1||m==-1)
		 {
		 	printf("NO This Vertex!\n");
		 	return;
		  } 
	
	   G->AdjMatrix[n][m]=1;
	   G->AdjMatrix[m][n]=1;//仅此处与有向图不同 
	 } 
	 G->kind=2;//表示构建的图类型为无向图UDG 
}

//3.构建有向网(Directed Network)
void CreateDN(MGraph *G) 
{
	int i,j;
	//1.输入顶点数和边数
	printf("输入顶点个数和边数(用逗号隔开):"); 
	scanf("%d,%d",&G->vexnum,&G->arcnum);
	printf("\n");
	
	//2.输入顶点元素 
	printf("输入顶点元素(无需空格隔开):");
	scanf("%s",G->Vertex);
	printf("\n");
	//3.矩阵初始化
	for(i=0;i<G->vexnum;i++) 
	 for(j=0;j<G->vexnum;j++)
	    {
	    	G->AdjMatrix[i][j]=MaxInt;
		}
	
	 //4.构建邻接矩阵
	 int n,m;
	 VertexType v1,v2;
	 int w;//v1->v2的权值 
	 
	 printf("输入边的信息:\n");
	 for(i=0;i<G->arcnum;i++)
	 {
	 	scanf(" %c%c,%d",&v1,&v2,&w);
	 	n=LocateVex(G,v1); //获取v1所对应的在Vertex数组中的坐标 
	 	m=LocateVex(G,v2); //获取v2所对应的在Vertex数组中的坐标
	 	
	 	if(n==-1||m==-1)
		 {
		 	printf("NO This Vertex!\n");
		 	return;
		  } 
	
	   G->AdjMatrix[n][m]=w;
	 }
	 G->kind=3; //表示构建的图类型为有向网DN 
}

//4.构建无向网(Undirected Network)
void CreateUDN(MGraph *G)//构建无向网(Undirected Network)
{
	int i,j;
	//1.输入顶点数和边数
	printf("输入顶点个数和边数(用逗号隔开):"); 
	scanf("%d,%d",&G->vexnum,&G->arcnum);
	printf("\n");
	
	//2.输入顶点元素 
	printf("输入顶点元素(无需空格隔开):");
	scanf("%s",G->Vertex);
	printf("\n");
	//3.矩阵初始化
	for(i=0;i<G->vexnum;i++) 
	 for(j=0;j<G->vexnum;j++)
	    {
	    	G->AdjMatrix[i][j]=MaxInt;
		}
	
	 //4.构建邻接矩阵
	 int n,m;
	 VertexType v1,v2;
	 int w;//v1->v2的权值 
	 
	 printf("请输入边的信息:\n");
	 for(i=0;i<G->arcnum;i++)
	 {
	 	scanf(" %c%c,%d",&v1,&v2,&w);
	 	n=LocateVex(G,v1); //获取v1所对应的在Vertex数组中的坐标 
	 	m=LocateVex(G,v2); //获取v2所对应的在Vertex数组中的坐标
	 	
	 	if(n==-1||m==-1)
		 {
		 	printf("NO This Vertex!\n");
		 	return;
		  } 
	
	   G->AdjMatrix[n][m]=w;
	   G->AdjMatrix[m][n]=w;//无向网仅此处不同 
     } 
     G->kind=4;//表示构建的图类型为无向网UDN 
}

void GraphChoice(MGraph *G)
{
	int target;
    printf(" 请选择图的类型:1.有向图DG  2.无向图UDG  3.有向网DN  4.无向网UDN\n\n");
    scanf("%d",&target);//选择图的类型
    printf("\n");

    switch (target) //根据所选类型,调用不同的函数实现构造图的功能
	{
        case 1:
        	 printf("您选择的是DG\n\n"); 
             CreateDG(G);
            break;
        case 2:
        	 printf("您选择的是UDG\n\n"); 
             CreateUDG(G);
            break;
        case 3:
        	 printf("您选择的是DN\n\n"); 
             CreateDN(G);
            break;
        case 4:
        	printf("您选择的是UDN\n\n"); 
            CreateUDN(G);
            break;
        default:
            break;
    }
}

void print(MGraph G)
{
	int i,j;
	printf("\n-------------------------------");
	printf("\n 邻接矩阵:\n\n"); 	
	
	if(G.kind==1||G.kind==2)//有向图与无向图 
	{
		printf("\t ");
	    for(i=0;i<G.vexnum;i++)
		printf("  %c",G.Vertex[i]);
		printf("\n");
		 
		for(i=0;i<G.vexnum;i++)
	   {
	   	  printf("\t%c",G.Vertex[i]);
	   	
		  for(j=0;j<G.vexnum;j++)
	    {
	 	    printf("  %d",G.AdjMatrix[i][j]);
	    }
	        printf("\n");
	   }
	} 
	
	else if(G.kind==3||G.kind==4)//有向网与无向网
	{
		printf("\t ");
		for(i=0;i<G.vexnum;i++)
		printf("\t%c",G.Vertex[i]);
		printf("\n");
		 
		for(i=0;i<G.vexnum;i++)
	   {
	   	  printf("\t%c",G.Vertex[i]);
	   	  
		  for(j=0;j<G.vexnum;j++)
	    {
	    	
	    	if(G.AdjMatrix[i][j]==MaxInt)
	 	    printf("\t∞");
	 	    else printf("\t%d",G.AdjMatrix[i][j]);
	    }
	      printf("\n");
	   }
	}
	 
}

int main() 
{
	MGraph G;
	GraphChoice(&G);
	print(G); 
	 
	return 0;
}

执行结果:

1. 有向图:

在这里插入图片描述

2. 无向图:

在这里插入图片描述

3. 有向网:

在这里插入图片描述

4. 无向网:

在这里插入图片描述

问题探究:scanf读入空白符


  • 问题描述在构建图或者网的函数中,有多个scanf连用,需要进行多次输入时,有些输入会发生异常。
void CreateUDN(MGraph *G)
{
	int i,j;
	
	printf("输入顶点个数和边数(用逗号隔开):"); 
	scanf("%d,%d",&G->vexnum,&G->arcnum);
	printf("\n");
	
	 
	printf("输入顶点元素(无需空格隔开):");
	for(i=0;i<G->vexnum;i++) 
	{
	    scanf("%c",G->Vertex[i]);//问题就出在此行!
	}    
	printf("\n");
	
	for(i=0;i<G->vexnum;i++) 
	 for(j=0;j<G->vexnum;j++)
	    {
	    	G->AdjMatrix[i][j]=MaxInt;
		}
	
	
	 int n,m;
	 VertexType v1,v2;
	 int w;
	 
	 printf("请输入边的信息:\n");
	 for(i=0;i<G->arcnum;i++)
	 {
	 	scanf("%c%c,%d",&v1,&v2,&w);//此行也有问题!
	 	n=LocateVex(G,v1);  
	 	m=LocateVex(G,v2); 
	 	
	 	if(n==-1||m==-1)
		 {
		 	printf("NO This Vertex!\n");
		 	return;
		  } 
	
	   G->AdjMatrix[n][m]=w;
	   G->AdjMatrix[m][n]=w;
     } 
}


此处应当输入5个顶点,但我们只输入了2个就结束了;接下来的权值也无法正确输入,情况类似。


  • 探究为了一探究竟,我试着输出这五个顶点的ASCII码值

    此时我们就恍然大悟了!在这五个位置中有三个ASCII码值为10,即是换行符!
  • 为了更进一步探究这个问题,我们将这问题简化一下:
#include <stdio.h>

int main()
{
    char ch[10];
    int i;

    printf("请输入ch:\n"); 
    for(i=0;i<5;i++)
    {
    	scanf("%c",&ch[i]);//1
    	printf("Target1\n");//2
	}
	
	for(i=0;i<5;i++)
	printf(" %d",ch[i]);

	return 0;
}
  • 一个简单的输入函数,我们用for循环给字符数组ch[ ]填值,根据代码执行的次序,执行应该如下(输入完一个字符按回车结束):
请输入ch:
A
Target1
B
Target1
C
Target1
D
Target1
E
Target1
 65 66 67 68 69

但是实际情况是:

  • 由此我们的很明显可以看出:在两个连续的Target1之间,正是我们在上一次输入时按下的换行符!由此我们可以得出结论1:当for遇上scanf连续输入字符时,如果以“回车”或“空格”作为输入结束,将会被当做字符读入。
  • 我们再来看一个实验:不输入“回车”或“空格”等作为结束
    在这里插入图片描述
  • 在for循环执行第一次的时候就输入5个字符,但是每次scanf只能读取一个,剩下的都被存储在缓存区中。连续出现5次Target1,说明for循环执行了5次而后四次却没有输入,这是为什么?
  • 其实当for循环执行后面4次的时候,缓冲区中已有字符正在排队(BCDE),故不需要输入,scanf会自动按次序读取缓冲区中排队的字符。
  • 所以我们得到了结论2:当用scanf输入的时候,如果多输入、误输入,那么多输入的数据就会被存放在缓冲区中,如果后续还有scanf,则会自动读取缓冲区中的数据。
  • 所以,也就解释了我们前面输入字符的时候按下“回车”作为结束标志时,“回车”会被认做字符存放在缓冲区中,当下次scanf读取字符时,将其读入,也就会造成上面的“输入5个字符只读取到2个,其中夹杂了3个回车”

  • scanf()原理分析
  1. 想象输入设备(键盘)连接着一个叫“缓冲”的东西,可以把缓冲认为是一个字符数组。
    当你的程序执行到scanf时,会从你的缓冲区读东西,如果缓冲区是空的,就阻塞住,等待你从键盘输入。
    现在假设你的缓冲区里有:abcd\n1234\n (其中\n是回车符)执行:scanf("%s",name);的时候,由于scanf是读数据直到看见空白符(空白符:指空格符、制表符、回车符)就停止的输入函数。所以执行后,把abcd存到了name中。缓冲区于是变成了 : \n1234\n
    接下来的执行就有问题了,如果遇到了:scanf("%d",&number);怎么办?因为遇到了回车符,它并不是一个数字,所以scanf还有一个特性,就是忽略先导的空白符。不管是有几百个回车也好,几万个空格也罢,只要它们连续地出现在缓冲区的开头,就统统忽略他们。然后再读有意义的字符。于是1234被读入number。
  2. 回到刚刚,当缓冲区还是:\n1234\n的时候,如果遇到了:scanf("%c",&sex);应该怎么办呢?你说,那好办呀,不是说了忽略前导空白符吗?跳过回车读’1’呀!想法是好的,可这只针对你的程序这一种情况。如果我编写的程序就是统计用户输入了多少个回车呢?所以对scanf来讲跳过前导空白符有个例外,当参数是%c的时候,就把缓冲区的第一个字符返回回去,不管是什么。
  3. 当执行scanf("%s",name)的时候,要求你从键盘输入,于是你输入了"abc",然后“回车”。缓冲区里自然而然地是:abc\nscanf把abc拿走了,留下了\n,缓冲区里现在就剩下\n于是,下一个scanf ("%c",&sex); 想当然地读取了\n。
  • 结论3:scanf对不同的参数(%d、%c、%s等)表现出来的特性不一样。

  • 解决方法
  1. scanf(" %c,&a");%c前面加一个空格。

    终于,在%c前面加了一个空格后,是我们想要的结果!
  • 原理分析:其实scanf函数在读取字符的时候,其实是将自身的格式缓冲区的数据进行字符串匹配。也就是说此时的模式串是“空格%c”,主串是缓冲区域的数据。“空格”或“回车”都属于空白字符,那么有了前面的“空格”无论模式串中有多少个“空格”或“回车”等空白字符,都可以将%c 对齐(对准)第一个“非空白字符”。(空白符:指空格符、制表符、回车符)
    在这里插入图片描述
  1. scanf("%c\n,&a");的%c后面加一个\n,过滤空格、制表符、回车等输入,也就是说当输入一个字符后,本次scanf不会立即结束,要等再接收到一个非空(非空格、非制表符、非回车)的输入scanf语句才结束。

  2. getchar();来抵消“空格”或“回车”。
  3. 在接收字符前,使用fflush()清空输入流中缓冲区中的内容,fflush(stdin);刷新标准输入缓冲区,即清空输入缓冲区。注意必须包含#include <stdio.h>头文件。

参考:

  1. scanf原理部分引用:scanf()函数的原理
  2. 部分参考:scanf函数输入空格分析
  • 59
    点赞
  • 252
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
邻接矩阵是一种表示的方式,其中矩阵的行和列分别对应中的顶点,矩阵中的值表示两个顶点之间是否有边相连。对于无向邻接矩阵是对称矩阵,因为对于任意一条边,它连接的两个顶点在矩阵中位置是相同的。 例如,下面是一个简单的无向邻接矩阵: ``` 1 2 3 4 1 0 1 1 0 2 1 0 1 1 3 1 1 0 1 4 0 1 1 0 ``` 通过这个邻接矩阵可以构建出以下的无向: ``` 1--2--3 \ | / \|/ 4 ``` 在构建无向时,我们可以从邻接矩阵中读取顶点和边的信息,然后构建数据结构。一种常见的数据结构是邻接表,其中每个顶点对应一个链表,链表中存储与该顶点相邻的所有顶点。在无向中,对于每条边,我们需要在两个顶点的链表中都添加对方。 以下是使用邻接表表示上述无向的代码: ```python class Node: def __init__(self, val): self.val = val self.next = None class Graph: def __init__(self, n, edges): self.n = n self.adj_list = [None] * n for u, v in edges: node1 = Node(v-1) node1.next = self.adj_list[u-1] self.adj_list[u-1] = node1 node2 = Node(u-1) node2.next = self.adj_list[v-1] self.adj_list[v-1] = node2 ``` 其中,`n`表示顶点个数,`edges`是一个包含边信息的列表,每个元素是一个长度为2的元组,表示一条边连接的两个顶点的编号。在构造函数中,我们首先创建一个长度为`n`的空列表`adj_list`,用于存储每个顶点的邻接表。然后对于每条边`(u, v)`,我们分别在顶点`u`和顶点`v`的邻接表中添加对方,即在`u`的邻接表中添加`v`,在`v`的邻接表中添加`u`。注意,这里我们将顶点编号从1开始,但在代码中需要将其转换为从0开始。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Attract1206

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值