实验9
姓名: 学号:班级:
8.1 实验目的
(1) 掌握图的基本概念。
(2) 掌握图的存储结构的设计与实现,基本运算的实现。
(3) 熟练掌握图的两种遍历算法、遍历生成树及遍历算法的应用。
8.2 实验任务
分别设计图(网)的邻接矩阵、邻接表存储结构,编写算法实现下列问题的求解。、
数据要求:可利用8.3中的数据,也可自行编写数据。
1.打印出图(网)的两种遍历序列。
2.求给定图中的边(或弧)的数目。
3.对给定的图G及出发点v0,设计算法从V0出发深度优先遍历图G,并构造出相应的生成树或生成森林。
4.对给定的图G及出发点v0,设计算法从V0出发广度优先遍历图G,并构造出相应的生成树或生成森林。
8.3 实验说明
这里介绍一种从文本文件读入图的数据创建图的方法,这样我们可以按照指定的格式,先从容地准备好数据,然后由程序自动读入数据来创建图。
createGrpAdjMatrix.h,文件创建邻接矩阵表示的图。
createGrpAdjLinkedList.h,文件创建邻接表表示的图。
(以下给出的图的创建方法仅供参考,实验者可自行设计其它创建方法)
1. 数据文件格式设计
这里数据用文本文件保存,文件扩展名可自行指定,比如g8.grp,只要数据按文本文件格式读写即可。下面给出一种数据文件格式,其实读者可以自行设计图的数据文件格式。
①标识行1:Graph
标识这是一个图的数据文件,这一行也可以不要。
②标识行2:UDG、或UDN、或DG、或DN
这一行用来标识此图是无向图(UDG)、无向网(UDN)、有向图(DG)、还是有向网(DN)。
③顶点行
这一行将图中所有顶点列出,顶点之间用空格进行分割。这些顶点数据读出后存放到图的顶点数组中。
例如,图6-21(a)所示的图的顶点行数据为:a b c d。
图的各种算法都是用顶点的编号来引用顶点的,所以这一行顶点的排列顺序是很重要的,顶点的排列顺序决定了顶点的编号。比如上例中,顶点a、b、c、d对应的编号就为1、2、3、4。
④边数据行
一条边一行,边的2个顶点之间用空格分割。如果是网,每一行再加边的权值,也以空格分割。如果是无向图和无向网,每条边会重复一次。
例如图6-18(a)无向图的边的数据为:
a b
a c
a d
b a
b c
c a
c b
c d
d a
d c
图6-21(a)无向网边的数据为:
a b 4
a c 5
a d 3
b a 4
b c 2
c a 5
c b 2
c d 6
d a 3
d c 6
⑤其它行
如果程序强大一点,还可以在文件中加注释行,允许出现空行等,当然这是非必须的。
举一个完整的图的数据文件的例子,对图6-18(a)的无向图,完整的数据文件如下:
//文件可以加注释行,注释以“//”开始
//Graph为图标志,否则判定格式不对
//标志行后,第一行为图的类型。UDG--无向图;UDN--无向网;DG--有向图;DN--有向网
//标志行后,第二行为顶点元素
//顶点行以下图的边或弧。用顶点表示,第一列为起始顶点;第二列为邻接点;在网中再增加一列表示权值。
//本图具有4个顶点5条边
//下一行为图的标识行
Graph
//图的类型标识,此为无向图
UDG
//顶点元素数据
a b c d
//以下为边的数据,共10行数据,表示5条边
a b
a c
a d
b a
b c
c a
c b
c d
d a
d c
文件名不妨叫做Gudg4.grp。
再举一个有向网的例子,对图6-22所示的有向网,完整的数据文件如下:
图1 一个有向网实例
//标识为图数据
Graph
//标识有向网
DN
//顶点数据
a b c d e f g h i j
//以下为边数据,共15条边
a b 2
a d 20
b e 1
c a 3
d c 8
d f 6
d g 4
e c 7
e h 3
f c 1
g h 1
h f 2
h j 2
i g 2
j i 1
不妨设文件名为Gdn10.grp
2. 从数据文件创建邻接矩阵表示的图
指定图的数据文件名,然后逐行读出数据并处理,自动创建邻接矩阵表示的图。本程序可以自动处理注释行和空行,程序实现如下:
1 //****************************文件创建图*****************************//
2 //* 函数功能:从文本文件创建邻接矩阵表示的图 *//
3 //* 入口参数 char fileName[],文件名 *//
4 //* 出口参数:Graph &G,即创建的图 *//
5 //* 返回值:bool,true创建成功;false创建失败 *//
6 //* 函数名:CreateGraphFromFile(char fileName[], Graph &G) *//
7 //*******************************************************************//
8 int CreateGraphFromFile(char fileName[], Graph &G)
9 {
10 FILE* pFile; //定义文件指针
11 char str[1000]; //存放读出一行文本的字符串
12 char strTemp[10]; //判断是否注释行
13 cellType eWeight; //边的信息,常为边的权值
14 GraphKind graphType; //图类型枚举变量
15 pFile=fopen(fileName,"r");
16 if(!pFile)
17 {
18 printf("错误:文件%s打开失败。\n",fileName);
19 return false;
20 }
21 while(fgets(str,1000,pFile)!=NULL)
22 {
23 strLTrim(str); //删除字符串左边空格,这是一个自定义的函数
24 if (str[0]=='\n') //空行,继续读取下一行
25 continue;
26 strncpy(strTemp,str,2);
27 if(strstr(strTemp,"//")!=NULL) //跳过注释行
28 continue;
29 else //非注释行、非空行,跳出循环
30 break;
31 }
32 //循环结束,str中应该已经是图的标识Graph,判断标识是否正确
33 if(strstr(str,"Graph")==NULL)
34 {
35 printf("错误:打开的文件格式错误!\n");
36 fclose(pFile); //关闭文件
37 return false;
38 }
39 //读取图的类型,跳过空行
40 while(fgets(str,1000,pFile)!=NULL)
41 {
42 strLTrim(str); //删除字符串左边空格,这是一个自定义函数
43 if (str[0]=='\n') //空行,继续读取下一行
44 continue;
45 strncpy(strTemp,str,2);
46 if(strstr(strTemp,"//")!=NULL) //注释行,跳过,继续读取下一行
47 continue;
48 else //非空行,也非注释行,即图的类型标识
49 break;
50 }
51 //设置图的类型
52 if(strstr(str,"UDG"))
53 graphType=UDG; //无向图
54 else if(strstr(str,"UDN"))
55 graphType=UDN; //无向网
56 else if(strstr(str,"DG"))
57 graphType=DG; //有向图
58 else if(strstr(str,"DN"))
59 graphType=DN; //有向网
60 else
61 {
62 printf("错误:读取图的类型标记失败!\n");
63 fclose(pFile); //关闭文件
64 return false;
65 }
66 //读取顶点行数据到str。跳过空行
67 while(fgets(str,1000,pFile)!=NULL)
68 {
69 strLTrim(str); //删除字符串左边空格,这是一个自定义函数
70 if (str[0]=='\n') //空行,继续读取下一行
71 continue;
72 strncpy(strTemp,str,2);
73 if(strstr(strTemp,"//")!=NULL) //注释行,跳过,继续读取下一行
74 continue;
75 else //非空行,也非注释行,即图的顶点元素行
76 break;
77 }
78
79 //顶点数据放入图的顶点数组
80 char* token=strtok(str," ");
81 int nNum=0;
82 while(token!=NULL)
83 {
84 G.Data[nNum]=*token;
85 token = strtok( NULL, " ");
86 nNum++;
87 }
88 //图的邻接矩阵初始化
89 int nRow=0; //矩阵行下标
90 int nCol=0; //矩阵列下标
91 if(graphType==UDG || graphType==DG)
92 {
93 for(nRow=0;nRow<nNum;nRow++)
94 for(nCol=0;nCol<nNum;nCol++)
95 G.AdjMatrix[nRow][nCol]=0;
96 }
97 else
98 {
99 for(nRow=0;nRow<nNum;nRow++)
100 for(nCol=0;nCol<nNum;nCol++)
101 G.AdjMatrix[nRow][nCol]=INF; //INF表示无穷大
102 }
103 //循环读取边的数据到邻接矩阵
104 int edgeNum=0; //边的数量
105 elementType Nf, Ns; //边或弧的2个相邻顶点
106 while(fgets(str,1000,pFile)!=NULL)
107 {
108 strLTrim(str); //删除字符串左边空格,这是一个自定义函数
109 if (str[0]=='\n') //空行,继续读取下一行
110 continue;
111 strncpy(strTemp,str,2);
112 if(strstr(strTemp,"//")!=NULL) //注释行,跳过,继续读取下一行
113 continue;
114 char* token=strtok(str," "); //以空格为分隔符,分割一行数据,写入邻接矩阵
115 if(token==NULL) //分割为空串,失败退出
116 {
117 printf("错误:读取图的边数据失败!\n");
118 fclose(pFile); //关闭文件
119 return false;
120 }
121 Nf=*token; //获取边的第一个顶点
122 token = strtok( NULL, " "); //读取下一个子串,即第二个顶点
123 if(token==NULL) //分割为空串,失败退出
124 {
125 printf("错误:读取图的边数据失败!\n");
126 fclose(pFile); //关闭文件
127 return false;
128 }
129 Ns=*token; //获取边的第二个顶点
130 //从第一个顶点获取行号
131 for(nRow=0;nRow<nNum;nRow++)
132 {
133 if(G.Data[nRow]==Nf) //从顶点列表找到第一个顶点的编号
134 break;
135 }
136 //从第二个顶点获取列号
137 for(nCol=0;nCol<nNum;nCol++)
138 {
139 if(G.Data[nCol]==Ns) //从顶点列表找到第二个顶点的编号
140 break;
141 }
142 //如果为网,读取权值
143 if(graphType==UDN || graphType==DN)
144 { //读取下一个子串,即边的附加信息,常为边的权重
145 token = strtok( NULL, " ");
146 if(token==NULL) //分割为空串,失败退出
147 {
148 printf("错误:读取图的边数据失败!\n");
149 fclose(pFile); //关闭文件
150 return false;
151 }
152 eWeight=atoi(token); //取得边的附加信息
153 }
154 if(graphType==UDN || graphType==DN)
155 G.AdjMatrix[nRow][nCol]=eWeight;
156 //如果为网,邻接矩阵中对应的边设置权值,否则置为1
157 else
158 G.AdjMatrix[nRow][nCol]=1;
159 edgeNum++; //边数加1
160 }
161 G.VerNum=nNum; //图的顶点数
162 if(graphType==UDG || graphType==UDN)
163 G.ArcNum=edgeNum / 2; //无向图或网的边数等于统计的数字除2
164 else
165 G.ArcNum=edgeNum;
166 G.gKind=graphType; //图的类型
167 fclose(pFile); //关闭文件
168 return true;
169 }
3. 从数据文件创建邻接表表示的图
程序实现如下:
1 //****************************文件创建图*****************************// 2 //* 函数功能:从文本文件创建邻接表表示的图 *// 3 //* 入口参数 char fileName[],文件名 *// 4 //* 出口参数:Graph &G,即创建的图 *// 5 //* 返回值:bool,true创建成功;false创建失败 *// 6 //* 函数名:CreateGraphFromFile(char fileName[], Graph &G) *// 7 //* 备注:本函数使用的数据文件格式以边(顶点对)为基本数据 *// 8 //*******************************************************************// 9 int CreateGraphFromFile(char fileName[], Graph &G) 10 { 11 FILE* pFile; //定义文件指针 12 char str[1000]; //存放读出一行文本的字符串 13 char strTemp[10]; //判断是否注释行 14 char* ss; 15 int i=0, j=0; 16 int edgeNum=0; //边的数量 17 eInfoType eWeight; //边的信息,常为边的权值 18 GraphKind graphType; //图类型枚举变量 19 pFile=fopen(fileName,"r"); 20 if(!pFile) 21 { 22 printf("错误:文件%s打开失败。\n",fileName); 23 return false; 24 } 25 while(fgets(str,1000,pFile)!=NULL) //跳过空行和注释行 26 { 27 strLTrim(str); //删除字符串左边空格,这是一个自定义函数 28 if (str[0]=='\n') //空行,继续读取下一行 29 continue; 30 strncpy(strTemp,str,2); 31 if(strstr(strTemp,"//")!=NULL) //跳过注释行 32 continue; 33 else //非注释行、非空行,跳出循环 34 break; 35 } 36 //循环结束,str中应该已经是图的标识Graph,判断标识是否正确 37 if(strstr(str,"Graph")==NULL) 38 { 39 printf("错误:打开的文件格式错误!\n"); 40 fclose(pFile); //关闭文件 41 return false; 42 } 43 //读取图的类型,跳过空行及注释行 44 while(fgets(str,1000,pFile)!=NULL) 45 { 46 strLTrim(str); //删除字符串左边空格,这是一个自定义函数 47 if (str[0]=='\n') //空行,继续读取下一行 48 continue; 49 strncpy(strTemp,str,2); 50 if(strstr(strTemp,"//")!=NULL) //注释行,跳过,继续读取下一行 51 continue; 52 else //非空行,也非注释行,即图的类型标识 53 break; 54 } 55 //设置图的类型 56 if(strstr(str,"UDG")) 57 graphType=UDG; //无向图 58 else if(strstr(str,"UDN")) 59 graphType=UDN; //无向网 60 else if(strstr(str,"DG")) 61 graphType=DG; //有向图 62 else if(strstr(str,"DN")) 63 graphType=DN; //有向网 64 else 65 { 66 printf("错误:读取图的类型标记失败!\n"); 67 fclose(pFile); //关闭文件 68 return false; 69 } 70 //读取顶点数据到str。跳过空行 71 while(fgets(str,1000,pFile)!=NULL) 72 { 73 strLTrim(str); //删除字符串左边空格,这是一个自定义函数 74 if (str[0]=='\n') //空行,继续读取下一行 75 continue; 76 strncpy(strTemp,str,2); 77 if(strstr(strTemp,"//")!=NULL) //注释行,跳过,继续读取下一行 78 continue; 79 else //非空行,也非注释行,即图的顶点元素行 80 break; 81 } 82 //顶点数据放入图的顶点数组 83 char* token=strtok(str," "); 84 int nNum=0; 85 while(token!=NULL) 86 { 87 G.VerList[nNum].data=*token; 88 G.VerList[nNum].firstEdge=NULL; 89 token = strtok( NULL, " "); 90 nNum++; 91 } 92 //循环读取边(顶点对)数据 93 int nRow=0; //矩阵行下标 94 int nCol=0; //矩阵列下标 95 EdgeNode* eR; //边链表尾指针 96 EdgeNode* p; 97 elementType Nf, Ns; //边或弧的2个相邻顶点 98 while(fgets(str,1000,pFile)!=NULL) 99 { 100 strLTrim(str); //删除字符串左边空格,这是一个自定义函数 101 if (str[0]=='\n') //空行,继续读取下一行 102 continue; 103 strncpy(strTemp,str,2); 104 if(strstr(strTemp,"//")!=NULL) //注释行,跳过,继续读取下一行 105 continue; 106 char* token=strtok(str," "); //以空格为分隔符,分割一行数据 107 if(token==NULL) //分割为空串,失败退出 108 { 109 printf("错误:读取图的边数据失败!\n"); 110 fclose(pFile); //关闭文件 111 return false; 112 } 113 Nf=*token; //获取边的第一个顶点 114 token = strtok( NULL, " "); //读取下一个子串,即第二个顶点 115 if(token==NULL) //分割为空串,失败退出 116 { 117 printf("错误:读取图的边数据失败!\n"); 118 fclose(pFile); //关闭文件 119 return false; 120 } 121 Ns=*token; //获取边的第二个顶点 122 //从第一个顶点获取行号 123 for(nRow=0;nRow<nNum;nRow++) 124 { 125 if(G.VerList[nRow].data==Nf) //从顶点列表找到第一个顶点的编号 126 break; 127 } 128 //从第二个顶点获取列号 129 for(nCol=0;nCol<nNum;nCol++) 130 { 131 if(G.VerList[nCol].data==Ns) //从顶点列表找到第二个顶点的编号 132 break; 133 } 134 //如果为网,读取权值 135 if(graphType==UDN || graphType==DN) 136 { //读取下一个子串,即边的附加信息,常为边的权重 137 token = strtok( NULL, " "); 138 if(token==NULL) //分割为空串,失败退出 139 { 140 printf("错误:读取图的边数据失败!\n"); 141 fclose(pFile); //关闭文件 142 return false; 143 } 144 eWeight=atoi(token); //取得边的附加信息,即权值 145 } 146 eR=G.VerList[nRow].firstEdge; 147 while(eR!=NULL && eR->next!=NULL) 148 { 149 eR=eR->next; //后移边链表指针,直至尾节点 150 } 151 p=new EdgeNode; //申请一个边链表结点 152 p->adjVer=nCol+1; //顶点的编号,从1开始 153 //边的附加信息(权值),对有权图保存权值,无权图为1 154 if(graphType==UDN || graphType==DN) 155 p->eInfo=eWeight; 156 else 157 p->eInfo=1; 158 p->next=NULL; 159 if(G.VerList[nRow].firstEdge==NULL) 160 { 161 G.VerList[nRow].firstEdge=p; 162 eR=p; 163 } 164 else 165 { 166 eR->next=p; 167 eR=p; //新的尾指针 168 } 169 edgeNum++; //边数加1 170 } 171 G.VerNum=nNum; //图的顶点数 172 if(graphType==UDG || graphType==UDN) 173 G.ArcNum=edgeNum / 2; //无向图或网的边数等于统计的数字除2 174 else 175 G.ArcNum=edgeNum; 176 G.gKind=graphType; //图的类型 177 fclose(pFile); //关闭文件 178 return true; 179 }
4. 图的销毁
以邻接矩阵为存储结构的图,因为使用矩阵存储图的数据,不存在销毁(释放内存)问题。但是以邻接表为存储结构的图,由于在创建图的过程中使用malloc()函数或new操作符动态申请了内存,当这个图不再需要时,必须手工释放动态申请的内存,否则造成内存泄漏。下面给出一个销毁邻接表表示的图的程序。