第7章 图(Graph)
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
无向边(Edge),用无序偶对(Vi,Vj)来表示。
有向边,也称为弧(Arc),用有序偶
图的存储结构
1、邻接矩阵(Adjacency Matrix)
邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
typedef char VertexType; /* 顶点类型应由用户定义 */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */
#define MAXVEX 100 /* 最大顶点数,应由用户定义 */
#define INFINITY 65535 /* 用65535来代表无穷大 */
typedef struct
{
VertextType vexs[MAXVEX]; /* 顶点表 */
EdgeType arc[MAXVEX][MAXVEX]; /* 邻接矩阵,可看做边表 */
int numVertexes, numEdges; /* 图中当前的顶点数和边数 */
}MGraph;
临界表顶点采用一个一维数组存储,另外对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。
顶点的所有邻接点构成一个线性表,用单链表存储,无向图为顶点的边表,有向图为顶点弧尾的出边表。
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;
对于有向图来说,邻接表只关心出度问题,入度必须要遍历整个图才能知道。有向图中采用了十字链表的存储方法。
结构定义书中没有给出,整理如下
typedef char VertexType; /* 顶点类型应由用户定义 */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */
typedef struct ArcNode /* 弧表结点 */
{
int tailvex; /* 弧起点在顶点表的下标 */
int headvex; /* 弧终点在顶点表的下标 */
struct ArcNode *headlink; /* 入边表指针域,指向终点相同的下一条边 */
struct ArcNode *taillink; /* 边表指针域,指向起点相同的下一条边 */
EdgeType weight; /* 用于存储权值,对于非网图可以不需要 */
}ArcNode;
typedef struct VertexNode /* 顶点表结点 */
{
VertexType data; /* 顶点域,存储顶点信息 */
ArcNode* firstin; /* 表示入边头指针,指向该顶点的入边表中的第一个结点 */
ArcNode* firstout; /* 表示出边头指针,指向该顶点的出边表中的第一个结点 */
}VertexNode, OrtList[MAXVEX];
typedef struct
{
OrtList ortList;
int numVertexes,numEdges; /* 图中当前顶点数和边数 */
}GraphOrtList;
4、邻接多重表(Adjacency MulList)
邻接多重表示对于无向图的邻接表的优化。
typedef char VertexType; /* 顶点类型应由用户定义 */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */
typedef emnu{ unvisited,visited} VisitIf;
typedef struct EBox
{
VisitIf mark: /*访问标记*/
int ivex,jvex; /*该边依附的两个顶点的位置*/
struct EBox ilink, jlink; /*分别指向依附这两个顶点的下一条边*/
InfoType info; /*该边信息指针*/
}EBox;
typedef struct VexBox
{
VertexType data;
EBox fistedge; /*指向第一条依附该顶点的边*/
}VexBox, Adjmulist[MAXVEX];
typedef struct
{
Adjmulist adjmulist;
int numVertexes,numEdges; /* 无向图中当前顶点数和边数 */
}AMLGraph;
边集数组是有两个一维数组构成。一个是存储顶点的信息;另一个是存储边的信息,这个边数组每个数据元素 有一条边的起点下标(begin)、终点下标(end)和权(weight)组成。
图的遍历
深度优先遍历DFS(Depth_First_Search)
邻接矩阵的深度优先遍历算法,对于n个顶点e条边的图,要查找每个顶点的邻接点需要访问矩阵中的所有元素,因此需要O(n^2)的时间。
typedef int Boolean; /* Boolean是布尔类型, 其值是true或false */
Boolen visited[MAX]; /* 访问标志的数组 */
/* 邻接矩阵的深度优先递归算法 */
void DFS ( MGraph G, int i )
{
int j;
visited[i] = true;
printf("%c ",G.vexs[i]);
for(j=0;j
/* 邻接表的深度优先递归算法 */
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; inumVertexes; i++)
visited[i] = false;
for(i=0; inumVertexes; i++)
if (! visited[i])
DFS(GL, i);
}
邻接矩阵的广度遍历
/* 邻接矩阵的广度遍历算法 */
void BFSTraverse (MGraph G)
{
int i, j;
Queue Q;
for(i=0; i
/* 邻接表的广度遍历算法 */
void BFSTraverse(GraphAdjList GL)
{
int i;
EdgeNode *p;
Queue Q;
for(i=0; inumVertexes; i++)
visited[i] = false;
InitQueue(&Q);
for(i=0; inumVertexes; 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;
}
}
}
}
}
最小生成树:构造连通图的最小代价生成树
普利姆(Prim)算法
普利姆算法是以顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树的。
/* Prim算法生成最小生成树 */
void MiniSpanTree_Prim( MGraph G )
{
int min, i, j, k;
int adjvex[MAXVEX]; /* 保存相关顶点下标 */
int lowcost[MAXVEX]; /* 保存相关顶点间边的权值 */
lowcost[0] = 0; /* 初始化第一个权值为0, 即V0加入生成树 */
adjvex[0] = 0; /* 初始化第一个顶点下标为0 */
for(i=1; i< G.numVertexes; i++) /* 循环除下标为0外的全部顶点 */
{
lowcost[i] = G.arc[0][i]; /* 将V0顶点与之有边的权值存入数组 */
adjvex[i] = 0; /* 初始化都为V0的下标 */
}
for(i=1; i
克鲁斯卡尔(Kruskal)算法
克鲁斯卡尔以边为目标去构建最小生成树,采用图的存储结构中的边集数组结构。
/* 对边集数组Edge结构的定义 */
typedef struct
{
int begin;
int end;
int weight;
}Edge;
/* Kruskal算法生成最小生成树 */
void MiniSpanTree_Kruskal (MGraph G) /* 生成最小生成树 */
{
int i, n, m;
Edge edges[MAXEDGE]; /* 定义边集数组 */
int parent[MAXVEX]; /* 定义一数组用来判断边与边是否形成环路 */
/* 此处省略将邻接矩阵G转化为边集数组edges,并按权由小到大排列的代码 */
for (i=0; i 0)
f = parent[f];
return f;
}
对比两个算法,克鲁斯卡尔算法主要是针对边来展开,边数少时效率会非常高,所有对稀疏图有很大优势;而普利姆算法对于稠密图,即边数非常多的情况会更好一些。
最短路径
迪杰斯特拉(Dijkstra)算法
#define MAXVEX 9
#define INFINITY 65535
typedef int Pathmatirx[MAXVEX]; /* 用于存储最短路径下标的数组 */
typedef int ShortPathTable[MAXVEX]; /* 用于存储到各点最短路径的权值 */
/* Dijkstra算法,求有向图G的V0顶点到其余顶点v最短路径P[v]及带权长度D[v] */
void ShortestPath_Dijkstra(MGraph G, int v0, Pathmatirx* P, ShortPathTable* D)
{
int v,w,k,min;
int final[MAXVEX]; /* final[w]=1表示求得顶点V0至Vw的最短路径 */
for(v=0,v
typedef int Pathmatirx[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];
/* Floyd 算法,求网图G中各顶点v到其余顶点w最短路径P[v][w]及带权长度D[v][w] */
void ShortestPath_Floyd(MGraph G, Pathmatirx *P, ShortPathTable *D)
{
int v,w,k;
/*初始化D与P */
for(v=0;v (*D)[v][k]+(*D)[k][w])
{
(*D)[v][w] = (*D)[v][k]+(*D)[k][w];
(*P)[v][w] = (*P)[v][k]; /* 路径设置经过下标为k的顶点 */
}
}
}
}
}
AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系。
拓扑排序:对一个有向图构造拓扑序列的过程。
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;
/* 拓扑排序, 若GL无回路, 则输出拓扑排序序列并返回OK, 若有回路返回ERROR */
Status TopologicalSort(GraphAdjList GL)
{
EdgeNode *e;
int i,k,gettop;
int top = 0; /* 用于栈指针下标 */
int count = 0; /* 用于统计输出顶点的个数 */
int *stack; /* 建栈存储入度为0的顶点 */
stack = (int *)malloc(GL->numVertexes * sizeof(int));
for(i=0;iadjList[i].in = 0)
stack[++top] = i; /* 将入度为0的顶点入栈 */
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)) /* 将k号顶点邻接点的入度减1 */
stack[++top] = k; /* 若为0则入栈,以便于下次循环输出 */
}
}
if (count < GL->numVertexes) /* 如果count小于顶点数,说明存在环 */
return ERROR;
else
return OK;
}
int *etv, *ltv; /* 事件最早发生时间和最迟发生时间数组 */
int *stack2; /* 用于存储拓扑序列的栈 */
int top2; /* 用于stack2的指针 */
/* 拓扑排序,用于关键路径计算 */
Status TopologicalSort(GraphAdjList GL)
{
EdgeNode *e;
int i,k,gettop;
int top = 0; /* 用于栈指针下标 */
int count = 0; /* 用于统计输出顶点的个数 */
int *stack; /* 建栈存储入度为0的顶点 */
stack = (int *)malloc(GL->numVertexes * sizeof(int));
for(i=0;iadjList[i].in = 0)
stack[++top] = i; /* 将入度为0的顶点入栈 */
top2 = 0 ; /* 初始化为0 */
etv = (int *)malloc(GL->numVertexes*sizeof(int));
for(i=0;inumVertexes;i++)
etv[i] = 0; /* 初始化为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)) /* 将k号顶点邻接点的入度减1 */
stack[++top] = k; /* 若为0则入栈,以便于下次循环输出 */
if ((etv[gettop]+e->weight)>etv[k]) /* 求各顶点事件最早发生时间值 */
etv[k] = etv[gettop]+e->weight;
}
}
if (count < GL->numVertexes) /* 如果count小于顶点数,说明存在环 */
return ERROR;
else
return OK;
}
/* 求关键路径,GL为有向图,输出GL的各项关键活动 */
void CriticalPath(GraphAdjList GL)
{
EdgeNode *e;
int i,gettop,k,j;
int ete,lte; /* 声明活动最早发生时间和最迟发生时间变量 */
TopologicalSort(GL); /* 求拓扑序列,计算数组etv和stack2的值 */
ltv=(int *)malloc(GL->numVertexes*sizeof(int)); /* 事件最晚发生时间 */
for(i=0;inumVertexes;i++)
ltv[i] = etv[GL->nmVertexes-1]; /* 初始化ltv */
while(top2 != 0) /* 计算ltv */
{
gettop = stack2[top--]; /* 将拓扑序列出栈,后进先出 */
for(e = GL->adjList[gettop].firstedge; e; e->next)
{
/* 求各顶点事件的最迟发生时间ltv值 */
k = e->adjvex;
/* 求各顶点事件最晚发生时间ltv */
if (ltv[k]-e->weightweight;
}
}
/* 求ete,lte和关键活动 */
for(j=0;jnumVertexes; j++)
{
for(e=GL->adjList[j].firstedge; e; e->next)
{
k = e->adjvex;
ete = etv[j]; /* 活动最早发生时间 */
lte = ltv[k] - e->weight; /* 活动最迟发生时间 */
if(ete == lte) /* 两者相等即在关键路径上 */
{
printf(" length: %d, ",
GL->adjList[j].data,GL-.adjList[k].data,e->weight);
}
}
}
}