图算法一之图的创建和转换

    几乎快近一个月没有写博客,因为一些事耽误了,或者去看其他方面的书去了。另外关于图,犹豫了很久,是否写这一章,因为也听同学和师兄师姐们说过,对于面试和笔试来说,图难度更大一些,而且代码量也更大一些,所以一般不会作为笔试和面试的首选考察对象,考察最多的就是链表、二叉树和字符串了。但是作为数据结构中最难的一章,其实图的应用应该比较多,另外这也是最后一次系统性的学习数据结构了,学习中没有点迎难而上的精神怎么能行?

   好了,言归正传,关于图的术语这些知识略去,可以参考任何一本数据结构的书,但是与本篇文章相关的知识还是要介绍一下的,现在开始了,其中本文的大部分内容和代码参考《大话数据结构》,其中用邻接表的尾插法创建图参考的是《妙趣横生的算法 C语言实现》


1.用邻接矩阵创建图

   图的邻接矩阵存储方式是用两个数组来表示图,一个一维数组储存图中定点信息,一个二维数组(邻接矩阵)存储储存图中边或弧的信息。

   文字还是没有图来的直观,其实看着图,感觉代码就出来了,下面的邻接表也是根据图来写代码的,邻接表中的图更能体现图形的优势。


代码对应如下:

#define MAXVEX 100
#define INFINITY 65536

struct MGraph{
	int vex[MAXVEX];			//顶点表
	int arc[MAXVEX][MAXVEX];    //邻接矩阵,可看左边表
	int numVertexes,numEdges;   //图中的顶点数和边数
};

//用邻接矩阵创建图
void createMGraph(MGraph *mGraph){
	printf("请输入图的顶点数和边数:\n");
	scanf("%d%d",&mGraph->numVertexes,&mGraph->numEdges);
	int i,j,k,w;
	printf("请输入个顶点:\n");
	for(i = 0;i < mGraph->numVertexes; i++){
		scanf("%d",&mGraph->vex[i]);   
	}
	for (i = 0; i < mGraph->numVertexes; i++)
		for(j = 0; j < mGraph->numVertexes;j++)
			mGraph->arc[i][j] = INFINITY;
	for(k = 0;k < mGraph->numEdges; k++){
		printf("请输入边(vi,vj)上的下标i,下标j和权值w:\n");
		scanf("%d%d%d",&i,&j,&w);
		mGraph->arc[i][j]=mGraph->arc[j][i]=w;
	}
}
    写的过程中发现了一处小问题,当邻接矩阵的图顶点如果用char表示的话,上文是用的int,如果用%c读取顶点,注意在读取顶点之前和之后用getchar()获得敲下的回车符,代码还是非常简单的,一看就明白,先初始化总顶点和边数,然后初始化各顶点和个边的权值,最后输入实际的边的值,最后用了无向图的邻接矩阵是对称矩阵的性质。


2.用邻接表创建图

   相比邻接矩阵创建图,用邻接表创建图就复杂多了。采用的是用数组和链表相结合的方法创建图,看图



   其实邻接表的方式,就是上面图就是用data和firstedge表示的顶点信息和用adjext和next构成的链表表示边表的信息,最后组合就是图了,我们知道链表的创建有两种方式,一种是头插法和尾插法,既然图中用到了链表,那么肯定也有两种方法

struct EdgeNode{		//边表结点
	int adjex;    //邻接点域,存储该顶点对应的下标
	int weight;   //权值
	struct EdgeNode *next;   //指向下一个邻接点
};

typedef struct VexNode{		//顶点表结点
	int data;    //顶点
	EdgeNode *firstarc;   //边表头指针
}AdjList[MAXVEX];

struct LGraph{
	int numVertexes,numEdges;
	AdjList adjList;   
};

//用邻接表创建图,其中边表结点链表使用的是头插法
void createLGraph(LGraph *lGraph){
	printf("请输入图的顶点数和边数:\n");
	scanf("%d%d",&lGraph->numVertexes,&lGraph->numEdges);
	int i,j,k,w;
	printf("请输入个顶点:\n");
	for(i = 0;i < lGraph->numVertexes; i++){
		scanf("%d",&lGraph->adjList[i].data);
		lGraph->adjList[i].firstarc = NULL;
	}
	for(k = 0;k < lGraph->numEdges; k++){
		printf("请输入边(vi,vj)上的下标i,下标j:\n");
		scanf("%d%d",&i,&j);
		EdgeNode *e = (EdgeNode*)malloc(sizeof(struct EdgeNode));
		e->adjex = j;
		e->next = lGraph->adjList[i].firstarc;
		lGraph->adjList[i].firstarc = e;

		e = (struct EdgeNode*)malloc(sizeof(struct EdgeNode));
		e->adjex = i;
		e->next = lGraph->adjList[j].firstarc;
		lGraph->adjList[j].firstarc = e;
	}
}

//用邻接表创建图,其中边表的链表使用的是尾插法
void createLGraph2(LGraph *graph){
	printf("请输入图的总顶点数和总边数:\n");
	scanf("%d%d",&graph->numVertexes,&graph->numEdges);
	int i,j,k;
	printf("请输入个顶点:\n");
	for(i=0;i<graph->numVertexes;i++){
		scanf("%d",&graph->adjList[i].data);
		graph->adjList[i].firstarc = NULL;
	}
	EdgeNode *e = NULL,*q = NULL,*p = NULL;
	for(i=0;i<graph->numVertexes;i++){
		printf("创建第%d个顶点的所有边,输入-1结束\n",i);
		int x;
		scanf("%d",&x);
		while(x!=-1){
			e = (struct EdgeNode *)malloc(sizeof(struct EdgeNode));
			e->adjex = x;
			e->next = NULL;
			if(graph->adjList[i].firstarc == NULL){
				graph->adjList[i].firstarc = e;
			}
			else{
				p->next = e;
			}
			p = e;
			scanf("%d",&x);
		}
	}
}

   
 由于 
用到了指针,相对来说变复杂了,出现的错误也就多了,对此总结 

   1.直观的感受是用头插法的代码剪短的多,而且还不用像尾插法那样需要最后输入一个-1判断某个顶点输入完毕,其原因是因为头插法每次都保持和firstarc链接上,当然头插法肯定是逆序的,而尾插法每次都是和最后一个结点链接,需要用到一个记录的结点p   

   2.在用尾插法创建图的时候,一开始以为直接复制粘贴就行了,发现了其实不然,因为头插法用的头结点,可以把j的顶点插入到i的顶点,也可以把i的顶点插入到j的顶点,而用尾插法如果想同时插入i和j顶点的话,是不行了,因为尾插法需要一次性把所有的关联的顶点全部插入完毕

   3.用上面的头插法更适合无向图,而尾插法更适用有向图,因为无向图是对称的,而尾插法,按道理说我在输入顶点1的时候输入了2,代表1和2之间有边,当等到顶点2输入的时候,本不应该重复输入1的结点, 但是代码需要每次都输入每个顶点相连的顶点

   理解了图的这两种表示方式,那么对于由邻接矩阵转换为邻接表的表示方式就呼之欲出了

//将图的邻接矩阵的表示形式转换为邻接表表示
LGraph* convert(MGraph *mgraph){
	int i,j;
	LGraph *lgraph = (LGraph *)malloc(sizeof(struct LGraph));
	lgraph->numVertexes = mgraph->numVertexes;
	lgraph->numEdges = mgraph->numEdges;
	for(i=0;i<mgraph->numVertexes;i++){
		lgraph->adjList[i].data = mgraph->vex[i];
		lgraph->adjList[i].firstarc = NULL;    //别忘了初始化
	}

	for(i=0;i<mgraph->numVertexes;i++){
		struct EdgeNode *p = NULL;
		for(j=0;j<mgraph->numVertexes;j++){
			struct EdgeNode *e = NULL;
			if(mgraph->arc[i][j]!=INFINITY){
				e = (struct EdgeNode *)malloc(sizeof(struct EdgeNode));
				e->next = NULL;
				e->adjex = j;
				if(lgraph->adjList[i].firstarc == NULL){
					lgraph->adjList[i].firstarc = e;
				}
				else{
					p->next = e;
				}
				p = e;
			}
		}
	}
	return lgraph;
}


其中本文中创建的两种方式的顶点输入其实都是0、1、2。。。,特此说明

最后,总的代码如下,最后还加上了关于显示邻接矩阵和邻接表的两个display函数:

#include <stdio.h>
#include <stdlib.h>

#define MAXVEX 100
#define INFINITY 65536

struct MGraph{
	int vex[MAXVEX];			//顶点表
	int arc[MAXVEX][MAXVEX];    //邻接矩阵,可看左边表
	int numVertexes,numEdges;   //图中的顶点数和边数
};

struct EdgeNode{		//边表结点
	int adjex;    //邻接点域,存储该顶点对应的下标
	int weight;   //权值
	struct EdgeNode *next;   //指向下一个邻接点
};

typedef struct VexNode{		//顶点表结点
	int data;    //顶点
	EdgeNode *firstarc;   //边表头指针
}AdjList[MAXVEX];

struct LGraph{
	int numVertexes,numEdges;
	AdjList adjList;   
};

//用邻接矩阵创建图
void createMGraph(MGraph *mGraph){
	printf("请输入图的顶点数和边数:\n");
	scanf("%d%d",&mGraph->numVertexes,&mGraph->numEdges);
	int i,j,k,w;
	//getchar();
	printf("请输入个顶点:\n");
	for(i = 0;i < mGraph->numVertexes; i++){
		//scanf("%c",&mGraph->vex[i]);
		scanf("%d",&mGraph->vex[i]);   //在处理%c的时候,需要注意前面输入顶点数和边数的最后的回车键,需要用getchar获取到
	}
	//getchar();
	for (i = 0; i < mGraph->numVertexes; i++)
		for(j = 0; j < mGraph->numVertexes;j++)
			mGraph->arc[i][j] = INFINITY;
	for(k = 0;k < mGraph->numEdges; k++){
		printf("请输入边(vi,vj)上的下标i,下标j和权值w:\n");
		scanf("%d%d%d",&i,&j,&w);
		mGraph->arc[i][j]=mGraph->arc[j][i]=w;
	}
}

//用邻接表创建图,其中边表结点链表使用的是头插法
void createLGraph(LGraph *lGraph){
	printf("请输入图的顶点数和边数:\n");
	scanf("%d%d",&lGraph->numVertexes,&lGraph->numEdges);
	int i,j,k,w;
	printf("请输入个顶点:\n");
	for(i = 0;i < lGraph->numVertexes; i++){
		scanf("%d",&lGraph->adjList[i].data);
		lGraph->adjList[i].firstarc = NULL;
	}
	for(k = 0;k < lGraph->numEdges; k++){
		printf("请输入边(vi,vj)上的下标i,下标j:\n");
		scanf("%d%d",&i,&j);
		EdgeNode *e = (EdgeNode*)malloc(sizeof(struct EdgeNode));
		e->adjex = j;
		e->next = lGraph->adjList[i].firstarc;
		lGraph->adjList[i].firstarc = e;

		e = (struct EdgeNode*)malloc(sizeof(struct EdgeNode));
		e->adjex = i;
		e->next = lGraph->adjList[j].firstarc;
		lGraph->adjList[j].firstarc = e;
	}
}

//用邻接表创建图,其中边表的链表使用的是尾插法
void createLGraph2(LGraph *graph){
	printf("请输入图的总顶点数和总边数:\n");
	scanf("%d%d",&graph->numVertexes,&graph->numEdges);
	int i,j,k;
	printf("请输入个顶点:\n");
	for(i=0;i<graph->numVertexes;i++){
		scanf("%d",&graph->adjList[i].data);
		graph->adjList[i].firstarc = NULL;
	}
	EdgeNode *e = NULL,*q = NULL,*p = NULL;
	for(i=0;i<graph->numVertexes;i++){
		printf("创建第%d个顶点的所有边,输入-1结束\n",i);
		int x;
		scanf("%d",&x);
		while(x!=-1){
			e = (struct EdgeNode *)malloc(sizeof(struct EdgeNode));
			e->adjex = x;
			e->next = NULL;
			if(graph->adjList[i].firstarc == NULL){
				graph->adjList[i].firstarc = e;
			}
			else{
				p->next = e;
			}
			p = e;
			scanf("%d",&x);
		}
	}
}

//将图的邻接矩阵的表示形式转换为邻接表表示
LGraph* convert(MGraph *mgraph){
	int i,j;
	LGraph *lgraph = (LGraph *)malloc(sizeof(struct LGraph));
	lgraph->numVertexes = mgraph->numVertexes;
	lgraph->numEdges = mgraph->numEdges;
	for(i=0;i<mgraph->numVertexes;i++){
		lgraph->adjList[i].data = mgraph->vex[i];
		lgraph->adjList[i].firstarc = NULL;
	}

	for(i=0;i<mgraph->numVertexes;i++){
		struct EdgeNode *p = NULL;
		for(j=0;j<mgraph->numVertexes;j++){
			struct EdgeNode *e = NULL;
			if(mgraph->arc[i][j]!=INFINITY){
				e = (struct EdgeNode *)malloc(sizeof(struct EdgeNode));
				e->next = NULL;
				e->adjex = j;
				if(lgraph->adjList[i].firstarc == NULL){
					lgraph->adjList[i].firstarc = e;
				}
				else{
					p->next = e;
				}
				p = e;
			}
		}
	}
	return lgraph;
}

void display(MGraph *graph){
	int i,j;
	for(i=0;i<graph->numVertexes;i++){
		for(j=0;j<graph->numVertexes;j++){
			printf("%d ",graph->arc[i][j]);
		}
		printf("\n");
	}
}

void display(LGraph *graph){
	int i,j;
	for(i=0;i<graph->numVertexes;i++){
		EdgeNode *tmp = graph->adjList[i].firstarc;
		printf("当前顶点是%d ",graph->adjList[i].data);
		while(tmp){
			printf("%d ",tmp->adjex);
			tmp = tmp->next;
		}
		printf("\n");
	}
}

int main(){
	MGraph metrixGraph;
	createMGraph(&metrixGraph);
	display(&metrixGraph);

	LGraph lgraph;
	createLGraph(&lgraph);
	display(&lgraph);

	createLGraph2(&lgraph);
	display(&lgraph);

	printf("讲邻接矩阵表示的图转换为用邻接表表示:\n");
	lgraph = *convert(&metrixGraph);
	display(&lgraph);
	return 0;
}


未完待续 下一节 图算法二之DFS


如果文章有什么错误或者有什么建议,欢迎提出,大家共同交流,一起进步

文章转载请注明出处,请尊重知识产权

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值