实验5 加权图
目标
l利用一个结点列表和邻接矩阵创建一个加权图的实现程序
l开发一个例程,查找图中每两个项点间的最小(或者最短)代价路径
l增加顶点颜色,并且实现一个方法,测试一个图是否有正确的颜色
l探讨四色原理,产生一个图,如果少于五种颜色,则无法为这个图创建一种正确颜色
概述
仅利用线性或者层次数据结构,有些关系不容易表达。在一个高速公路网络中,两个城市的连接关系就是这样一种关系。虽然在高速公路网络中,利用线性(例如,只有一条街道)或者层次(紧急通道和出口)结构,也可以描述城市之间的关系,但是,我们都多次在环形道上开过车,并且已经知道大多数高速公路网络都不是线性和层次的。现在,我们所需要的就是一个数据结构,利用它,我们将每个城市和网络中其他任何城市连结起来。这种类型的数据结构就叫图。
像树一样,一个图包含一系列节点(称为顶点)和一系列边。与树不一样的是,图中的边能连结任何两个顶点,而不仅是父节点和它的子节点。下图就表示一个简单的高速公路网络。
在图中的每个顶点都有一个惟一的标签来标记一个特定的城市。每条边都有一个权值来标记经过相应路径的代价(根据距离、时间、或者金钱来衡量)。请记住图中的边是无向的;也就是说,如果有一条边连结顶点A和B,那么这条边既可以从A指向B,也可以从B指向A。形成的这个加权的无向图就表示了在这条高速公路网络中,在两个城市间通过公路旅行的代价。在这个实验中,重点是加权无向图的实现和应用。
加权图ADT
数据项:图中的每个顶点都有一个标签(字串类型),它是顶点的惟一标识符。顶点还可以包括其他数据。
结构:在图中,利用一系列无向边来表示两个顶点之间的关系,每条边连结两个顶点。这些边共同定义了顶点间的一种对称关系。在加权图中,每条边都有一个权值,用来标记通过这条边的代价。
运算
InitWtGraph(int maxNumber )
要求:无
结果:创建一个空的图。为其分配足够的内存空间,它包含maxNumber个顶点的图。
DeWtGraph( )
要求:无
结果:释放(free)存储图占用的空间。
voidinsertVertex( Vertex newVertex )
要求:图不满。
结果:添加newVertex到图中。如果这个顶点在图中已存在,则更新它。
voidinsertEdge( char * v1, char *v2, int wt )
要求:图包括顶点v1和v2。
结果:添加一条连接v1和v2的无向边到图中。边的权值为 wt , 如果已经存在连接这两个顶点的边,则更新边的权值。
boolretrieveVetex( char *v, Vertex &vData )
要求:无
结果:在图中搜索顶点v。如果发现这个顶点,则复制这个顶点的数据到vData,然后返回true, 否则返回false,vData值不确定。
boolgetEdgeWeight( char *v1, char *v2, int &wt )
要求:图包括顶点v1和v2。
结果:在图中搜索连接v1和v2的无向边,如果发现这条边,则返回true,并且用wt返回这条边的权值,否则返回false, wt值不确定。
voidremoveVertex( char *v )
要求:图包括顶点v。
结果:从图中删除顶点v。
voidremoveEdge( char *v1, char *v2 )
要求:图包括顶点v1和v2。
结果:从图中删除连接v1和v2的无向边。
voidclear( )
要求:无
结果:删除图中的所有顶点和边。
boolisEmpty( )
要求:无
结果:如果图为空(无顶点),则返回true,否则,返回false 。
boolisFull( )
要求:无
结果:如果图为满,则返回true,否则,返回false。
voidshowStructure( )
要求:无
结果:以数组形式输出图的顶点,以邻接矩阵形式输出边(带权值)。如果图为空,则输出“Empty graph”。这个运算只用于测试/调试目的。
实验5 作业单
姓名小组____________________ 日期____________________
请在教师布置的练习时应的已布置列上打一个钩(√)。在提交这个实验的一组材料前面附上这个作业单。
练习 | 已布置:打钩或 列出练习编号 | 已完成 |
实验前练习 |
|
|
过渡练习 |
|
|
实验中练习1 |
|
|
实验中练习2 |
|
|
实验中练习3 |
|
|
实验后练习1 |
|
|
实验后练习2 |
|
|
总计 |
|
|
实验5 实验前练习
姓名小组____________________ 日期____________________
可以用许多方式表示一个图。在这个实验中,使用数组存储一系列顶点,并且使用邻接矩阵存储边。在邻接矩阵中下标(j,k)表示从顶点到顶点k的边。对于一个加权图来说,每个矩阵项包含相应边的权值。经过特定选择的权值可以用来表示图中不存在的边。
下图显示了顶点列表和邻接矩阵。‘-’用来标记图中不存在的边。
顶点列表 | 邻接矩阵 | ||||||
索引 | 标记 | 从/到 | 0 | 1 | 2 | 3 | 4 |
0 1 2 3 4 | A B C D E | 0 1 2 3 4 | - 50 100 - - | 50 - - 93 - | 100 - - 112 210 | - 93 112 - 87 | - - 210 87 - |
顶点A有一个索引值0,顶点C有一个索引值2 ,从顶点A到顶点C的边的权值存储在邻接矩阵下标为(0,2)的数据项中。
Ø第一步:利用存储顶点(vertexList)的一个数组和存储边(adjMatrix)的一个邻接矩阵,实现加权图中的运算。顶点数是不固定的,因此,我们需要能够存储一个图拥有的最大顶点数(maxSize),以及在图中拥有的实际顶点数(size)。基于文件wtgraph.h的声明,实现我们的运算。在文件testwtgraph.cpp中实现showStructure运算。
const int defMaxGraphSize = 100,
vertexLabelLength= 11,
infiniteEdgeWt= INT_MAX;
struct Vertex {
charlabel[vertexLabelLength];
};
struct WtGraph {
intmaxSize, size;
Vertex*verteist;
int*adjMatrix[];
};
int getIndex( WtGraph &G, char *v );
int Get edge( WtGraph G, int row, int col );
voidsetEdge( WtGraph &G, int row, int col, int wt );
voidInitWtGraph( WtGraph &G, int maxNumber );
void DeWtGraph(WtGraph &G );
voidinsertVertex( WtGraph &G, Vertex newVertex );
voidinsertEdge( WtGraph &G, char *v1, char *v2, int wt );
boolretrieveVertex( WtGraph G, char *v, Vertex &vData );
boolGet edgeWeight( WtGraph G, char *v1, char *v2, int &wt );
voidremoveVertex( WtGraph &G, char *v);
voidremoveEdge( WtGraph &G, char *v1, char *v2 );
voidclear( WtGraph &G );
boolisEmpty( WtGraph G );
boolisFull( WtGraph G );
voidshowStructure( WtGraph G );
使用getEdge()和setEdge()函数来访问邻接矩阵中的数据项。
赋值语句:setEdge( 2, 3, 100 ); 为邻接矩阵第二行、第三列的数据项赋一个100的权值。
语句
if (getEdge(j,k) == infiniteEdgeWt )
cout<< “Edge is missing from graph " << endl;
测试是否存在连接索引值为j和索引值为k 的顶点的一条边。
Ø第二步:将加权图的实现保存在文件wtgraph.cpp中。
实验5 过渡练习
姓名小组____________________ 日期____________________
在文件testwtgraph.cpp中,利用下面的命令,进行交互式测试加权图的实现。
命令 | 操作 |
+v =v w wt ?v #v w -v !v w E F C Q | 添加顶点v 添加一条连接v和w的无向边到图中。边的权值是wt 检索顶点v 检索连接v和w的无向边,并且输出权值wt 删除顶点 删除连接v和w的边 报告图是否空 报告图是否满 清除图 退出测试程序 |
请注意,v和w表示顶点标签(类型char *) ,不是单个字符(char 类型)。因此,我们必须按照上述格式小心地键入正确的命令(包括空格)。
Ø第一步:为加权图实现准备一个测试计划。测试计划应覆盖各种不同类型的图,它们的顶点以不同的方式连接。一定要包括试图检索不存在的边,或者连接不存在的顶点这样的测试情况。测试计划如下。
Ø 第二步:执行测试计划。如果发现我们的实现中有错误,修改它们,并且重新执行测试计划。
加权图 ADT 中运算的测试计划
测试项目 | 命令 | 预期结果 | 检查 |
|
|
|
|
实验5 实验中练习1
姓名小组____________________ 日期____________________
在加权图的许多应用中,不仅需要决定两个顶点之间是否有一条边,而且还要决定顶点间是否有路径。扩展邻接矩阵中的(j,k)表示索引为j到索引为k的结点的最小(最短)路径的代价。下图产生表示如下所示的路径矩阵。
顶点列表 | 邻接矩阵 | ||||||
索引 | 标记 | 从/到 | 0 | 1 | 2 | 3 | 4 |
1 1 2 3 4 | A B C D E | 0 1 2 3 4 | 0 50 100 143 230 | 50 0 150 93 180 | 100 150 0 112 199 | 143 93 112 0 87 | 230 180 199 87 0 |
这个图包括从顶点A到顶点E的许多路径。连接这些顶点的最短路径代价存储在路径矩阵数据项(0,4)中,0是A的索引值,4是E的索引值。相应的路径是ABDE。
在创建路径矩阵时,我们假设一个顶点到它本身的路径代价是0,存储在下标为(j,j)的数据项中。这样的假设是基于一个顶点访问它本身的事件不会发生这样一个事实,因此其代价是什么都没有。
给出一个图的邻接矩阵,只知道所有的边都是路径,我们开始创建一个路径矩阵。应用以下规则,我们可以将这些一条边的路径合并且为两条边的路径。
if 从顶点j到顶点m存在一条路径 && 从顶点m到顶点k存在一条路径,then 从顶点j到顶点k存在一条路径。
我们将这个规则应用到新产生的路径上,就会形成越来越多条边组成的路径。这个过程的关键是采用一个完全的、高效的方式列举和组合路径。以下算法中描述了这个任务的一个实现方法,该算法就是著名的Warshall算法。变量j、k和m是顶点索引,而不是顶点标签。
Initialize the path matrix so thatit is the same as the edge
matrix (all edges are paths). Inaddtoin, create a path with
cost 0 from each vertex back toitself.
for (m=0; m<size; m++)
for (j=0; j<size; j++)
for(k=0; k<size; k++)
if there exists a path from vertex j tovertex in and
there exists a path from vertex m to vertexk,
thenadd a path from vertex j to vertex k to the path
matrix.
这个算法建立了两个顶点间存在的路径,但是没有代价。不过,扩展上述规则,我们很容易决定顶点间路径的代价。
if there exists a path from a vertex j to avertex m and
there exists a path from a vertex m to avertex k and the cost of going from j to m to k is less than entry (j,k) in thepath matrix,
then replace entry (j,k) with the sum ofentries (j,m) and (m,k).
这个规则结合前面的算法,得出以下算法,这就是著名的Floyd算法。
Initialize the path matrix so that it is thesame as the edge
matrix (all edges are paths). In addtoin,create a path with
cost 0 from each vertex back to itself.
for (m=0; m<size; m++)
for (j=0; j<size; j++)
for ( k=0; k<size; k++)
if there exists a path from vertex j tovertex m and
thereexists a path from vertex in to vertex k and the sum of entries (j,m) and (m,k)is less than entry (j,k) in the path matrix,
then replace entry (j,k) with the sum ofentries (j,m)
and (m,k).
voidcomputePaths ( )
要求:无
结果:计算一个图的路径矩阵。
Ø第一步:向文件wtgraph.h的类型WtGraph中添加int*pathMaxtrix;
以及函数说明void computePaths() ;
Ø第二步:完成computePaths运算,将它添加到文件wtgraph.cpp中。
Ø第三步:增强函数showStructure()的功能,使其除了输出图的顶点列表和邻接矩阵外,还输出路径矩阵。
Ø第四步:在文件testlwtgraph.cpp中增加交互式测试程序,测试该函数,测试命令为PM。
Ø第五步:为这个computePaths运算准备一个测试计划。测试计划应覆盖各种不同的图,它们以不同的方式连接顶点,并且带有不同的权值。确保要包括这些情况:一对顶点间的边的权值比这两个顶点的多个边的路径的代价还要大。上图中边CE和路径CDE就表示这种特性。测试计划表格如下。
Ø第六步:执行测试计划。如果发现computePaths运算实现中有错,修改它们,并且重新执行测试计划。
computePaths运算测试计划
测试项目 | 命令 | 预期结果 | 检查 |
|
|
|
|
实验5:实验中练习2
姓名小组____________________ 日期____________________
假设我们希望创建一个高速公路网络的公路地图。为了避免混乱,必须细心地为城市着色,并且遵守这样一条规则:任何两个共有一条边界的城市不能用同一种颜色。只有对城市着色满足这样的标准,才称为地图的正确着色。
利用图的术语重新描述这个问题,我们说,对图中每个顶点赋颜色值时,不能与它邻接的另一个顶点赋相同的颜色值。下图是一个对图正确着色(灰和白)的例子。
要正确着色,两个颜色是不够的。在图论中,最著名的理论是四色理论,它描述为,对任何一个平面图(也就是说,任何图都能在一张纸上画出,并且没有边与另一条边交叉)创建正确的着色,要求最多使用四种颜色。要求四种颜色的平面图如下所示。请记住,如果一个图不是平面的,就必须使用四种以上颜色。
下面的加权图运算测试一个图是否正确着色。
boolhasProperColoring()
要求:所有顶点都被赋了一种颜色。
结果:如果图中没有一个顶点与其邻顶点有相同的颜色,则返回true ,否则,返回false。
Ø第一步:向文件wtgraph.h中的Vertex类型声明中添加下列数据。
char color; // Vertex color (‘ r ' for red and forth );
向文件wtgraph.h中的WtGraph类型声明中添加下列函数原型。
bool hasProperColoring();
Ø第二步:实现hasProperColoring运算,将它添加到文件wtgraph.cpp中。
Ø第三步:修改showStructure()函数,除了输出标签外,还输出顶点颜色。
Ø第四步:在文件testwtgraph.cpp中增加测试程序,测试该函数,测试命令为PC。
Ø第五步:为这个hasProperColoring运算准备一个测试计划。测试计划应覆盖各种不同图和顶点着色。测试计划表格如下。
Ø第六步:执行测试计划。如果发现hasProperColoring运算实现中有错,请改正之,并且重新执行测试计划。
properColoring运算测试计划
测试项目 | 命令 | 预期结果 | 检查 |
|
|
|
|
实验5 实验中练习3
姓名小组____________________ 日期____________________
通信网络包含一系列交换中心(顶点)的和一系列连结这些中心的通信线路(边)。当设计一个网络时,一个通信公司需要知道,如果由于天气或者设备故障,一条通信线路不起作用了,这个网络是否还支持所有中心间的通信。因此,需要知道下面这些问题的答案。
给定一个图中,每一个顶点是否都有一条通向其他每一个顶点的路径,从图中删除任何一条边,是否总能产生一个图,在这个图中,每一个顶点是否都有一条通向其他每一个顶点的路径。
显然的,这个问题的答案取决于图。下面这个图的答案是:yes。
另一方面,对下面这个图来说,由于删除了连接顶点D和顶点E的边,得到了两个无连通的子图,对这个图来说,答案是:no。
虽然对一个任意的图来说决定这个问题的答案是困难的,但总有一类图,对这类图来说这个问题的答案永远是yes。给出以下定义:
n 如果在图G中,每一个顶点都有一条通向其他每一个顶点的路径,则这个图是连通的。
n 在图G中,一个顶点v的度(degree)是图中与这个顶点连接的所有边的个数。
利用简单的图论可以推导出一条规则。
在一个连通图中,如果所有的顶点度数都是偶数,则从图中删除任何一条边仍能产生一个连通图。
如果将这个规则用于图中,则我们知道刚才问题的答案对这个图来说是:yes。请注意,这个规则并没有涉及有一个或者多个顶点的度数是奇数的连通图。
下面加权图的运算是测试图中每个顶点的度数是否为偶数。
boolareAllEven()
要求:无
结果:如果图中的每个顶点的度数都是偶数,则返回true,否则,返回false。
Ø 第一步:完成上述areAllEven运算,并且把它加到文件wtgraph.cpp中,这个运算的原型在文件wtgraph.h中说明。
Ø 第二步:在文件testwtgraph.cpp中添加测试程序测试areAllEven,测试命令为D。
Ø 第三步:为这个运算准备一个测试计划。测试计划包含的图的顶点以不同的方式连接。测试计划表格如下。
Ø 第四步:执行测试计划。如果发现areAllEven运算的实现中有错,纠正它们,并且重新执行测试计划。
areAllEven运算测试计划
测试项目 | 命令 | 预期结果 | 检查 |
|
|
|
|
实验5 实验后练习1
姓名小组____________________ 日期____________________
Floyd算法(实验中练习1)计算图中每对顶点间的最短路径。假设我们不仅想知道一对顶点间的最小路径的代价,而且想知道哪些顶点位于这个路径中。首先,我们需要为路径矩阵的每个数据项存储一个顶点列表。幸运的是,我们不必存储这么多信息。在路径矩阵中,对每个数据项(j,k),我们所需要知道的只是从顶点j到顶点k的最短路径上,j后面的顶点的索引值。也就是说,在从顶点j到顶点k的最短路径上,第二个顶点的索引值。例如:下图表示为以下路径矩阵。
产生扩展路径矩阵
顶点列表 | 邻接矩阵(代价/最短路径上的第二个顶点) | ||||||
索引 | 标记 | 从/到 | 0 | 1 | 2 | 3 | 4 |
0 1 2 3 4 | A B C D E | 0 1 2 3 4 | 0/0 50/0 100/0 143/1 230/3 | 50/1 0/1 150/0 93/1 180/3 | 100/2 150/0 0/2 112/2 199/3 | 143/1 93/3 112/3 0/3 87/3 | 230/1 180/3 199/3 87/4 0/4 |
在这个路径矩阵中数据项(0,4)表示从顶点A到顶点E的最短路径代价是230。它进一步表示顶点B(索引值为1)是最短路径上的第二个顶点。这样,最短路径的形式是AB … E。
解释我们如何利用这个扩展路径矩阵列出在给出的一对顶点间的最小路径上的顶点。
实验5 实验后练习2
姓名小组____________________ 日期____________________
给定一个示例图,如果用少于五种颜色,就不能创建出正确的着色方法(见实验中练习2 )。你的例子与四色定理矛盾吗?