目录
一、图的最小生成树(未完结)
构造最小生成树的算法,其中Prim算法与Kruskal算法,都是利用MST的性质,
最小生成树需要解决的两个问题;
尽可能选取权值最小的边,且不能构成回路;
选取n-1条边,连接图中所有的 n-1条边;
图的生成树:
是图的一个极小连通子图:
含有图中全部的n个顶点;
含有n-1条边;
图的生成树不唯一;
图的最小生成树:
在一个连通网的所有生成树中,n-1条边的权值之和最小,也称最小代价生成树。
MST的性质:
设图G=< V,R >是一个带权的连通图,集合U 是顶点集V 的一个非空子集。构建生成树时需要一条边连接集合 U 和 集合 V - U。
如果 (u ,v)∈ R,u ∈ U,v ∈ V - U,且边(u,v)具有一条权值最小的边,那么一定存在一颗包含边(u,v)的最小生成树。
Prim算法:
一句话概括:从一个顶点 V0 出发,从图中找与其连接的权值最小边的顶点,加入到顶点集合 U中,然后继续从V-U集合中找连接U集合最小权值的边,如此重复直到所有顶点都加入到生成树的顶点集合 U中。
具体地:
记 V 是连通网的顶点集合,U 是生成树的顶点集合,TE是求得生成树的边集。
开始时
1,U={V0},TE为空;
2,将V-U的最小权值的边纳入到TE中,对应的顶点纳入到U
3,重复2,直到U=V。
算法实现:
定义一个辅助数组 shortEdge[],用于存放V-U集合中的顶点以及权值的信息,
其中 adjvex用于存放顶点的序号,lowcost用于存放该顶点与集合U中连接的最小权值。
lowcost = 0 ,v∈U, 此顶点已经存放到集合U中
lowcost = min| cost{u,v}| |u∈U,v∈V-U} 从不在集合U中找连接的且权值最小的
struct { int adjvex; int lowcost; }shorEdge[MAXVEX];
Kruskal算法:
Kruskal算法的贪心准则是:从剩下的边中选择权值最小的且不会产生回路的边加入生成树的边集中。
基本思想:
先构造一个只含n个顶点的子图SG,然后从权值最小的边中,且不会产生回路,则在SG中加入该边,依次按照边权值递增的顺序,选择合适的边进行添加直到n-1条边为止。
算法实现时:
对于连通网G,先将所有边按照权值从小到大排序,然后将n个顶点看成n个独立的集合。从小到大选择边,所选的边需要满足其两个顶点都不在同一个顶点集合中,将该边加入到边集中,并且将两个顶点的顶点集合合并,如果该边的两个顶点集合重复(产生回路),则删除此边。重复,直到所有顶点都在同一个集合中,则选择出n-1条边。
二、拓扑排序
AOV-网:
假设有向图为一个工程,每个顶点代表一个活动,弧表示活动之间的优先关系,且没有回路,则称此有向无环图为:“顶点表示活动的网”,简称“AOV-网”。
拓扑排序:
对于AOV-网,存在满足以下性质的线性序列为拓扑序列。
1,网中所有顶点都在此序列中。
2,若顶点A和B存在一条路径,在线性序列中,A一定排在B前。
如何实现拓扑排序?
1,在有向图中找到无前驱的顶点,并输出
2,删除此顶点及以此顶点为结尾的弧(也就是删掉此弧)。
算法思想:
用邻接表存储有向图,无前驱的顶点即其入度为0,所以需要知道每个顶点的入度,并用一个数组用于存放各自顶点的入度,或在邻接表中的顶点表增加一个域用于存放其入度。
找到入度为0的顶点后,需要删除此顶点,并输出,然后删除此弧,那么其邻接表中的边表的所有顶点的入度都需要减 1 ,此时边表中的顶点入度变为0的顶点继续重复操作。在整个过程中,需要用栈或者队列单独存放入度为0的点,便于进行下一步操作时不用再次查找刚刚入度为0的点。
算法实现思想:
1,栈或队初始化,获得每个顶点的入度信息,定义一个count 累加器初始化
2,找到入度为0 的顶点,入栈或入队
3,当栈或队不为空时循环操作:
出栈或出队,输出顶点,count累加器加 一;
将顶点的邻接点的入度都减 一;
将新的入度为0 的顶点入栈;
4,根据累加器count,与顶点的总个数进行比较,判断是否有回路,
如果有回路,操作3的循环就会停止。
算法实现:
实例:
运行结果:
执行代码:
#include<stdio.h>
#include<stdlib.h>
#define MAXVEX 20
//====================================== 邻接表结构建立图 =============================
//建立邻接表的结构
typedef struct ArcNode
{
int adjvex;
struct ArcNode* next;
}ArcNode;
typedef struct VertexNode
{
char vexdata;
int indegree; //顶点边表中存放入度信息
ArcNode* head;
}VertexNode;
typedef struct
{
VertexNode vertex[MAXVEX];
int vexnum;
int arcnum;
}Adjlist;
ArcNode* Init_Arc()
{
ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));
p->adjvex = 0;
p->next = NULL;
return p;
}
Adjlist* Init_G()
{
Adjlist* G=(Adjlist*)malloc(sizeof(Adjlist));
G->vexnum = 0;
G->arcnum = 0;
return G;
}
int include(Adjlist *G, char v)
{
for (int i = 1; i <= G->vexnum; i++)
{
if (G->vertex[i].vexdata == v)
return i;
}
return 0;
}
Adjlist* Create()
{
int i, j, k;
char vex1, vex2;
Adjlist* G=Init_G();
printf("------请输入图顶点个数与弧数------\n");
scanf("%d,%d", &G->vexnum, &G->arcnum);
printf("------ 请输入%d个顶点 ------\n",G->vexnum);
for (i = 1; i <= G->vexnum; i++)
{
scanf(" %c", &G->vertex[i].vexdata);
G->vertex[i].head = NULL;
}
printf("------ 请输入%d条弧 -------\n", G->arcnum);
for (k = 1; k <= G->arcnum; k++)
{
printf("第%d条弧\n", k);
scanf(" %c", &vex1);
scanf(" %c", &vex2);
i = include(G, vex1);
j = include(G, vex2);
ArcNode* p1 = Init_Arc(); //头插法,有向图
p1->adjvex = j;
p1->next = G->vertex[i].head;
G->vertex[i].head = p1;
/*ArcNode* p2 = Init_Arc(); //无向图多一个步操作
p2->adjvex = i;
p2->next = G.vertex[j].head;
G.vertex[j].head = p2;*/
}
return G;
}
//====================================== 查找所有顶点的入度信息 ==========================
void Find(Adjlist* G)
{
int i;
ArcNode* p;
for (i = 1; i <= G->vexnum; i++)
G->vertex[i].indegree = 0;
for (i = 1; i <= G->vexnum; i++)
{
for (int j = 1; j <= G->vexnum; j++) //邻接表需要遍历整个表查找入度信息
{
p = G->vertex[j].head;
while (p != NULL)
{
if (p->adjvex == i)
G->vertex[i].indegree++;
p = p->next;
}
}
}
}
//===================================== 建立队列 ======================================
typedef struct QNode
{
int data;
struct QNode* next;
}QNode;
typedef struct LQNode
{
QNode* front;
QNode* rear;
}LQNode;
//初始化
LQNode* Init_Q()
{
LQNode* p;
QNode* q;
p = (LQNode*)malloc(sizeof(LQNode));
q = (QNode*)malloc(sizeof(QNode));
q->next = NULL;
p->front = p->rear = q;
return p;
}
//入队
void Push(LQNode* p, int v)
{
QNode* q = (QNode*)malloc(sizeof(QNode));
if (q == NULL)
return;
q->next = NULL;
q->data = v;
p->rear->next = q;
p->rear = q;
}
//出队
int Pop(LQNode* p)
{
int v;
if (p->front == p->rear)
printf("栈为空");
QNode* q;
q = p->front->next;
v = q->data;
p->front->next = q->next;
free(q);
if (p->front->next == NULL)
p->rear = p->front;
return v;
}
//=========================================== 拓扑排序 ==================================
int TopoSort(Adjlist* G)
{
LQNode* Q = Init_Q();
int count = 0;
int i, j;
ArcNode* q;
for (i = 1; i<=G->vexnum; i++)
{
if (G->vertex[i].indegree == 0)
Push(Q, i);
}
while (Q->front!=Q->rear)
{
int k=Pop(Q);
printf("%c ", G->vertex[k].vexdata);
count++;
q = G->vertex[k].head;
while (q != NULL)
{
j = q->adjvex;
G->vertex[j].indegree--;
if (G->vertex[j].indegree == 0)
Push(Q, j);
q = q->next;
}
}
printf("\n");
if (count < G->vexnum)
return 0;
else
return 1;
}
main()
{
printf("============== 邻接表图的建立开始 ===============\n");
Adjlist* G = Create();
printf("============== 图的建立完成 ===============\n");
printf("============== 录入顶点的入度信息 ===============\n");
Find(G);
printf("============== 入度信息录入完成 ===============\n");
printf("============== 拓扑排序开始 ===============\n");
int temp=TopoSort(G);
if (temp == 0)
printf("---- 此有向图有回路 ---\n");
printf("============= 拓扑排序结束 ===============\n");
}
算法实现中的问题:
对于邻接表存储,如果要找到各顶点的入度信息,需要知道之前写过的邻接表的弊端:
如果采用逆邻接表,查找各顶点的入度,很方便,但是在后续查找顶点的邻接点时需要遍历整个邻接表。
如果采用邻接表存储,查找各顶点的入度需要遍历整个表,但是查找邻接点时,只用遍历其边表即可。
两种查找入度信息的方法:
邻接表存储时查找入度信息:
void Find(Adjlist* G) { int i; ArcNode* p; for (i = 1; i <= G->vexnum; i++) G->vertex[i].indegree = 0; for (i = 1; i <= G->vexnum; i++) { for (int j = 1; j <= G->vexnum; j++) { p = G->vertex[j].head; while (p != NULL) { if (p->adjvex == i) G->vertex[i].indegree++; p = p->next; } } } }
逆邻接表查找顶点入度信息:
void Find(Adjlist* G) { int i; ArcNode* p; for (i = 1; i <= G->vexnum; i++) G->vertex[i].indegree = 0; for (i = 1; i <= G->vexnum; i++) { p = G->vertex[i].head; while (p != NULL) { G->vertex[i].indegree++; p = p->next; } } }
三、关键路径(未完结)
AOE-网
顶点表示事件,弧表示活动,弧上的权值表示活动持续的时间,称这样的有向网为“弧表示活动的网”,简称AOE-网。
AOE-网的性质:
1、顶点所代表的的事件发生后,从该顶点发出的所有弧所代表的活动才能开始。
2、到达某顶点的所有弧所代表的活动都结束后,该顶点所代表的事件才能发生。
其中,网中只有一个入度为0的点,为“源点”,仅有一个出度为0的点,为“汇点”。
关键路径
AOE-网中具有最长的路径长度的路径为关键路径,所代表的活动为关键活动。
活动的最早开始时间为关键活动时间的总和,最晚开始时间为最短的路径时间总和。当最早开始时间与最晚开始时间相等时,该活动为关键活动。(分析关键路径的目的是,确认哪些是关键活动,提高关键活动的功效,缩短整个工期。)
算法思想:
1、对顶点进行拓扑排序,求出每个事件的最早开始时间。
2、对顶点进行逆拓扑排序,求出每个事件的最晚开始时间。
3、计算每个活动的最早开始时间与最晚开始时间。
4、找出关键活动,也就是活动的最早开始与最晚开始时间相等。
四、最短路径
最短路径问题分为两种:
1、单元最短路径问题(从图中某顶点到达其余顶点的最短路径)。
2、每对顶点之间的最短路径问题。
每对顶点之间的最短路径问题:(Floyd算法)
算法思想:
Floyd算法属于动态规划算法,先求子问题的解,然后由子问题的解得到原问题的解。
用矩阵记录路径的长度,(有弧相连直接到达的路径),然后进行n次试探。
一个一个的试是否存在Vk,满足在Vi和Vj的中间,然后判断加入Vk后的路径长度与不加入时Vi——Vj的长度的大小。
void Floyd(AdjMatrix G) { int dist[MAXVEX][MAXVEX]; int path[MAXVEX][MAXVEX]; int i,j,k; for(i=1;i<=G->vexnum;i++) { for(j=1;j<=G->vexnum;j++) { dist[i][j]=G->arcs[i][j]; if(dist[i][j]!=INFINITY) path[i][j]=G->vex[i]+G->vex[j]; else path[i][j]=""; } } for(i=1;i<=G->vexnum;i++) { for(j=1;j<=G->vexnum;j++) { for(k=1;k<=G->vexnum;k++) { if(dist[i][k]+dist[k][j]<dist[i][j]) { dist[i][j]=dist[i][k]+dist[k][j]; path[i][j]=path[i][k]+path[k][j]; } } } }
单元最短路径问题:(Dijkstra算法)
常用算法为:Dijkstra算法(迪杰斯特拉),按照路径长度递增的顺序分别产生到达各顶点最短路径的贪心算法。
算法实现中,找到图中各顶点的最短路径分两种情况:
1、起始点到该顶点。
2、起始点经过某中间顶点(已经找到最短路径的顶点)或多个然后到达该顶点。
算法思想:
集合S中存放已经找到最短路径的顶点,V-S中存放未找到的顶点。
1、初始化
集合S中存放源点V0,集合V-S存放其余顶点,V0到达V-S中所有顶点的路径长度为其权值(与源点有弧)或∞(没有弧相连)。
2、从集合V-S中按照路径长度递增的顺序找到最短的Vk,然后加入到集合S中。
3、集合S中加入Vk后,寻找下一条最短路径,必须修改V0到达V-S中剩余的顶点的最短路径。因为,加入Vk后,V0到达其他顶点比没有加入Vk的路径短,则更新V0到该顶点的路径长度(也就是,存在一个顶点,V0通过Vk顶点到达该顶点的路径长度比最初长,则更新路径长度)。
4、重复以上操作,直到V-S中的所有顶点都加入到集合S中。
算法实现
void Dijkstra(AdjMatrix* G, char start1,char end1) { int mindist, i, j, k, t = 1,num=1; int dist[MAXVEX]={0}; //dist数组用于存放源点到该顶点的最短路径长度 int sign[MAXVEX]={0}; //sign数组用于标记是否加入到集合S中 int path[MAXVEX][MAXVEX]={0}; //path二维数组存放到达各顶点的路径 int start = LocateVex(G, start1); int end = LocateVex(G, end1); //初始化 for (i = 1; i < MAXVEX; i++) { dist[i] = 0; sign[i] = 0; for (j = 1; j < MAXVEX; j++) path[i][j] = 0; } for (i = 1; i <= G->vexnum; i++) //dist数组此时存放源点开始到达各顶点的路径长度 { dist[i] = G->arcs[start][i]; if (G->arcs[start][i] != INFINITY) path[i][1] = start; } sign[start] = 1; //标记加入到集合S中 //num控制循环次数,直到顶点都加入到自己理解的集合S中 while (num < G->vexnum) { //第二步找最短的顶点 mindist = INFINITY; for (i = 1; i <= G->vexnum; i++) { if (!sign[i] && dist[i]< mindist) { k = i; mindist = dist[i]; } } if (mindist == INFINITY) return; sign[k] = 1; //第三步集和S中加入新的顶点后修改权值 for (j= 1; j <= G->vexnum; j++) { //判断加入Vk后,是否存在源点可以通过中间顶点Vk到达Vj的路径比不加入Vk更短 if (!sign[j] && G->arcs[k][j] < INFINITY && dist[k] + G->arcs[k][j] < dist[j]) { dist[j] = dist[k] + G->arcs[k][j]; //如果存在一个顶点Vj,更新j顶点的路径,将Vk的路径复制到Vj中,并加入Vk的路径 t = 1; while (path[k][t]!=0) { path[j][t] = path[k][t]; t++; } path[j][t] = k; } } num++; } }
实例
执行代码
#define _CRT_SECURE_NO_WARNINGS 1 #pragma warning(disable:6031) #include<stdio.h> #include<stdlib.h> #define MAXVEX 20 //表示顶点的最大个数 #define INFINITY 200 //表示顶点的权值的极大值 无穷大 typedef struct { int arcs[MAXVEX][MAXVEX]; char vex[MAXVEX]; int vexnum; int arcnum; int weight; }AdjMatrix; //对图的顶点初始化 AdjMatrix* Init() { AdjMatrix* G = (AdjMatrix*)malloc(sizeof(AdjMatrix)); if (G == NULL) printf("申请内存错误"); G->vexnum = 0; G->arcnum = 0; G->weight = 0; for (int i = 0; i < MAXVEX; i++) //对矩阵初始化为极值 { for (int j = 0; j < MAXVEX; j++) G->arcs[i][j] = INFINITY; } for (int i = 0; i < MAXVEX; i++) G->vex[i] = 0; return G; } //=================================== 邻接矩阵存储结构创建图 ========================= //查找匹配的顶点在矩阵中的位置 int LocateVex(AdjMatrix* G, char v) { int i; for (i = 1; i <= G->vexnum; i++) { if (G->vex[i] == v) return i; } return 0; } AdjMatrix* Create() { AdjMatrix* G = Init(); int i, j, k; char vex1, vex2; printf("----输入图的顶点数与边数-----\n"); scanf("%d%d", &G->vexnum, &G->arcnum); printf("-----依次输入图中%d个顶点------\n", G->vexnum); for (i = 1; i <= G->vexnum; i++) { scanf(" %c", &(G->vex[i])); } printf("-----输入图中%d条边-----\n", G->arcnum); for (k = 1; k <= G->arcnum; k++) { printf("第%d条边:\n ", k); scanf(" %c", &vex1); scanf(" %c", &vex2); printf("该边的权值:\n"); scanf("%d", &G->weight); i = LocateVex(G, vex1); j = LocateVex(G, vex2); G->arcs[i][j] = G->weight; } printf("=================》邻接矩阵建立图完成 ================《\n"); return G; } void print(AdjMatrix* G) { int i, j; for (i = 1; i <= G->vexnum; i++) { for (j = 1; j <= G->vexnum; j++) { printf("%-3d ", G->arcs[i][j]); } printf("\n"); } } void Dijkstra(AdjMatrix* G, char start1,char end1) { int mindist, i, j, k, t = 1,num=1; int dist[MAXVEX]={0}; int sign[MAXVEX]={0}; int path[MAXVEX][MAXVEX]={0}; int start = LocateVex(G, start1); int end = LocateVex(G, end1); //初始化 for (i = 1; i < MAXVEX; i++) { dist[i] = 0; sign[i] = 0; for (j = 1; j < MAXVEX; j++) path[i][j] = 0; } for (i = 1; i <= G->vexnum; i++) { dist[i] = G->arcs[start][i]; if (G->arcs[start][i] != INFINITY) path[i][1] = start; } sign[start] = 1; //num控制循环次数,直到顶点都加入到自己理解的集合S中 while (num < G->vexnum) { //第二步找最短的顶点 mindist = INFINITY; for (i = 1; i <= G->vexnum; i++) { if (!sign[i] && dist[i]< mindist) { k = i; mindist = dist[i]; } } if (mindist == INFINITY) return; sign[k] = 1; //第三步集和S中加入新的顶点后修改权值 for (j= 1; j <= G->vexnum; j++) { if (!sign[j] && G->arcs[k][j] < INFINITY && dist[k] + G->arcs[k][j] < dist[j]) { dist[j] = dist[k] + G->arcs[k][j]; //更新j顶点的路径 t = 1; while (path[k][t]!=0) { path[j][t] = path[k][t]; t++; } path[j][t] = k; } } num++; } //打印起始点到终点的路径 for (i = 1; path[end][i] != 0; i++) printf("%c ->", G->vex[path[end][i]]); printf("%c", G->vex[end]); printf("\n"); printf("最短路径长度:\n"); printf("%d\n", dist[end]); } main() { AdjMatrix* G = Create(); printf("打印矩阵\n"); print(G); printf("请输入起始点与终点!\n"); char start, end; scanf(" %c", &start); scanf(" %c", &end); printf("起始点到终点的最短路径:\n"); Dijkstra(G,start,end); }