Chapter 7 图
是什么:由顶点的有穷非空集合和顶点之间边的集合组成曾。G(V,E),V(Vertex),E(Edge).
图的定义
各种图定义
无向边:顶点之间的边没有方向,无序偶对(vi ,vj )。无向图:任意两顶点之间的边都是无向边。 有向边:顶点之间的边有方向,则称这条边为有向边,也称为弧。有序偶<vi ,vj >, vi 称为弧尾,vj 称为弧头。有向图:任意两个顶点之间的边都是有向边。 简单图:不存在顶点到自身的边,且同一条边不重复出现。 无向完全图:任意两个顶点之间都存在边。含n个顶点的无向完全图有[n*(n-1)]/2条边。 有向完全图:任意两个顶点之间都存在方向互为相反的两条弧。含n个顶点的有向完全图有n*(n-1)条边。 有很少边或弧的图称为稀疏图 ,反之称为稠密图 。 网:带权图。 子图:假设两个图G=(V,{E}),G’=(V’,{E’}),如果V’包含于V 且 E’包含于E,则称G’为G的子图。
图的顶点与边间关系
无向图顶点v的度 是和v相关联的边的数目,记为TD。边数是个顶点度数和的一半 有向图,以顶点v为头的弧的数目称为v的入度 ,记为ID(v);以v为尾的弧的数目称为v的出度OD(v),记为OD(v),顶点v的度TD(v)=ID(v)+OD(v)。 路径:一个顶点序列。简单路径:序列中顶点不重复出现的路径。路径长度:路径上的边或弧的数目。 回路(或环):第一个顶点和最后一个顶点相同的路。简单回路:除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路。
连通图相关术语
连通图:无向图中任意两个顶点都是联通的。连通分量:无向图中的极大连通子图 。 强连通图:有向图中对每一对vi 、vj 都存在路径。强连通分量:有向图中的极大强连通子图。 连通图的生成树:一个极小的连通子图,它含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。从这里也可以知道:如果一个图有n个顶点且小于n-1条边,则是非连通图;如果多于n-1条边,必定构成一个环。但有n-1条边的并不一定是生成树。 有向树:如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1,则是一棵有向树。 有向图的生成森林:由若干棵有向树组成,含图中全部顶点,但只有足以构成若干棵不相交的有向树的弧。
图的定义与术语总结
按有无方向分为有向图 和无向图 。无向图由顶点 和边 构成,有向图由顶点 和弧 构成。弧有弧头 和弧尾 之分。 图按照边或弧的多少分为稀疏图 和稠密图 。如果两个顶点之间都存在边的叫完全图 ,有向的叫有向完全图 。若无重复的边或顶点到自身的边则叫简单图 。 图中顶点之间有邻接点 、依附 的概念。无向图顶点的边数叫做度 ,有向图顶点分为入度 和出度 。 图上的边或弧带上权称为网 。 图中顶点间存在路径, 两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环 ,当中不重复叫简单路径 。若任意两顶点都是连通的,则图就是连通图 。有向则称强连通图 。图中有子图,若子图极大连通则就是连通分量 ,有向的则称强连通分量 。 无向图中连通且n个顶点n-1条边叫生成树 。有向图中一顶点入度为0其余顶点入度为1的叫有向树。一个有向图由若干棵有向树构成生成森林 。
图的抽象数据类型
ADT 图( Graph)
Data
顶点的有穷非空集合和边的集合。
Operation
CreateGraph ( * G, V, VR) : 按照顶点集V和边弧集VR的定义构造图G。
DestoryGraph ( * G) : 图G存在则销毁。
LocateVex ( G, u) : 若图G中存在顶点u,则返回图中的位置。
GetVex ( G, v) : 若图G中存在顶点u,则返回图中的位置。
PutVex ( G, v, value) : 将图G中顶点v赋值value。
FirstAdjVex ( G, * v) : 返回顶点v的一个邻接顶点,若顶点在G中无邻接顶点返回空。
NextAdjVex ( G, v, * w) : 返回顶点v相对于顶点w的下一个邻接顶点,若w是v的最后
一个邻接点则返回“空”。
InsertVex ( * G, v) : 在图G中增添新顶点v。
DeleteVex ( * G, V) : 删除图G中顶点v及其相关的弧。
InsertArc ( * G, v, w) : 在图G中增添弧< v, w> ,若G是无向图,还需要增添对称弧< w, v> 。
DeleteArc ( * G, v, w) : 在图G中删除弧< v, w> ,若G是无向图,则还删除对称弧< w, v> 。
DFSTraverse ( G) : 对图G中进行深度有限遍历,在遍历过程中对每个顶点调用。
HFSTraverse ( G) : 对图G中进行广度优先遍历,在遍历过程中对每个顶点调用。
endADT
图的存储结构
图不可能用简单的顺序存储结构来表示。而多重链表的方式,即以一个数据域和多个指针域组成的结点表示图中的一个顶点,尽管可以实现,但如果各个顶点度数相差很大,会造成浪费。
邻接矩阵
用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。设图G有n个顶点,则邻接矩阵是一个n*n的方阵,有边对应位置为1,否则为0。 无向图邻接矩阵的特点:主对角线为0、对称矩阵、行或列中1的个数是对应顶点的度、邻接点就是扫描对应行,为1的就是邻接点。 有向图邻接矩阵的特点:主对角线为0、不对称、出度是行之和、入度是列之和、邻接点仍是扫描行。 带权图的邻接矩阵:初始化为INFINITY,对角线为0,其余为权。
typedef char VertexType;
typedef int EdgeType;
#define MAXVEX 100
#define INFINITY 65535
typedef struct
{
VertexType ves[ MAXVEX] ;
EdgeType arc[ MAXVEX] [ MAXVEX] ;
int numVertexes, numEdges;
} MGraph;
void CreateMGraph ( MGraph * G)
{
int i, j, k, w;
printf ( "输入顶点和边数:\n" ) ;
scanf ( "%d%d" , & G-> numVertexes, & G-> numEdges) ;
for ( i= 0 ; i< G-> numVertexes; i++ )
scanf ( & G-> vexs[ i] ) ;
for ( i= 0 ; i< G-> numVertexes; i++ )
for ( j= 0 ; j< G-> numVertexes; j++ )
G-> arc[ i] [ j] = INFINITY;
for ( k= 0 ; k< G-> numEdges; k++ )
{
printf ( "输入边(vi,vj)上的下标i,下标j和权w:\n" ) ;
scanf ( "%d,%d,%d" , & i, & j, & w) ;
G-> arc[ i] [ j] = w;
G-> arc[ j] [ i] = G-> arc[ i] [ j] ;
}
}
邻接表
为什么:邻接矩阵对稀疏图较浪费。 是什么:数组与链表相结合的存储方法。顶点用一个一维数组存储,每个元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。每个顶点的所有邻接点构成一个线性表,由于个数不定,用单链表存储。无向图称为顶点的边表,有向图则称为顶点作为弧尾的出边表。 adjves是邻接点域,存储某顶点的邻接点在顶点表中的下标。 有向图有方向,以顶点为弧尾存储边表,很容易得到出度。若为了便于确定顶点入度,可以对每个顶点建立一个以弧头存储的边表,叫逆邻接表 对于带权值的图,可以在边表节点定义中再增加一个weight的数据域存储权值信息。
typedef char VertexType;
typedef int EdgeType;
typedef struct EdgeNode
{
int adjvex;
EdgeType weight;
struct EdgeNode * next;
} EdgeNode;
typedef struct VertexNode
{
VertexType data;
EdgeNode * firstedge;
} VertexNode, AdjList[ MAXVEX] ;
typedef struct
{
AdjList adjList;
int numVertexes, numEdges;
} GraphAdjList;
void CreateALGraph ( GraphAdjList * G)
{
int i, j, k;
EdgeNode * e;
printf ( "输入顶点数和边数:\n" ) ;
scanf ( "%d,%d" , & G-> numVertexes, & G-> numEdges) ;
for ( i= 0 ; i< G-> numVertexes; i++ )
{
scanf ( & G-> adjList[ i] . data) ;
G-> adjList[ i] . firstedge= NULL ;
}
for ( k= 0 ; k< G-> numEdges; k++ )
{
printf ( "输入边(vi,vj)上的顶点序号:\n" ) ;
scanf ( "%d,%d" , & i, & j) ;
e= ( EdgeNode* ) malloc ( sizeof ( EdgeNode) ) ;
e-> adjvex= j;
e-> next= G-> adjList[ i] . firstedge;
G-> adjList[ i] . firstedge= e;
e= ( EdgeNode* ) malloc ( sizeof ( EdgeNode) ) ;
e-> adjvex= i;
e-> next= G-> adjList[ i] . firstedge;
G-> adjList[ j] . firstedge= e;
}
}
十字链表
为什么:对于有向图来说,邻接表有缺陷,入度出度没法同时保证。 是什么:组合邻接表与逆邻接表。创建复杂度和邻接表是相同的。 重新定义顶点表结点结构: firstin表示入边表的右指针,指向该顶点的入边表中第一个结点。 firstout表示出边表头指针,指向该顶点的出边表中的第一个结点。 重新定义的边表结点结构: tailvex指弧起点在顶点表中的下标,headvex是指弧终点在顶点表中的下标,headlink是指入边表指针域,指向终点相同的下一条边,taillink是指边表指针域,指向起点相同的下一条边。如果是网,还可以增加一个weight域来存储权值。
邻接多重表
为什么:邻接表对无向图的边的操作比较麻烦,若要删除边需要对邻接表结构中右边表的阴影两个结点进行删除操作。 是什么: 重新定义边表结点结构: ivex和jvex是某条边依附的两个顶点在顶点表中的下标。ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。 ilink指向的结点的jvex一定要和它本身的ivex的值相同。连线⑦就是指(v1 ,v0 )这条边,它是相当于顶点v1 指向(v1 ,v2 )边后的下一条。 邻接表与多重邻接表的差别,仅仅在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。若要删除(v0 ,v2 )这条边,只需要将⑥⑨的链接指向改为^就可。
边集数组
由两个一维数组组成。一个存储顶点的信息;另一个存储边的信息,这个边数组每个数组元素由一条边的起点下标(begin)、终点下标(end)和权(weight)组成。
图的遍历
是什么:从某一顶点除法访遍图中其余顶点,且是每一个顶点仅被访问一次。
深度优先遍历(DFS)
是什么:从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点除法深度优先遍历图,直到图中所有和v有路径相通的顶点都被访问到。 一个递归过程,像一棵树的前序遍历。 对于非连通图,只需要对它的连通分量分别进行深度优先遍历。即在先前一个顶点进行一次深度优先遍历后,若图中尚有顶点仍未访问,则另选图中一个未曾被访问的顶点作为起始点,重复上述过程,直至图中所有顶点都被访问到为止。
typedef int Boolean;
Boolean visited[ MAXVEX] ;
void DFS ( MGraph G, int i)
{
int j;
visited[ i] = TRUE;
printf ( "%c" , G. vexs[ i] ) ;
for ( j= 0 ; j< G. numVertexes; j++ )
if ( G. arc[ i] [ j] == 1 && ! visited[ j] )
DFS ( G, j) ;
}
void DFSTraverse ( MGraph G)
{
int i;
for ( i= 0 ; i< G. numVertexes; i++ )
visited[ i] = FALSE;
for ( i= 0 ; i< G. numVertexes; i++ )
if ( ! visited[ i] )
DFS ( G, i) ;
}
typedef int Boolean;
Boolean visited[ MAXVEX] ;
void DFS ( GraphAdjList GL, int i)
{
EdgeNode * p;
visited[ i] = TRUE;
printf ( "%c" , GL-> adjList[ i] . data) ;
p= GL-> adjList[ i] . firstedge;
while ( p)
{
if ( ! visited[ p-> adjvex] )
DFS ( GL, p-> adjvex) ;
p= p-> next;
}
}
void DFSTraverse ( GraphAdjList GL)
{
int i;
for ( i= 0 ; i< GL. numVertexes; i++ )
visited[ i] = FALSE;
for ( i= 0 ; i< GL-> numVertexes; i++ )
if ( ! visited[ i] )
DFS ( GL, i) ;
}
广度优先遍历(BFS)
是什么:类似于树的层序遍历。又称广度优先搜索。
void BFSTraverse ( MGraph G)
{
int i, j;
Queue Q;
for ( i= 0 ; i< G. numVertexes; i++ )
visited[ i] = FALSE;
InitQueue ( & Q) ;
for ( i= 0 ; i< G. numVertexes; i++ )
{
if ( ! visited[ i] )
{
visited[ i] = TRUE;
printf ( "%c" , G. vexs[ i] ) ;
EnQueue ( & Q, i) ;
while ( ! QueueEmpty ( Q) )
{
DeQueue ( & Q, & i) ;
for ( j= 0 ; j< G. numVertexes; j++ )
{
if ( G. arc[ i] [ j] == 1 && ! visited[ i] )
{
visited[ j] = TRUE;
printf ( "%c" , G. vexs[ j] ) ;
EnQueue ( & Q, j) ;
}
}
}
}
}
}
void BFSTraverse ( GraphAdjList GL)
{
int i;
EdgeNode * p;
Queue Q;
for ( i= 0 ; i< GL. numVertexes; i++ )
visited[ i] = FALSE;
InitQueue ( & Q) ;
for ( i= 0 ; i< GL. numVertexes; i++ )
{
if ( ! visited[ i] )
{
visited[ i] = TRUE;
printf ( "%c" , GL. adjList[ i] . data) ;
EnQueue ( & Q, i) ;
while ( ! QueueEmpty ( Q) )
{
DeQueue ( & Q, & i) ;
p= GL. adjList[ i] . firstedge;
while ( p)
{
if ( ! visited[ p-> adjvex] )
{
visited[ p-> adjvex] = TRUE;
printf ( "%c" , GL. adjList[ p-> adjvex] . data) ;
EnQueue ( & Q, p-> adjvex) ;
}
p= p-> next;
}
}
}
}
}
如果只是单纯遍历,DFS和BFS差不多,如果是查找,那么深度优先更适合目标比较明确,以找到目标为主要目的的情况,而广度优先更适合在不断扩大遍历范围时找到相对最优解的情况。
最小生成树
普里姆(Prim)算法
typedef char VertexType;
typedef int EdgeType;
#define MAXVEX 100
#define INFINITY 65535
typedef struct
{
VertexType ves[ MAXVEX] ;
EdgeType arc[ MAXVEX] [ MAXVEX] ;
int numVertexes, numEdges;
} MGraph;
void MiniSpanTree_Prim ( MGraph G)
{
int min, i, j, k;
int adjvex[ MAXVEX] ;
int lowcost[ MAXVEX] ;
lowcost[ 0 ] = 0 ;
adjvex[ 0 ] = 0 ;
for ( i= 1 ; i< G. numVertexes; i++ )
{
lowcost[ i] = G. arc[ 0 ] [ i] ;
adjvex[ i] = 0 ;
}
for ( i= 1 ; i< G. numVertexes; i++ )
{
min= INFINITY;
j= 1 ; k= 0 ;
while ( j< G. numVertexes)
{
if ( lowcost[ j] != 0 && lowcost[ j] < min)
{
min= lowcost[ j] ;
k= j;
}
j++ ;
}
printf ( "(%d,%d)" , adjvex[ k] , k) ;
lowcost[ k] = 0 ;
for ( j= 1 ; j< G. numVertexes; j++ )
{
if ( lowcost[ j] != 0 && G. arc[ k] [ j] < lowcost[ j] )
{
lowcost[ j] = G. arc[ k] [ j] ;
adjvex[ j] = k;
}
}
}
}
克鲁斯卡尔(Kruskal)算法
typedef struct
{
int begin;
int end;
int weight;
} Edge;
void MiniSpanTree_Kruskal ( MGraph G)
{
int i, n, m;
Edge edges[ MAXEDGE] ;
int parent[ MAXVEX] ;
for ( i= 1 ; i< G. numVertexes; i++ )
parent[ i] = 0 ;
for ( i= 0 ; i< G. numEdges; i++ )
{
n= Find ( parent, edges[ i] . begin) ;
m= Find ( parent, edges[ i] . end) ;
if ( n!= m)
{
parent[ n] = m;
printf ( "(%d,%d) %d" , edges[ i] . begin, edges[ i] . end, edges[ i] . weight) ;
}
}
}
int Find ( int * parent, int f)
{
while ( parent[ f] > 0 )
f= parent[ f] ;
return f;
}
最短路径
迪杰斯特拉(Dijkstra)算法
#define MAXVEX 9
#define INFINITY 65535
typedef int Patharc[ MAXVEX] ;
typedef int ShortPathTable[ MAXVEX] ;
void ShortestPath_Dijkstra ( MGraph G, int v0, Patharc * P, ShortPathTable * D)
{
int v, w, k, min;
int final[ MAXVEX] ;
for ( v= 0 ; v< G. numVertexes; v++ )
{
final[ v] = 0 ;
( * D) [ v] = G. arc[ v0] [ v] ;
( * P) [ v] = 0 ;
}
( * D) [ v0] = 0 ;
final[ v0] = 1 ;
for ( v= 1 ; v< G. numVertexes; v++ )
{
min= INFINITY;
for ( w= 0 ; w< G. numVertexes; w++ )
{
if ( ! final[ w] && ( * D) [ w] < min)
{
k= w;
ming= ( * D) [ w] ;
}
}
final[ k] = 1 ;
for ( w= 0 ; w< G. numVertexes; w++ )
{
if ( ! final[ w] && ( min+ G. arc[ k] [ w] < ( * D) [ w] ) )
{
( * D) [ w] = min+ G. arc[ k] [ w] ;
( * P) [ w] = k;
}
}
}
}
弗洛伊德(Floyd)算法
typedef int Pathmatrix[ MAXVEX] [ MAXVEX] ;
typedef int ShortPathTable[ MAXVEX] [ MAXVEX] ;
void ShortestPath_Floyd ( MGraph G, int v0, Patharc * P, ShortPathTable * D)
{
int v, w, k;
for ( v= 0 ; v< G. numVertexes; ++ v)
{
for ( w= 0 ; w< G. numVertexes; ++ w)
{
( * D) [ v] [ w] = G. matrix[ v] [ w] ;
( * P) [ v] [ w] = w;
}
}
for ( k= 0 ; l< G. numVertexes; ++ k)
{
for ( v= 0 ; v< G. numVertexes; ++ v)
{
for ( w= 0 ; w< G. numVertexes; ++ w)
{
if ( ( * D) [ v] [ w] > ( * D) [ v] [ k] + ( * D) [ k] [ w] )
{
( * D) [ v] [ w] = ( * D) [ v] [ k] + ( * D) [ k] [ w] ;
( * P) [ v] [ w] = ( * P) [ v] [ k] ;
}
}
}
}
}
for ( v= 0 ; v< G. numVertexes; v++ )
{
for ( w= v+ 1 ; w< G. numVertexes; w++ )
{
printf ( "v%d-v%d weight: %d" , v, w, D[ v] [ w] ) ;
k= P[ v] [ w] ;
printf ( " path: %d" , v) ;
while ( k!= w)
{
printf ( "-> %d" , k) ;
k= P[ k] [ w] ;
}
printf ( " -> %d\n" , w) ;
}
printf ( "\n" ) ;
}
拓扑排序
是什么:有向图中,若从顶点vi 到vj 有一条路径,则在顶点序列中点点vi 必须在vj 之前,这样的顶点序列为一个拓扑序列 。拓扑排序 就是对一个有向图构造拓扑序列的过程。 干什么:在一个表示工程的有向图中,顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称为AOV网 。如果此网的全部顶点都被输出,则说明它是不存在回路的AOV网。一个不存在回路的AOV网,我们可以将它应用在各种各样的工程或项目的流程图中。 拓扑排序算法:从AOV网中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧,继续重复此步骤,直到输出全部顶点或者AOV中不存在入度为0的顶点为止。 这个图所需要使用的数据结构:由于要删除顶点,用邻接表会更加方便。考虑道算法过程中始终要查找入度为0的顶点,我们在原来顶点表结点结构中,增加一个入度域in。
typedef int Status;
#define MAXEDGE 50
typedef struct EdgeNode
{
int adjvex;
int weight;
struct EdgeNode * next;
} EdgeNode;
typedef struct VertexNode
{
int in;
int data;
EdgeNode * firstedge;
} VertexNode, AdjList[ MAXVEX] ;
typedef struct
{
AdjList adjList;
int numVertexes, numEdges;
} GraphAdjList, * GraphAdjList;
Status TopologicalSort ( GraphAdjList GL)
{
EdgeNode * e;
int i, k, gettop;
int top= 0 ;
int count= 0 ;
int * stack;
stack= ( int * ) malloc ( GL-> numVertexes* sizeof ( int ) ) ;
for ( i= 0 ; i< GL-> numVertexes; i++ )
if ( GL-> adjList[ i] . in == 0 )
stack[ ++ top] = i;
while ( top!= 0 )
{
gettop= stack[ top-- ] ;
printf ( "%d ->" , GL-> adjList[ gettop] . data) ;
count++ ;
for ( e= GL-> adjList[ gettop] . firstedge; e; e= e-> next)
{
k= e-> adjvex;
if ( ! ( -- GL-> adjList[ k] . in) )
stack[ ++ top] = k;
}
}
if ( count < GL-> numVertexes)
return ERROR;
else
return OK;
}
关键路径
是什么:从源点到汇点具有最大长度的路径。 干什么:AOE网是要建立在活动之间制约关系没有矛盾的基础上,再来分析整个完整工程至少需要多少时间,或者为缩短完成工程所需时间,应当加快哪些活动等问题。AOE网 是用有向图的边表示活动的网。 关键路径算法原理: 只需找到所有活动的最早开始时间和最晚开始时间,并且比较它们,如果相等就意味着此活动是关键活动,活动间的路径为关键路径。如果不等,则就不是。 因此,定义参数: ①事件的最早发生时间etv(earliest time of vertex)。 ②事件的最晚发生时间ltv(latest time of vertex)。 ③活动的最早开工时间ete(earliest time of edge)。 ④活动的最晚开工时间lte(lastest time of edge)。 我们可以根据1、2求得3、4,然后再根据ete[k]和lte[k]是否相等来判断弧ak 是否是关键路径。 将AOE网转化为邻接表,弧链表增加weight域,用来存储弧的权值。 求事件的最早发生时间etv的过程,就是问哦们从头至尾找拓扑序列的过程,因此,在求关键路径之前,需要先调用一次拓扑序列算法的代码来计算etv和拓扑序列列表。
typedef struct EdgeNode
{
int adjvex;
int weight;
struct EdgeNode * next;
} EdgeNode;
typedef struct VertexNode
{
int in;
int data;
EdgeNode * firstedge;
} VertexNode, AdjList[ MAXVEX] ;
typedef struct
{
AdjList adjList;
int numVertexes, numEdges;
} GraphAdjList, * GraphAdjList;
int * etv, * ltv;
int * stack2;
int top2;
Status TopologicalSort ( GraphAdjList GL)
{
EdgeNode * e;
int i, k, gettop;
int top= 0 ;
int count= 0 ;
int * stack;
stack= ( int * ) malloc ( GL-> numVertexes* sizeof ( int ) ) ;
for ( i= 0 ; i< GL-> numVertexes; i++ )
if ( GL-> adjList[ i] . in == 0 )
stack[ ++ top] = i;
top2= 0 ;
etv= ( int * ) malloc ( GL-> numVertexes* sizeof ( int ) ) ;
for ( i= 0 ; i< GL-> numVertexes; i++ )
etv[ i] = 0 ;
stack2= ( int * ) malloc ( GL-> numVertexes* sizeof ( int ) ) ;
while ( top!= 0 )
{
gettop= stack[ top-- ] ;
printf ( "%d ->" , GL-> adjList[ gettop] . data) ;
count++ ;
stack2[ ++ top2] = gettop;
for ( e= GL-> adjList[ gettop] . firstedge; e; e= e-> next)
{
k= e-> adjvex;
if ( ! ( -- GL-> adjList[ k] . in) )
stack[ ++ top] = k;
if ( ( etv[ gettop] + e-> weight) > etv[ k] )
etv[ k] = etv[ gettop] + e-> weight;
}
}
if ( count < GL-> numVertexes)
return ERROR;
else
return OK;
}
void CriticalPath ( GraphAdjList GL)
{
EdgeNode * e;
int i, gettop, k, j;
int ete, lte;
TopologicalSort ( GL) ;
ltv= ( int * ) malloc ( GL-> numVertexes* sizeof ( int ) ) ;
for ( i= 0 ; i< GL-> numVertexes; i++ )
ltv[ i] = etv[ GL-> numVertexes- 1 ] ;
while ( top2!= 0 )
{
gettop= stack2[ top2-- ] ;
for ( e= GL-> adjList[ gettop] . firstedge; e; e= e-> next)
{
k= e-> adjvex;
if ( ltv[ k] - e-> weight< ltv[ gettop] )
ltv[ gettop] = ltv[ k] - e-> weight;
}
}
for ( j= 0 ; j< GL-> numVertexes; j++ )
{
for ( e= GL-> adjList[ j] . firstedge; e; e= e-> next)
{
k= e-> adjvex;
ete= etv[ j] ;
lte= ltv[ k] - e-> weight;
if ( ete== lte)
printf ( "<v%d,v%d> length:%d," ,
GL-> adjList[ j] . data, GL-> adjList[ k] . data, e-> weight) ;
}
}
}
总结
各种定义 存储结构一共五种。十字链表是针对有向邻接图邻接表结构的优化,邻接多重表是针对无向图邻接表结构的优化。边集数组更多考虑的是对边的关注。通常稠密图,或读存数据较多,结构修改较少的图,用邻接矩阵要更合适,反之则应该考虑邻接表。 图的遍历分为深度和广度两种。 图的三种应用:最小生成树、最短路径和有向无环图。