图的定义与术语
1、图按照有无方向分为无向图和有向图。无向图由顶点和边构成,有向图由顶点和弧构成。弧有弧尾和弧头之分。
2、如果任意两个顶点之间都存在边叫完全图,有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图。
3、图中顶点之间有邻接点、依附的概念。无向图顶点的边数叫做度,有向图顶点分为入度和出度。
4、图上的边或弧上带权则称为网。
5、图中顶点间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环,当中不重复叫简单路径。若任意两顶点都是连通的,则图就是连通图,有向则称为强连通图。
6、无向图中连通且n个顶点n-1条边叫生成树。有向图中一顶点入度为0其余顶点入度为1的叫有向树。
图的存储结构
邻接矩阵
图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组存储图中的边或弧的信息。
邻接矩阵存储的结构代码如下:
typedef char VertexType;/*顶点类型应由用户定义*/
typedef int EdgeType;/*边上的权值类型应由用户定义*/
#define MAXVEX 100//最大顶点数,由用户定义
#define INFINITY 65535//用65535来代表无限
typedef struct {
VertexType vexs[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++)/*读入numEdges条边,建立邻接矩阵*/
{
printf("输入边(vi,vj)上的下标i,小标j和权w:\n");
scanf("%d,%d,%d",&i,&j,&w);/*输入边(vi,vj)上的权w*/
G->arc[i][j]=w;
G->arc[j][i] =G->arc[i][j];/*因为是无向图,矩阵对称*/
}
}
邻接表
邻接表存储方法:
1、图中顶点用一维数组存储,每个数据元素存储指向第一个邻接点的指针,以便于查找该顶点的边信息。
2、图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。
结点定义代码:
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);/*输入边(vi,vj)上的顶点序号*/
e=(EdgeNode*)malloc(sizeof(EdgeNode));/*向内存申请空间,生成边表结点*/
e->adjvex=j;/*邻接序号为j*/
e->next=G->adjList[i].firstedge;/*将e指针指向当前顶点指向的结点*/
G->adjList[i].firstedge=e;/*将当前顶点的指针指向e*/
e=(EdgeNode*)malloc(sizeof(EdgeNode));/*向内存申请空间,生成边表结点*/
e->adjvex=i;
e->next=G->adjList[j].firstedge;/*将e指针指向当前顶点指向的结点*/
G->adjList[j].firstedge=e;/*将当前顶点的指针指向e*/
}
}
剩余的图的存储结构还有,十字链表,邻接多重表,边集数组。
图的遍历
深度优先遍历
//邻接矩阵的深度优先遍历
typedef int Boolean; /*Boolean是布尔类型,其值是TRUE或FALSE*/
Boolean visited[MAX];/*访问标志的数组*/
/*邻接矩阵的深度优先递归算法*/
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,若是连通图,只会执行一次*/
DFS(G, i);
}
/*邻接表的深度优先递归算法*/
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,若是连通图,只会执行一次*/
DFS(GL, i);
}
广度优先遍历
/*邻接矩阵的广度优先遍历*/
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);/*将队中元素出队列,赋值给i*/
for(j = 0; j< G.numVertexes; j++) {
/*判断其他顶点若与当前顶点存在边且未访问过*/
if(G.arc[i][j] == 1 && !visited[j]) {
visited[j]=TRUE;/*找到的此顶点标记已访问*/
printf("%c",G.vexs[j]);/*打印顶点*/
EnQueue(&Q, j);/*将找到的此顶点入队列*/
}
}
}
}
}
}
/*邻接表的广度优先遍历算法*/
void BFSTraverse(GraphAdjList GL) {
int i;
EdgeNode *po;
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(!visitied[p->adjvex]) {
visited[p->adjvex] = TRUE;
printf("%c", GL->adjList[p->adjvex].data);
EnQueue(&Q, p ->adjvex);/*将此顶点入队列*/
}
p=p->next;
}
}
}
}
}
最小生成树
/*prim算法生成最小生成树*/
void MiniSpanTree_Prim(MGraph G) {
int min, i , j, k;
int adjvex[MAXVEX];/*保存相关顶点下标*/
int lowcost[MAXVEX];/*保存相关顶点间边的权值*/
lowcost[0] = 0;/*初始化第一个权值为0,即V0加入生成树*/
/*lowcost的值为0,在这里就是此下标的顶点已经加入生成树*/
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 = 0; i < G.numVertexes; i++) {
min = INFINITY;/*初始化最小权值*/
j = 1; k = 0;
while(j < G.numVertexes) { /*循环全部顶点*/
if(lowcost[j] != 0 && lowcost[j] < min) {/*如果权值不为0且权值小于min*/
min = lowcost[j]; /*则让当前权值成为最小值*/
k = j;
}
j++;
}
printf("(%d,%d)",adjvex[k], k);/*打印当前顶点边中权值最小边*/
lowcost[k] = 0;/*将当前顶点的权值设置为0,表示此顶点已经完成任务*/
for(j = 1; j < G.numVertexes; j ++)/*循环所有顶点*/
{
if(lowcost[j] != && G.arc[k][j] <lowcost[j]) {
/*若小标为k顶点各边权值小于此前这些顶点未被加入生成树权值*/
lowcost[j] = G.arc[i][j];/*将较小权值存入lowcost*/
adjvex[j] = k;/*将下标为k的顶点存入adjvex*/
}
}
}
}
/*Kruskal算法生成最小生成树*/
void MiniSpanTree_Kruskal(MGraph G) /*生成最小生成树*/
{
int i, n , m;
Edge edges[MAXEDGE];/*定义边集数组*/
int parent[MAXVEX];/*定义一数组用来判断边与边是否形成回路*/
/*此处省略将邻接矩阵G转化为边集数组edges并按权有小到大排序的代码*/
for(i = 0; i < G.numVertexes; i++)
parent[i] = 0;/*初始化数组值为0*/
for(i = 0; i <G.numEdges; i++) { /*循环每一条边*/
n = Find(parent, edges[i].begin);
m = Find(parent, edges[i].end);
if(n != m)/*假如n与m不等,说明此边没有与现有生成树形成环路*/
{
parent[n] = m;/*将此边的结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中*/
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];/*用于存储最短路径下标的数组*/
typdef int ShortPathTable[MAXVEX];/*用于存储到各点最短路径的权值和*/
/*Dijkstra算法,求有向网G的V0顶点到其余顶点V最短路径P[v]及带权长度D[v]*/
/* P[v]的值为前驱顶点下标,D[v]表示V0到V的最短路径长度和。*/
void ShortestPath_Dijkstra (MGraph G,int v0, Patharc *p, ShortPathTable *D) {
int v, w, k ,min;
int final[MAXVEX];/*final[w]=1表示求得顶点V0至Vw的最短路径*/
for(v=0; v< G.numberVertexes; v++)/*初始化数据*/
{
final[v]= 0;/*全部顶点初始化为未知最短路径状态*/
(*D)[v]= G.arc[v0][v];/*将与V0点有连线的顶点加上权值*/
(*P)[v]=0;/*初始化路径数组P为0*/
}
(*D)[v0] = 0;/*V0至V0路径为0*/
final[v0] = 1;/*V0至V0不需要路径*/
/*开始主循环,每次求得V0到某个V顶点的最短路径*/
for(v=1; v< G.numVertexes; v++) {
min = INFINITY;/*当前所知离V0顶点的最近距离*/
for(w=0; w<G.numVertexes; w++)/*寻找离V0最近的顶点*/
{
if(!final[w] && (*D)[w]<min){
k = w;
min=(*D)[w];/*w顶点离V0顶点更近*/
}
}
final[k] = 1;/*将目前找到的最近的顶点置为1*/
for(w=0; w < G.numVertexes; w++) /*修正当前最短路径及距离*/
{
/*如果经过V顶点的路径比现在这条路径的长度短的话*/
if(!final[w]&& (min + G.arc[k][w] < (*D)[w]))
{/*说明找到了更短的路径,修改D[w]和P[w]*/
(*D)[w] = min+G.arc[k][w];/*修改当前路径长度*/
(*P)[w]= k;
}
}
}
}
Floyd算法
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;
for(v = 0; v < G.numVertexes; ++v)/*初始化D与P*/
{
for(w = 0; w < G.numVertexes; ++w)
{
(*D)[v][w] = G.matirx[v][w];/*D[v][w]值即为对应点间的权值*/
(*P)[v][w] = w;/*初始化P*/
}
}
for(k = 0; k < 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])
{/*如果经过下标为k顶点路径比原两点间路径更短*/
/*将当前两点间权值设为更小的一个*/
(*D)[v][w] = (*D)[v][k] + (*D)[k][w];
(*P)[v][w]= (*P)[v][k];/*路径设置经过下标为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");
}
拓扑排序
设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列v1,v2,·····,vn,满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前。则我们称这样的顶点序列为一个拓扑序列。
所谓拓扑排序,其实就是对一个有向图构造拓扑序列的过程。
拓扑排序算法
结构代码:
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; i < GL->numVertexes; i++)
if(GL->adjList[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; i < GL.numVertexes; i++)
if(0 == GL->adjList[i].in)
stack[++top] = i;
top2= 0;/*初始化为0*/
etv=(int *)malloc(GL->numVertexes * sizeof(int));/*事件最早发生时间*/
for(i = 0; i < GL->numVertexes; i++)
etv[i]=0;/*初始化为0*/
stack2=(int *)malloc(GL->numVertexes * sizeof(int));/*初始化*/
while(top != 0) {
gettop=stack[top--];
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;
}
/*求关键路径,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; i < GL->numVertexes; i++)
ltv[i] = etv[GL->numVertexes - 1];/*初始化ltv*/
while(top2 ! = 0)/*计算ltv*/
{
gettop=stack2[top2--];/*将拓扑序列出栈,后进先出*/
for(e = GL->adjList[gettop].firstedge; e; e = e->next) {
/*求各顶点事件的最迟发生时间ltv值*/
k = e->adjvex;
if(ltv[k]- e->weight < ltv[gettop])/*求各顶点事件最晚发生时间ltv*/
ltv[gettop] = ltv[k] - e->weight;
}
}
for(j=0; j < GL->numVertexes; j++)/*求ete,lte和关键活动*/
{
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);
}
}
}
网友评论