图(下)
一.最小生成树
什么是最小生成树?
首先,它是一棵树,没有回路,加入有n个结点,那么它一定有n-1条边。其次,是生成树,也就是意味着它要包含全部顶点,n-1条边都在图里。最后,要求边的权重和最小。满足了这三点,它就是一颗最小生成树。
为了找出这样的一棵树,我们有两种算法
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<G.numVertexes;i++)
{
min=INF;//初始化最小权值为无穷大
j=1;k=0;
while(j<G.numVertexes)
{
if(lowcost[j]!=0&&lowcost[j]<min)//如果权重不为0,且权重小于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算法:
核心思想:克鲁斯布尔简单地来说其实就是”将森林合成树“,怎么说呢,我们运用了一个结构体数组,结构体里面存的是起点和终点,以及边的权重,为了防止出现回路的情况,我们还定义了一个数组,用于并查集,防止形成回路,开始的时候这个数组初始为0,然后我们对每一条边进行以下操作:定义一个Find函数,该函数用于查找连线顶点的尾部下标,然后我们判断一下一个结构体的开始和终止的Find值是否相同,如果相同就证明会形成回路,我们就舍弃该点,否则,我们就将该点的记录下来,并输出,就能得到我们的最小生成树了。
typedef struct
{
int begin;
int end;
int weight;
}Edge;
int Find(int *parent,int f)//并查集
{
while(parent[f]>0)
f=parent[f];
return f;
}
void MiniSpanTree_Kruskal(MGraph G)//生成最生成树
{
int i,m;
Edge edges[MAXEDGE];//定义边集数组
int parent[MAXVEX];//定义一数组用来判断边与边是否形成回路
for(i=0;i<G.numVertexes;i++)
{
parent[i]=0;//初始化数组的值为0
}
for(i=0;i<G.numVertexes;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);
}
}
}
二.拓扑排序
在了解拓扑排序之前,我们得先知道一个概念,那就是什么是AOV网
在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点所表示活动的网,我们称为AOV网。
拓扑序列:设G={V,E}是一个具有n个顶点的有向图,V中的顶点序列V1,V2…Vn,满足若从顶点Vi到Vj有一条路径,则在顶点序列中顶点Vi必须在Vj之前,则我们称这样的一个顶点序列为拓扑序列。
简单地解释一下,就比如我们小学的学习一样,如果你想读六年级,那么你必须是从五年级过来的,如果你想读五年级,那么你必定是从四年级来的,拓扑序列其实就是这个意思。
拓扑排序算法:
对AOV网进行拓扑排序的基本思路就是:从网中找一个入度为0的顶点,然后输出此顶点并删除,并且删除以此顶点为尾巴的弧,如此循环即可。
因为要进行删除,所以我们用邻接表就比较方便一些
typedef struct EdgeNode//边表结点
{
int adjvex;//邻接点域,存储该顶点对应的下标
int weight;//用于存储权值,对于非网图可以不需要
struct EdgeNode *next;//邻域,指向下一个邻接点
}EdgeNode;
typedef struct VertexNode
{
int in;//入度
int data;//顶点域,存储顶点信息
EdegeNode *firstedge;//边表头指针
}
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;//建栈存储入度为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;
}
}