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

来源:互联网 发布:淘宝点击热力图 编辑:程序博客网 时间:2024/05/21 14:46

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

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


1.用邻接矩阵创建图

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

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


代码对应如下:

#define MAXVEX 100#define INFINITY 65536struct 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 65536struct 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 0
原创粉丝点击