目录
二、图的应用
1.拓扑排序
(1)基于DFS的拓扑排序(无法识别回路)
void TopSort_DFS(Graph&G)
{
for (int i=0;i<G.Vernum();i++)//将图中所有顶点的标志位初始化为false
G.visited[i]=false;
for (int i=0;i<G.Vernum();i++)
if(G.visited[i]==false) Do_TopSort (G.i);//深度优先搜索
}
void Do_TopSort(Graph&G,int V)
{
G.visited[V]=true;
for(int w=G.Firstadj(V);w!=-l;w=G.Nextadj(V,w) )
if(G.visited[w]==false) Do_TopSort(G,w);
cout<<V<<"\t";
}
原理:
此算法得到逆拓扑排序,即从后往前看时后面节点只受之前节点的影响。
对于一个节点来说,其它节点与其关系有两种:该节点指向别的,或别的节点指向该节点。
DFS过程中,第一种关系的节点会被遍历到,故当这次DFS结束,开始下一次DFS时,下一次的节点与此次节点只有可能是第二种关系,所以之后的DFS序列往后放符合逆拓扑序列。
因为是逆拓扑排序,所以在递归返回时再输出。逆拓扑可用栈调正。
(2)基于BFS的拓扑排序(可识别回路)
void TopSort_Queue(Graph&G)
{
int i;
int*indegree=new int[G.Vernum()];//保存顶点的入度
for(i=0;i<G.Vernum();i++)
indegre[i]=G.InDegree[i];
for(i=0;i<G.Vernum();i++)//初始化Visited数组
G.Visited[i]=false;
queue <int> Q;
for(i=0;i<G.Vernum();i++)//图中入度为0的顶点入队
if(indegree[i]==0) Q.push(i);
while(!Q.Isempty())//如果队列中还有顶点
{
int V=Q.pop();
cout<<V <<"\t";
G.visited[V]=true;
for(int w=G.Firstadj(V);w!=-l;w=G.Nextadj(V,w))
{
indegree[w]--;
if(indegree[w] ==0) Q.push(w);
}
}
cout<<endl;
for(i=0;i<G.Vernum();i++)
{
if(G.visited[i]==false)
{
cout<<"此图存在回路"<<endl;
break;
}
}
}
原理:
每次输出入度为零的节点,由于入度为0,剩余的节点不可能优先级更高(或说指向它),所以它只受之前输出节点的影响,它后面的节点可能受到它的影响。每次输出节点后要更新剩余被它指向节点的入度。
2.关键路径
bool CriticalPath(Graph&G)
{
int*ve=new int[G. Vernum()];//事件最早发生时间数组
queue <int> Q;//存储入度为0的顶点
stack <int> S;//用于实现逆拓扑序列的栈
int cnt=0;//cnt用于对顶点计数
int v,w;
int*indegree=new int[G.VerNum()];
for(w=0;w<G.Vernum();w++)//保存顶点的入度
indegree [w] =G. InDegree[w];
for(w=0;w <G.Vernum();w++)//初始化事件最早发生时间
ve[w]=0;
for(w=0;w<G.Vernum();w++)//图中入度为0的顶点人队
if(indegree[w]==0) Q.push(w);//取出一个入度为0的顶点
while(!Q.Isempty())
{
v=Q.pop();
S.push(v);//顶点v人栈,以便得到逆拓扑序列
cnt++;
for(w=G.Firstadj(v);w!=-1;w=G.Nextadj(v,w))
{
inegree[w]--;
if(indegree[w]==0) Q.push(w);
if (ve[v]+G.weight(v,w)>ve[w])
ve[w]=ve[v]+G.weight(v,w);
}
}
if(cnt<G.Vernum())
{
delete []ve;
cout<<"此图存在回路"<<endl;
return false;
}
int*vl=new int[G.Vernum()];//事件最迟发生时间数组
v=S.pop()//取出栈顶,栈顶为汇点
for(w=0;w<G.Vernum();w++)//初始化事件最迟发生时间
vl[w]=ve[v];
while(!S.Isempty())
{
v=S.pop();
for (w=G.FirstAdj(v);w!=-1;w=G.Nextadj(v,w))
if(vl[w]-G.weight(v,w)<vl[v])
vl[v]=vl[w]-G.weight(v.w);
}
int e,l;//e和1分别为活动最早开始时间和最迟开始时间
for(v=0;v<G.Vernum();v++)
{//求e、1以及关键路径
for (w=G.Firstadj(v);w!=-1;w=G.Nextadj(v,w))
{
e=ve[v];
l=vl[w]-G.weight(v,w);
if(e==l)
cout<<"<"<<v<<<","<<w<<">";
}
}
delete[]ve;
delete[]vl;
return true;
}
3.最短路径
(1)Dijkstra算法
void Dijkstra(Graph&G,int*dist,int*pre,int s)
{
int i,j,n=G. Vernum();
bool*S=new bool [n]//最短路径终点集S
for(i=0;i<n;i++)
{//初始化dist、pre、s
S[i] =false;
if(i! =s&&G.weight (s,i)==0)
dist[i]=maxValue;
else
dist[i]=G.weight(s,i);
if(i!=s&&dist[i]<maxValue)
pre[i]=s;
else
pre[i]=-1;
}
S[s]=true;
int Min,v;
for(i=0;i<n-1;i++)
{
Min=maxValue;
v=s;
for(j=0:j<n;j++)
{
if(S[j]==false&&dist[j]<Min)
{
v=j;
Min=dist[j];
}
}
S[v]=true;//将顶点v加入到集合S中
for(j=0;j<n;j++) //修改
{
if (S[j]==false&&G.weight(v,j)!=0&&dist[v]+G.weight(v.j)<dist(j])
dist[j]-dist[v]+G.weight(v.j);
pre[j]=v;
}
}
}
(2)Floyd算法
void MidNode(int i,intj,int**path)
{//输出中间过渡点
if(path[i][j]!=-1&&i!=path[i][j])
{
MidNode(i,path[i][j],path);
cout<<path[i][j]<<"->";
MidNode(path[i][j],j,path);
}
void Output_AllPaths(intn,int**path)
{//输出任意对顶点之间的最短路径
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(i!=j&&path[i][j]!=-1)
{
cout<<i<<"->";
MidNode(i,j,path);
cout<<j<<endl;
}
else
cout<<i<<"->"<<j<<"之间无路可达"<<endl;
}
//用Floyd算法求任意对顶点之间的最短距离
void Floyd(Graph&G,int **dist,int **path)
{
int i,j,k,n=G.Vernum();
for(k=0;k<n;k++) //k为中间过渡点
{
for(i=0;i<n;i++)
for(j=0;j<n;j++)
{
if(dist[i][k]+dist[k][j]<dist[i][j]])
{
dist[i][j]=dist[i][k]+dist[k][j];
path[i][j]=k;
}
}
}
Output_AIlPaths(n,path);//输出任意对顶点之间的最短路径
}
4.最小生成树
(1)Prim算法
适用于边稠密的图
//利用Prim算法求带权连通图的最小生成树,用MST来存储最小生成树的边
void Prim (Graph&G,int s,Edge*MST)
{
int i,j,n=G.Vertnum();
Edge *MST_Edge=new Edge[n];//辅助数组MST_Edge
for (i=0;i<n;i++) //初始化MST_Edge数组
{
MST_Edge [i] .from=i;
MST_Edge [i] .to=s;
if (i!=s&&G.weight(i,s) ==0)
MST_Edge[i].weight=maxValue;
else
MST_Edge[i].weight=G.weight(i,s);
}
int v, Min;
for(i=0;i<n-1;i++)
{//找到U中顶点到V-U中顶点权值最小的边,并记录顶点
Min=maxValue;
v=0;
for(j=0;j<m;j++)
if(MST_Edge[j].weight!=0&&MST_Edge[j].weight<Min)
{
Min=MST_Edge[j].weight;
v=j;
}
//将顶点v加入集合U中
MST[i].from=v;
MST[i].to=MST_Edge[v].to;
MST[i].weight=Min;
MST[i]_Edge[v].weight=0;
//修改辅助数组中与v关联的边的权值
for(j=0;j<n;j++)
if(G.weight(v.j)!=0&&G.weight(v.j)<MSTST_Edge[j] .weight)
{
MST_Edge[j].to=v;
MST_Edge[j].weight=G.weight(v.j);
}
}
}
(2)Kruskal算法
#include<stdilb.h>
int cmp (constt void *a,const void*b)
{
Edge*c= (Edge*) a;
Edge*d= (Edge*) b;
if (c-> weight! =d-> weight)
return c-> weight - d-> weight;
else return d-> from-c-> from;
//用kruskal算法求带权连通图的最小生成树,用MST来存储最小生成树的边
void Kruskal (Graph&G, Edge*MST)
{
n=G. VerticesNumO;
int*Vset=new int [n] ;
Edge*E=new Edge [G. Edgnum()];//记录图的所有边
int i,j;
int EdgeCnt=0;
for (i=0;i<n;i++)
{ //将图中所有的边存储在数组E中
for(j=G.Firstadj(i);j!=-1;j=G.Nextadj(i,j))
if (i <j)
{
[EdgeCnt] .from=i;
[EdgeCnt] .to=j;
[EdgeCnt++] .weight=G.weight (i.j);
}
Vset[i]=i;
}
qsort(E,EdgeCnt,sizeof(E[0]),cmp);//使用快速排序按照权值从小到大排序
int cnt=0;//生成的边数
i=0;//E的下标
int x,y;
while(cnt <n-1)
{
x=Vset[E[i].from];
y=Vset[E [i].to];
if(x!=y)//不在同一个连通分量中
{
MST[cnt++]=E[i];
for(j=0;j<n;j++)
if(Vset[j]==y) Vset[j]=x;//合并
}
i++;
}
}