文章出自:http://dsqiu.iteye.com/blog/1689199
拓撲排序和關鍵路徑
拓撲排序
拓撲排序最大的用途就是判斷一個有向圖是否有環,當然判斷還有一種方法就是Floyd算法。如果用鄰接表的話拓撲排序的時間複雜度是O(N*E),鄰接矩陣是O(N^2),N表示頂點數,E表示邊數,Floyd時間複雜度是O(N^3)。
拓撲排序方法可分爲無前趨的頂點優先的拓撲排序方法和無後繼的頂點優先的拓撲排序方法。
基本拓撲排序算法步驟
1.在有向圖中任選一個沒有前驅的頂點輸出
2.從圖中刪除該頂點和所有以它爲起點的邊
3.重複1和2,直到全部頂點都以輸出
拓撲排序的實現(無前趨的頂點優先的拓撲排序方法)
鄰接表實現(使用stack存儲)
- /**********************************
- title: 拓撲排序(鄰接表實現)
- author: jay chang
- date: 2009/07/16
- **********************************/
- #include<iostream>
- #define MAXSIZE 99
- #define TRUE 1
- #define FALSE 0
- using namespace std;
- typedef char VertexData;
- typedef int AdjType;
- typedef struct Stack //定義棧
- {
- int data[MAXSIZE+1];
- int top;
- }Stack;
- typedef struct ArcNode //定義弧結點
- {
- AdjType adj;
- ArcNode *nextArc;
- }ArcNode;
- typedef struct VertexNode //定義頂點
- {
- VertexData vertexData;
- ArcNode *firstArc;
- }VertexNode;
- typedef struct AdjMatrix //定義圖
- {
- VertexNode vertexNodes[MAXSIZE+1];
- int verNum,arcNum;
- }AdjMatrix;
- //全局變量
- int indegree[MAXSIZE+1]={0};
- int LocateGraph(AdjMatrix *g, VertexData vertexData)
- {
- int iIndex;
- for(iIndex=1;iIndex<=g->verNum;iIndex++)
- {
- if(vertexData==g->vertexNodes[iIndex].vertexData)
- return iIndex;
- }
- return FALSE;
- }
- void CreateGraph(AdjMatrix *g)
- {
- int iCount,arcStart,arcEnd;char start,end;
- cout<<"*****************************************"<<endl;
- cout<<"*** 拓撲排序\n";
- cout<<"*** Author: Jay Chang\n";
- cout<<"*****************************************"<<endl;
- cout<<"輸入有向無環圖的頂點,及弧數\n";
- cin>>g->verNum>>g->arcNum;
- cout<<"輸入有向無環圖的頂點信息\n";
- ArcNode *q=NULL;
- for(iCount=1;iCount<=g->verNum;iCount++)
- {
- cout<<"輸入第"<<iCount<<"個頂點的信息"<<endl;
- cin>>g->vertexNodes[iCount].vertexData;
- g->vertexNodes[iCount].firstArc=NULL;
- }
- for(iCount=1;iCount<=g->arcNum;iCount++)
- {
- cout<<"輸入第"<<iCount<<"條弧的起始與結束的信息"<<endl;
- cin>>start>>end;
- arcStart=LocateGraph(g,start);
- arcEnd =LocateGraph(g,end);
- //添加弧結點
- q=(ArcNode*)malloc(sizeof(ArcNode));
- q->adj=arcEnd;
- q->nextArc=g->vertexNodes[arcStart].firstArc;
- g->vertexNodes[arcStart].firstArc=q;
- //對於第arcEnd個頂點的入度值加1
- indegree[arcEnd]++;
- }
- }
- //判棧空
- int IsEmpty(Stack *stack)
- {
- return stack->top==-1?TRUE:FALSE;
- }
- //初始化棧
- void InitStack(Stack *stack)
- {
- stack->top=-1;
- }
- //出棧
- void Pop(Stack *stack,int *iIndex)
- {
- *iIndex=stack->data[stack->top--];
- }
- //進棧
- void Push(Stack *stack,int value)
- {
- stack->data[++stack->top]=value;
- }
- //拓撲排序
- int Topological(AdjMatrix *g)
- {
- int iCount,count=0;
- Stack *stack=(Stack*)malloc(sizeof(Stack));
- InitStack(stack);
- ArcNode *p=NULL;
- //對於入度爲0的頂點入棧
- for(iCount=1;iCount<=g->verNum;iCount++)
- {
- if(indegree[iCount]==0){
- Push(stack,iCount);
- }
- }
- cout<<"輸出拓撲序列\n";
- //輸出頂點後,將與該頂點相連的頂點的邊刪除,將與相連頂點的入度減1,如減後爲0,入棧,棧空結束
- while(!IsEmpty(stack))
- {
- Pop(stack,&iCount);
- cout<<g->vertexNodes[iCount].vertexData<<" ";
- count++;
- p=g->vertexNodes[iCount].firstArc;
- while(p!=NULL)
- {
- if(--indegree[p->adj]==0)
- Push(stack,p->adj);
- p=p->nextArc;
- }
- }//end while
- if(count<g->verNum){
- cout<<"有迴路"<<endl;
- return FALSE;
- }
- cout<<endl;
- }
- int main()
- {
- AdjMatrix *g=(AdjMatrix*)malloc(sizeof(AdjMatrix));
- CreateGraph(g);
- Tuopu(g);
- return 0;
- }
轉自http://jaychang.iteye.com/blog/702073
基於DFS實現(無後繼的頂點優先的拓撲排序方法)
- #include<iostream>
- using namespace std;
- int timef = 0;
- int n ;
- int a[1000][1000];// 圖的鄰接矩陣
- int f[1000]; //完成時間
- int vis[1000]; //1代表 被發現 2代表 已完成
- void DFS(int u)
- {
- vis[u] = 1; //記錄發現時刻
- for(int v=1; v<=n; v++) //adj(u) //O(E)
- if(a[u][v] && vis[v]==0)
- DFS(v);
- vis[u] = 2; //記錄完成時刻
- timef++;
- f[u] = timef;
- }
- void DFS_main() //O(V+E)
- {
- timef = 0;
- for(int i=1; i<=n; i++) /// O(V)
- {
- if(vis[i] == 0)
- DFS(i);
- }
- }
- void Topological_sort() //O(V+E)
- {
- int tp[1000]; ////存放拓撲序列1..V
- DFS_main();
- for(int i=1; i<=n; i++) //按finish的時間倒序存放在tp序列tp中
- tp[n-f[i]+1] = i;
- for(int i=1; i<=n; i++)
- cout<<tp[i]<<" ";
- cout<<endl;
- }
- int main()
- {
- memset(vis,0,sizeof(vis));
- cin>>n;
- for(int i=1; i<=n; i++)
- for(int j=1; j<=n; j++)
- cin>>a[i][j];
- Topological_sort();
- system("pause");
- return 0;
- }
轉自http://blog.sina.com.cn/s/blog_6ec5c2d00100szit.html
關鍵路徑
相關量介紹,設源點v0,匯點vn-1:
ve(i)表示事件vi最早發生的時間,vl(i)表示事件vi最晚發生的時間。
ve(0)=0,按拓撲排序計算ve(i)的值,ve(j)=max{ve(i)+w(i,j)|<i,j>∈E}。
vl(n-1)=ve(n-1),vl(i)按逆拓撲排序進行計算,vl(i)=min{vl(j)-w(i,j)|<i,j>∈E}。
活動<i,j>最早開始時間ee(i)=ve(i);活動<i,j>最晚開始時間el(i,j)=vl(j)-w(i,j),如果el(i,j)=ee(i,j),則活動<i,j>是關鍵活動。
關鍵路徑算法的流程
1.以拓撲排序的次序按ve(j)=max{ve(i)+w(i,j)|<i,j>∈E}計算各個頂點(事件)最早發生的時刻
2.以逆拓撲排序的次序按vl(j)=min{ve(i)+w(i,j)|<i,j>∈E}計算各個頂點(事件)最晚發生的時刻
3.計算每個活動<i,j>發生的最早時間ee(i,j)和最晚時間el(i,j),如果滿足ee(i,j)=el(i,j)則是關鍵路徑並輸出。
關鍵路徑算法實現
- #include <iostream>
- using namespace std;
- #define MAX 10000000
- #define MAX_VERTEX_NUM 20
- int ve[MAX_VERTEX_NUM];
- /*順序棧的定義*/
- #define Stack_Size 100
- typedef struct sqStack
- {
- int *elem;
- int top;
- int stackSize;//棧數組長度
- }sqStack;
- /*順序棧的初始化*/
- void initStack_Sq(sqStack &S)
- {
- S.elem=new int[Stack_Size];
- S.top=-1;
- S.stackSize=Stack_Size;
- }
- /*入棧*/
- void push(sqStack &S,int x)
- {
- if(S.top==Stack_Size-1)
- cout<<"Stack Overflow!";
- S.elem[++S.top]=x;
- }
- /*出棧*/
- int pop(sqStack &S)
- {
- int x;
- if(S.top==-1)
- cout<<"Stack Empty!";
- x=S.elem[S.top--];
- return x;
- }
- typedef struct EdgeNode
- {//邊表結點的定義
- int adjvex;//存放鄰接點在頂點表中的位置
- struct EdgeNode * nextedge;//指向下一個邊表結點
- int weight;
- }EdgeNode;
- typedef struct VexNode
- {//頂點表結點的定義
- char vex;//存放頂點信息
- EdgeNode * firstedge;//指向第一個邊表結點
- int indegree;
- }VexNode;
- typedef struct
- {//頂點表的定義
- VexNode vexs[MAX_VERTEX_NUM];
- int vexnum,edgenum;
- }LGraph;
- /*構造有向圖的鄰接表*/
- void CreateDG_AL(LGraph &G,int n,int e)
- {
- int i,j,k,w;
- G.vexnum=n;
- G.edgenum=e;
- for(i=0;i<n;i++)
- {
- cin>>G.vexs[i].vex;
- G.vexs[i].firstedge=NULL;//初始化爲空
- }
- for(k=0;k<e;k++)
- {
- EdgeNode *p;
- cin>>i>>j>>w;
- p=new EdgeNode;
- p->adjvex=j;
- p->weight=w;
- p->nextedge=G.vexs[i].firstedge;
- G.vexs[i].firstedge=p;//採用頭插法
- }
- }
- //拓撲排序並求各頂點事件的最早發生時間及拓撲逆序列
- void TopoSort(LGraph &G,sqStack &T)
- {
- sqStack S;
- initStack_Sq(S);
- EdgeNode *p;
- int count=0;
- int i;
- for(i=0;i<G.vexnum;i++)
- G.vexs[i].indegree=0;//初始化爲0
- for(i=0;i<G.vexnum;i++)
- {//計算各個頂點的入度
- p=G.vexs[i].firstedge;
- while(p)
- {
- G.vexs[p->adjvex].indegree++;
- p=p->nextedge;
- }
- }
- for(i=0;i<G.vexnum;i++)
- if(G.vexs[i].indegree==0)
- push(S,i);//將度爲0的頂點入棧,這裏進棧的是入度爲0的頂點在數組中的位置
- for(i=0;i<G.vexnum;i++)
- ve[i]=0;//初始化頂點事件的最早發生時間爲0
- while(S.top!=-1)
- {
- i=pop(S);
- cout<<G.vexs[i].vex<<" ";//將棧頂的元素出棧且輸出,即將入度爲0的頂點輸出
- push(T,i);//爲了求得拓撲序列的逆序列,將元素依次進棧就得到了逆序列
- count++;//計數器加1
- p=G.vexs[i].firstedge;//讓p指向入度爲0的頂點的第一個邊表結點
- while(p)
- {
- int k;
- int dut;
- dut=p->weight;
- k=p->adjvex;
- G.vexs[k].indegree--;//將入度爲0的頂點的鄰接點的入度減1
- if(G.vexs[k].indegree==0)
- push(S,k);//度減1後的頂點如果其入度爲0,則將其入棧
- if(ve[i]+dut>ve[k])
- ve[k]=ve[i]+dut;//經過while循環,將頂點事件的所有鄰接點的最早發生時間算出來,
- //並且經過外層的while循環,不斷地更新爲較大的ve[k]值
- p=p->nextedge;
- }
- }
- cout<<endl;
- if(count<G.vexnum)
- cout<<"Network G has citcuits!"<<endl;
- }
- //求關鍵路徑和關鍵活動
- void CriticalPath(LGraph &G)
- {
- int i,j,k,dut;
- int ee,el;
- int vl[MAX_VERTEX_NUM];
- EdgeNode *p;
- sqStack T;
- initStack_Sq(T);
- TopoSort(G,T);
- for(i=0;i<G.vexnum;i++)
- vl[i]=ve[G.vexnum-1];//初始化頂點事件的最遲發生時間爲匯點的最早發生時間
- //因爲求最遲發生時間是從匯點向源點開始計算的
- while(T.top!=-1)
- {//經過while循環,按堆棧順序求出每個頂點的最遲發生時間
- for(j=pop(T),p=G.vexs[j].firstedge; p ;p=p->nextedge)
- {//這裏應該注意for循環的機制:每一次循環都要判斷一次條件,包括第一次
- k=p->adjvex;
- dut=p->weight;
- if(vl[k]-dut<vl[j])
- vl[j]=vl[k]-dut;//按堆棧T中事件的順序,將該頂點事件的最遲發生時間經過for循環算出來,
- //注意:經過for循環算出的是一個頂點的最遲發生時間
- }
- }
- for(i=0;i<G.vexnum;i++)
- {//依次遍歷每一個活動
- for(p=G.vexs[i].firstedge;p;p=p->nextedge)
- {
- k=p->adjvex;
- dut=p->weight;
- ee=ve[i];//求活動的最早開始時間
- el=vl[k]-dut;//求活動的最遲開始時間
- if(ee==el)
- {//若兩者相等,說明這這個活動爲關鍵活動
- cout<<"("<<G.vexs[i].vex<<","<<G.vexs[k].vex<<")"<<dut<<" ";
- cout<<"ee="<<ee<<","<<"el="<<el<<endl;
- }
- }
- }
- }
- void main()
- {
- freopen("in.txt","r",stdin);
- LGraph G;
- CreateDG_AL(G,9,11);
- CriticalPath(G);
- }
轉自http://blog.csdn.net/hackerain/article/details/6054188
小結
這篇文章講了有關有向無環圖的兩個應用,最要將能熟悉拓撲排序和關鍵路徑的算法的原理就能加以應用。如果你有任何建議或者批評和補充,請留言指出,不勝感激,更多參考請移步互聯網。