数据结构---图
1、图的定义
- 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
- 无向边:若顶点V1和V2之间的边没有方向,则称这条边为无向边,用无序偶对(V1,V2)来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。
- 有向边:若从顶点V1到V2的边有方向,则称这条边为有向边,也称为弧(Arc)。如果图中任意两个顶点之间的边都是有向边,则称该图为有向图。
2、图的存储结构
- 邻接矩阵
- 图的邻接矩阵存储方式是用两个数组来表示图,一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
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->nummVertexes; i++)
for(j = 0; j < G->numVertexes; j++)
G->arc[i][j] = INFNITY; /*邻接矩阵初始化*/
for(k = 0; k < G->numVertexes; k++)
{
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]; /*因为是无向图,矩阵对称*/
}
}
-邻接表
重点关注顶点的操作:
- 结点定义的代码:
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->numVertexes; 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; /*邻接序号为i*/
e->next = G->adjList[j].firstedge; /*将e指针指向当前顶点指向的结点*/
G->adjList[j].firstedge = e; /*将当前顶点的指针指向e*/
}
}
- 十字链表
- 十字链表的好处就是因为把邻接表和逆邻接表整合在了一起,这样既容易找到以v1为尾的弧,也容易找到以v1为头的弧,因而容易求得顶点的出度和入度。
- 邻接多重表
重点关注边的操作:
- 邻接多重表与邻接表的差别,仅仅是在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。
- 边集数组
- 边集数组是由两个一维数组组成。一个是存储顶点的信息;另一个是存储边的信息,这个边数组每个数组元素由一条边的起始下标(begin)、终点下标(end)和权(weight)组成。
3、图的遍历
- 深度优先遍历(Depth_First_Search,DFS)
/*邻接表的深度优先递归算法*/
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);
}
-广度优先遍历(Breadth_First_Search,BFS)
/*邻接表的广度遍历算法*/
void BFSTraverse(GraphAdjList GL)
{
int i;
EdgeNode *p;
Queue Q;
for(int i = 0; i < GL->numVertexes; i++)
visited[i] = FALSE;
InitQueue(&Q); /*初始化一辅助用的队列*/
for(int 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); /*将队中元素出队列,赋值为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; /*指针指向下一个邻接点*/
}
}
}
}
}
4、最小生成树
- 我们把构造联通网的最小代价生成树称为最小生成树(Minimum Cost Spanning Tree)。
-普里姆(Prim)算法
/*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 = 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; /*将当前最小值的下标存入k*/
}
j++;
}
printf("(%d, %d)", adjvex[k], k); /*打印当前顶点边中权值最小边*/
lowcost[k] = 0; /*将当前顶点的权值设置为0,表示此点点已经完成任务*/
for(j = 1; j < G.numVertexes; j++)
{
if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j])
{/*若下标为k顶点各边权值小于此前这些顶点未被加入生成树权值*/
lowcost[j] = G.arc[k][j]; /*将较小权值存入lowcost*/
adjvex[j] = k; /*将下标为k的顶点存入adjvex*/
}
}
}
}
-克鲁斯卡尔(Kruskal)算法
- edge边集数组结构定义代码:
/*对边集数组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]; /*定义一数组用来判断边与边是否形成回路*/
for(int i = 0; i < G.numVertexes; i++)
parent[i] = 0; /*初始化数组值为0*/
for(int 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;
}
5、最短路径
-迪杰斯特拉(Dijkstra)算法
#define MAXVEX 9
#define INFINITY 65535
typedef int Pathmatrirx[MAXVEX]; /*用于存储最短路径下标的数组*/
typedef int ShortPathTable[MAXVEX]; /*用于存储到各点最短路径的权值和*/
/*P[V]的值为前驱顶点下标,D[V]表示v0到v的最短路径长度和*/
void ShortestPath_Dijkstra(MGraph G, int v0, Pathmatrirx *P, ShortPathTable *D)
{
int v, w, k, min;
int final[MAXVEX]; /*final[w] = 1表示求得顶点v0至Vw的最短路径*/
for(v = 0; v < G.numVertexes; v++)
{
final[v] = 0; /*全部顶点初始化为未知最短路径状态*/
(*D)[v] = G.matirx[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; /*将目前找到的最近的顶点置为0*/
for(w = 0; w < G.numVertexes; w++) /*修正当前最短路径及距离*/
{
/*如果经过v顶点的路径比现在这条路径的长度短的话*/
if(!final[w] && (min + G.matirx[k][w] < (*D)[w]))
{
/*说明找到了更短的路径,修改D[w]和P[w]*/
(*D)[w] = min + G.matirx[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的顶点*/
}
}
}
}
}
拓扑排序
- 在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的有限关系,这样的有向图为顶点表示活动的网,我们称为AVO网(Activity On Vertex Network)。AOV网中不能存在回路。
- 设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(GraphAdiList GL)
{
EdgeNode *e;
int i, k, gettop;
int top = 0; /*用于栈指针下标*/
int count = 0; /*用于统计输出顶点的个数*/
int *stack; /*建栈存储入度为0的顶点*/
stack = (int *)malloc(GL->numVerexes * 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;
}