有向圖_拓撲排序_AOE關鍵路徑_判斷圖中是否存在環
阿新 • • 發佈:2019-02-11
目錄
0.圖——判環
0.1.1無向圖判斷是否存在環
0.1.1無向圖,若深度優先遍歷過程中遇到回邊(即指向已訪問過的頂點的邊),則該無向圖必定存在環路。
參考圖的關節點與重連通分量,圖的深度優先生成樹。
定義visited[]陣列為深度優先遍歷時訪問連通圖的頂點v的序號。
如果某個頂點的visited值比其鄰邊的頂點的visited的值要大,說明,該頂點是後訪問到了,存在一條回邊連線到了其祖先節點!
因為對於任意一個頂點v而言,其孩子節點為在它之後才訪問的節點,而其雙親結點和又回邊連線的祖先結點是在它之前就訪問過的結點!
0.1.2有向圖判斷是否存在環
0.2.1對於有向圖,利用拓撲排序來判斷是否存在環:
對於無環的有向圖,對其進行拓撲排序可輸出其所有頂點,而有環的圖則不行!
0.2無向圖判環
//用鄰接表來存圖 //先定義圖的頂點資料結構, typedef struct Node { int num;//頂點編號 char Alphabet; //int visited; }Node; //無向圖的鄰接表表示,每個頂點對應一個列表 class UDGALGraph { private: vector<Node*> vecNodes;//圖的結點地址向量,依次存每一個結點的地址 vector<vector<Node*>*> vexLists;//儲存圖中有向邊的資訊:每個頂點有一個鄰接表,該鄰接表上依次掛有其鄰接頂點的地址 int vexNum, edgeNum; vector<int> visitedOrder; public: UDGALGraph() { CreateUDG(); } ~UDGALGraph() { DestroyDUG(); } void CreateUDG(); void DestroyDUG(); void DFS(int v,int& count,bool& hasLoop); //從圖al的頂點v出發,遞迴地深度優先遍歷圖G bool DFSTraverse(); bool hasLoop(); };
無向圖利用深度優先遍歷來判斷是否存在環:
void DFS(int v,int& count,bool& hasLoop) {//從圖al的頂點v出發,遞迴地深度優先遍歷圖G visitedOrder[v] = count++; cout << vecNodes[v]->Alphabet << " "; for (int i = 0; i < (vexLists[v]->size()); i++) { int nodeNum = vexLists[v]->at(i)->num; if (visitedOrder[nodeNum] == 0) { DFS(nodeNum, count, hasLoop); } else if (visitedOrder[nodeNum]>visitedOrder[v]) { hasLoop = true; } } } bool DFSTraverse() { int count = 1; bool hasLoop = false; for (int i = 0; i < vexNum; i++) { if (visitedOrder[i] == 0) { DFS(i, count, hasLoop); } } /* algraph是在堆中分配的結點,每次範圍後,其每一個結點的標誌位都設定為1了,退出時, 下次再遍歷前要清一下標誌位。 */ for (int i = 0; i < vexNum; i++) { visitedOrder[i] = 0; } return hasLoop; } bool hasLoop() {//對於無向圖來說,若深度優先遍歷過程中遇到回邊(即連線已經訪問過的頂點的鄰邊),則說明該無向圖存在環! if (DFSTraverse()) { return true; } else { return false; } }
0.3有向圖判環
//有向圖的鄰接表表示,每個頂點對應一個鄰接連結串列
class DGALGraph
{
private:
vector<Node*> vecNodes;//圖的結點地址向量,依次存每一個結點的地址
vector<vector<Node*>*> vexLists;//儲存圖中有向邊的資訊:每個頂點有一個鄰接表,該鄰接表上依次掛有其鄰接頂點的地址
int vexNum, edgeNum;
vector<int> InDegree;
vector<int> visited;
deque<int> topologicalSequence;//有向無環圖的拓撲序列
public:
DGALGraph()
{
CreateUDG();
}
~DGALGraph()
{
DestroyDUG();
}
void CreateUDG();
void DestroyDUG();
bool topologicalSort();
bool hasLoop();
void printTopoSeq()
};
有向圖利用拓撲排序來判斷是否存在環:
bool topologicalSort()
{
cout << "有向圖的拓撲排序:" << endl;
stack<int> inDegree0VexStack;
for (int i = 0; i < vexNum; i++)
{
if (InDegree[i] == 0)
{
inDegree0VexStack.push(i);
}
}
int count = 0;//對輸出頂點計數
while (!inDegree0VexStack.empty())
{
int i = inDegree0VexStack.top();//輸入i號頂點,並計數
inDegree0VexStack.pop();
//cout << vecNodes[i]->Alphabet << " ";
cout << i + 1 << " ";
++count;
for (int j = 0; j < vexLists[i]->size(); j++)
{//對i號頂點的每一個鄰接頂點j的入度減1,即i->i的鄰接頂點
int nodeNum = vexLists[i]->at(j)->num;
if ((--InDegree[nodeNum] == 0))
{//若入度減到了0,則入棧
inDegree0VexStack.push(nodeNum);
}
}
}
if (count < vexNum)
{//該有向圖有環
return true;
}
else
{//該有向圖無環,可將所有頂點按拓撲有序輸出。
return false;
}
}
bool hasLoop()
{
if (topologicalSort())
{
cout << endl;
cout << "該有向圖有環!" << endl;
return true;
}
else
{
cout << endl;
cout << "該有向圖無環!" << endl;
return false;
}
}//topologicalSort
1.有向圖的拓撲排序
2.關鍵路徑
2.1.鄰接連結串列儲存AOE網
typedef struct ArcNode{
int v1; //弧尾
int v2;//弧頭:該弧指向的頂點的位置
int weight; //資料域
}ArcNode;
typedef struct VNode{
int vexNo; //頂點編號
}VNode;
class AOEALGraph{
private://用鄰接連結串列儲存AOE網!
int vexNum, arcNum;
vector<VNode*> nodesPtrVector;
vector<vector<ArcNode*>*> nodeArcPtrVector;//每個頂點的出弧指標向量
stack<int> ReverseTopoOrder;//逆拓撲排序
vector<int> InDegree;//各個頂點的入度
vector<int> ve;//各個頂點的最早發生時間向量
vector<int> vl;//各個頂點的最晚發生時間向量
vector<ArcNode*> CP;//關鍵路徑!存弧,即活動
public:
AOEALGraph()
{
CreateAOE();
}
void CreateAOE();
bool TopologicalOrder();
bool CriticalPath()
};
2.1.1拓撲排序: bool TopologicalOrder():
bool TopologicalOrder()
{
int count=0;
stack<int> _0InDegreeStack;
for (int i = 0; i < vexNum; i++)
{
if (InDegree[i] == 0)
{
_0InDegreeStack.push(i);
}
}
while (!_0InDegreeStack.empty())
{
int j = _0InDegreeStack.top();
_0InDegreeStack.pop();
ReverseTopoOrder.push(j);//j號頂點入逆拓撲排序棧!
count++;
for (int i = 0; i < nodeArcPtrVector[j]->size(); i++)
{//遍歷j號頂點的所有鄰接點k
int k = nodeArcPtrVector[j]->at(i)->v2;
if (--InDegree[k] == 0)
{//對j號頂點的每一個鄰接頂點的入度-1,若入度為0則入0入度棧
_0InDegreeStack.push(k);
}
if (ve[j] + nodeArcPtrVector[j]->at(i)->weight > ve[k])
{
ve[k] = ve[j] + nodeArcPtrVector[j]->at(i)->weight;
}
}
}
if (count < vexNum)
{
return false;
}
else
{
return true;
}
}
2.1.2關鍵路徑:bool CriticalPath()
bool CriticalPath()
{
if (!TopologicalOrder())
{
return false;
}
for (int i = 0; i < vexNum; i++)
{//初始化各個頂點事件的最遲發生時間!
vl[i] = ve[vexNum - 1];
}
while (!ReverseTopoOrder.empty())//按拓撲逆序求各個頂點的vl值
{
int j = ReverseTopoOrder.top();
ReverseTopoOrder.pop();
for (int i = 0; i < nodeArcPtrVector[j]->size(); i++)
{
int k = nodeArcPtrVector[j]->at(i)->v2;
int dut = nodeArcPtrVector[j]->at(i)->weight;
if (vl[k] - dut < vl[j])
{
vl[j] = vl[k] - dut;
}
}//for
}
for (int j = 0; j < vexNum; j++)
{//求沒一條弧的最早開始時間ee(i)和最晚開始時間el(i),若ee=el則為關鍵活動!將關鍵活動的這條弧的指標存入CP向量
for (int i = 0; i < nodeArcPtrVector[j]->size(); i++)
{
int k = nodeArcPtrVector[j]->at(i)->v2;
int dut = nodeArcPtrVector[j]->at(i)->weight;
int ee = ve[j];
int el = vl[k] - dut;
if (ee == el)
{
CP.push_back(nodeArcPtrVector[j]->at(i));
}
}
}
//輸出關鍵活動弧
for (int i = 0; i < CP.size(); i++)
{
cout << CP[i]->v1 << "->" << CP[i]->v2 << ",weight=" << CP[i]->weight << endl;
}
return true;
}
求關鍵路徑的完整程式碼與測試用例:
// CriticalPath2.cpp : 定義控制檯應用程式的入口點。
//
#include "stdafx.h"
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
typedef struct ArcNode{
int v1; //弧尾
int v2;//弧頭:該弧指向的頂點的位置
int weight; //資料域
}ArcNode;
typedef struct VNode{
int vexNo; //頂點編號
}VNode;
class AOEALGraph{
private://用鄰接連結串列儲存AOE網!
int vexNum, arcNum;
vector<VNode*> nodesPtrVector;
vector<vector<ArcNode*>*> nodeArcPtrVector;//每個頂點的出弧指標向量
stack<int> ReverseTopoOrder;//逆拓撲排序
vector<int> InDegree;//各個頂點的入度
vector<int> ve;//各個頂點的最早發生時間向量
vector<int> vl;//各個頂點的最晚發生時間向量
vector<ArcNode*> CP;//關鍵路徑!存弧,即活動
public:
AOEALGraph()
{
CreateAOE();
}
void CreateAOE()
{
cin >> vexNum >> arcNum;
for (int i = 0; i < vexNum; i++)
{
VNode* vexNodePtr = new VNode;
vexNodePtr->vexNo = i;
nodesPtrVector.push_back(vexNodePtr);
vector<ArcNode*>* arcALPtr = new vector < ArcNode* > ;
nodeArcPtrVector.push_back(arcALPtr);
InDegree.push_back(0);
ve.push_back(0);
vl.push_back(0);
}
int v1, v2, weight;
int v1No, v2No;
for (int i = 0; i < arcNum; i++)
{
cin >> v1 >> v2 >> weight;
v1No = v1 - 1;
v2No = v2 - 1;
ArcNode* arcNodePtr = new ArcNode;
arcNodePtr->v1 = v1No;
arcNodePtr->v2 = v2No;
arcNodePtr->weight = weight;
nodeArcPtrVector[v1No]->push_back(arcNodePtr);
InDegree[v2No]++;//頂點V2的入度+1
}
}
bool TopologicalOrder()
{
int count=0;
stack<int> _0InDegreeStack;
for (int i = 0; i < vexNum; i++)
{
if (InDegree[i] == 0)
{
_0InDegreeStack.push(i);
}
}
while (!_0InDegreeStack.empty())
{
int j = _0InDegreeStack.top();
_0InDegreeStack.pop();
ReverseTopoOrder.push(j);//j號頂點入逆拓撲排序棧!
count++;
for (int i = 0; i < nodeArcPtrVector[j]->size(); i++)
{//遍歷j號頂點的所有鄰接點k
int k = nodeArcPtrVector[j]->at(i)->v2;
if (--InDegree[k] == 0)
{//對j號頂點的每一個鄰接頂點的入度-1,若入度為0則入0入度棧
_0InDegreeStack.push(k);
}
if (ve[j] + nodeArcPtrVector[j]->at(i)->weight > ve[k])
{
ve[k] = ve[j] + nodeArcPtrVector[j]->at(i)->weight;
}
}
}
if (count < vexNum)
{
return false;
}
else
{
return true;
}
}
bool CriticalPath()
{
if (!TopologicalOrder())
{
return false;
}
for (int i = 0; i < vexNum; i++)
{//初始化各個頂點事件的最遲發生時間!
vl[i] = ve[vexNum - 1];
}
while (!ReverseTopoOrder.empty())//按拓撲逆序求各個頂點的vl值
{
int j = ReverseTopoOrder.top();
ReverseTopoOrder.pop();
for (int i = 0; i < nodeArcPtrVector[j]->size(); i++)
{
int k = nodeArcPtrVector[j]->at(i)->v2;
int dut = nodeArcPtrVector[j]->at(i)->weight;
if (vl[k] - dut < vl[j])
{
vl[j] = vl[k] - dut;
}
}//for
}
for (int j = 0; j < vexNum; j++)
{//求沒一條弧的最早開始時間ee(i)和最晚開始時間el(i),若ee=el則為關鍵活動!將關鍵活動的這條弧的指標存入CP向量
for (int i = 0; i < nodeArcPtrVector[j]->size(); i++)
{
int k = nodeArcPtrVector[j]->at(i)->v2;
int dut = nodeArcPtrVector[j]->at(i)->weight;
int ee = ve[j];
int el = vl[k] - dut;
if (ee == el)
{
CP.push_back(nodeArcPtrVector[j]->at(i));
}
}
}
//輸出關鍵活動弧
for (int i = 0; i < CP.size(); i++)
{
cout << CP[i]->v1 << "->" << CP[i]->v2 << ",weight=" << CP[i]->weight << endl;
}
return true;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
AOEALGraph aoe;
aoe.CriticalPath();
system("pause");
return 0;
}
/*
9 11
1 2 6
1 3 4
1 4 5
2 5 1
3 5 1
4 6 2
5 7 9
5 8 7
6 8 4
7 9 2
8 9 4
0->1,weight=6
1->4,weight=1
4->6,weight=9
4->7,weight=7
6->8,weight=2
7->8,weight=4
請按任意鍵繼續. . .
6 8
1 2 3
1 3 2
2 4 2
2 5 3
3 4 4
3 6 3
4 6 2
5 6 1
0->2,weight=2
2->3,weight=4
3->5,weight=2
請按任意鍵繼續. . .
*/
無向圖、有向圖判環的完整程式碼和測試用例
// TopoSort.cpp : 定義控制檯應用程式的入口點。
//
#include "stdafx.h"
#include<iostream>
#include<vector>
#include<stack>
#include<deque>
using namespace std;
//用鄰接表來存圖
//先定義圖的頂點資料結構,
typedef struct Node
{
int num;//頂點編號
char Alphabet;
//int visited;
}Node;
//無向圖的鄰接表表示,每個頂點對應一個列表
class UDGALGraph
{
private:
vector<Node*> vecNodes;//圖的結點地址向量,依次存每一個結點的地址
vector<vector<Node*>*> vexLists;//儲存圖中有向邊的資訊:每個頂點有一個鄰接表,該鄰接表上依次掛有其鄰接頂點的地址
int vexNum, edgeNum;
vector<int> visitedOrder;
public:
UDGALGraph()
{
CreateUDG();
}
~UDGALGraph()
{
DestroyDUG();
}
void CreateUDG()
{
cout << "請輸入無向圖的頂點個數和邊數目,然後依次輸入各條邊的兩個頂點資訊:" << endl;
cin >> vexNum >> edgeNum;
for (int i = 0; i < vexNum; i++)
{
Node* nodePtr = new Node;
nodePtr->num = i;
nodePtr->Alphabet = 'A' + i;
//nodePtr->visited = 0;
vecNodes.push_back(nodePtr);
vector<Node*>* vexVecPtr = new vector < Node* >;
vexLists.push_back(vexVecPtr);
visitedOrder.push_back(0);
}
char VexAlphabet1, VexAlphabet2;
int vexNo1, vexNo2;
for (int i = 0; i < edgeNum; i++)
{
cin >> VexAlphabet1 >> VexAlphabet2;
vexNo1 = VexAlphabet1 - 'A';
vexNo2 = VexAlphabet2 - 'A';
//無向圖,在鄰接表中存雙向邊
vexLists[vexNo1]->push_back(vecNodes[vexNo2]);
vexLists[vexNo2]->push_back(vecNodes[vexNo1]);
}
}
void DestroyDUG()
{
for (int i = 0; i < vexNum; i++)
{
delete vecNodes[i];
vecNodes[i] = nullptr;
}
for (int i = 0; i < vexLists.size(); i++)
{
delete vexLists[i];
}
}
void DFS(int v,int& count,bool& hasLoop)
{//從圖al的頂點v出發,遞迴地深度優先遍歷圖G
visitedOrder[v] = count++;
cout << vecNodes[v]->Alphabet << " ";
for (int i = 0; i < (vexLists[v]->size()); i++)
{
int nodeNum = vexLists[v]->at(i)->num;
if (visitedOrder[nodeNum] == 0)
{
DFS(nodeNum, count, hasLoop);
}
else if (visitedOrder[nodeNum]>visitedOrder[v])
{
hasLoop = true;
}
}
}
bool DFSTraverse()
{
int count = 1;
bool hasLoop = false;
for (int i = 0; i < vexNum; i++)
{
if (visitedOrder[i] == 0)
{
DFS(i, count, hasLoop);
}
}
/*
algraph是在堆中分配的結點,每次範圍後,其每一個結點的標誌位都設定為1了,退出時,
下次再遍歷前要清一下標誌位。
*/
for (int i = 0; i < vexNum; i++)
{
visitedOrder[i] = 0;
}
return hasLoop;
}
bool hasLoop()
{//對於無向圖來說,若深度優先遍歷過程中遇到回邊(即連線已經訪問過的頂點的鄰邊),則說明該無向圖存在環!
if (DFSTraverse())
{
return true;
}
else
{
return false;
}
}
};
//有向圖的鄰接表表示,每個頂點對應一個鄰接連結串列
class DGALGraph
{
private:
vector<Node*> vecNodes;//圖的結點地址向量,依次存每一個結點的地址
vector<vector<Node*>*> vexLists;//儲存圖中有向邊的資訊:每個頂點有一個鄰接表,該鄰接表上依次掛有其鄰接頂點的地址
int vexNum, edgeNum;
vector<int> InDegree;
vector<int> visited;
deque<int> topologicalSequence;//有向無環圖的拓撲序列
public:
DGALGraph()
{
CreateUDG();
}
~DGALGraph()
{
DestroyDUG();
}
void CreateUDG()
{
cout << "請輸入有向圖的頂點個數和邊數目,然後依次輸入各條邊的兩個頂點資訊:" << endl;
cin >> vexNum >> edgeNum;
for (int i = 0; i < vexNum; i++)
{
Node* nodePtr = new Node;
nodePtr->num = i;
nodePtr->Alphabet = 'A' + i;
//nodePtr->visited = 0;
vecNodes.push_back(nodePtr);
vector<Node*>* vexVecPtr = new vector < Node* >;
vexLists.push_back(vexVecPtr);
visited.push_back(0);
InDegree.push_back(0);
}
char VexAlphabet1, VexAlphabet2;
int vexNo1, vexNo2;
for (int i = 0; i < edgeNum; i++)
{
cin >> VexAlphabet1 >> VexAlphabet2;
vexNo1 = VexAlphabet1 - 'A';
vexNo2 = VexAlphabet2 - 'A';
//有向圖
vexLists[vexNo1]->push_back(vecNodes[vexNo2]);
InDegree[vexNo2]++;//
}
}
void DestroyDUG()
{
for (int i = 0; i < vexNum; i++)
{
delete vecNodes[i];
vecNodes[i] = nullptr;
}
for (int i = 0; i < vexLists.size(); i++)
{
delete vexLists[i];
}
}
bool topologicalSort()
{
cout << "有向圖的拓撲排序:" << endl;
stack<int> inDegree0VexStack;
for (int i = 0; i < vexNum; i++)
{
if (InDegree[i] == 0)
{
inDegree0VexStack.push(i);
}
}
int count = 0;//對輸出頂點計數
while (!inDegree0VexStack.empty())
{
int i = inDegree0VexStack.top();//輸入i號頂點,並計數
inDegree0VexStack.pop();
//cout << vecNodes[i]->Alphabet << " ";
cout << i + 1 << " ";
++count;
for (int j = 0; j < vexLists[i]->size(); j++)
{//對i號頂點的每一個鄰接頂點j的入度減1,即i->i的鄰接頂點
int nodeNum = vexLists[i]->at(j)->num;
if ((--InDegree[nodeNum] == 0))
{//若入度減到了0,則入棧
inDegree0VexStack.push(nodeNum);
}
}
}
if (count < vexNum)
{//該有向圖有環
return true;
}
else
{//該有向圖無環,可將所有頂點按拓撲有序輸出。
return false;
}
}
bool hasLoop()
{
if (topologicalSort())
{
cout << endl;
cout << "該有向圖有環!" << endl;
return true;
}
else
{
cout << endl;
cout << "該有向圖無環!" << endl;
return false;
}
}//topologicalSort
void DFS(int v)
{//從圖al的頂點v出發,遞迴地深度優先遍歷圖G
visited[v] = 1;
cout << vecNodes[v]->Alphabet << " ";
for (int i = 0; i < (vexLists[v]->size()); i++)
{
int nodeNum = vexLists[v]->at(i)->num;
if (visited[nodeNum] == 0)
{
DFS(nodeNum);
}
}
topologicalSequence.push_front(v);
}
//對於有向無環圖,也可以使用深度優先遍歷來求其拓撲序列!
//最先退出DFS函式的的頂點即出度為0的頂點,是拓撲排序序列中的最後一個頂點,
//按最先退出DFS函式的先後順序記錄下來的序列就是拓撲排序的逆序序列。
//類似於有向圖的強連通分量時的finish陣列,用以記錄退出DFS函式的先後順序!
//用一個雙端佇列來儲存,就可以順序輸出!
void DFSTraverse()
{
for (int i = 0; i < vexNum; i++)
{
if (visited[i] == 0)
{
DFS(i);
}
}
/*
algraph是在堆中分配的結點,每次範圍後,其每一個結點的標誌位都設定為1了,退出時,
下次再遍歷前要清一下標誌位。
*/
for (int i = 0; i < vexNum; i++)
{
visited[i] = 0;
}
}
void printTopoSeq()
{
cout << endl;
cout << "拓撲序列為:" << endl;
for (int i = 0; i < topologicalSequence.size(); i++)
{
cout << vecNodes[topologicalSequence[i]]->Alphabet << " ";
}
cout << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
//UDGALGraph udg;
//if (udg.hasLoop())
//{
// cout << "有環" << endl;
//}
//else
//{
// cout << "無環" << endl;
//}
//DGALGraph dg;
//dg.hasLoop();
DGALGraph dg2;
dg2.DFSTraverse();
dg2.printTopoSeq();
system("pause");
return 0;
}
/*
無向有環圖1:
A-B
A-D
B-C
C-A
4 4
A B
A D
B C
C A
請輸入無向圖的頂點個數和邊數目,然後依次輸入各條邊的兩個頂點資訊:
4 4
A B
A D
B C
C A
A B C D 有環
請按任意鍵繼續. . .
無向無環圖2:
4 3
A B
A D
B C
請輸入無向圖的頂點個數和邊數目,然後依次輸入各條邊的兩個頂點資訊:
4 3
A B
A D
B C
A B C D 無環
請按任意鍵繼續. . .
*/
/*
有向無環圖:
6 8
A B
A C
A D
C B
C E
D E
F D
F E
請輸入無向圖的頂點個數和邊數目,然後依次輸入各條邊的兩個頂點資訊:
6 8
A B
A C
A D
C B
C E
D E
F D
F E
有向圖的拓撲排序:
6 1 4 3 5 2
該有向圖無環!
請按任意鍵繼續. . .
*/
資料結構有向圖_拓撲排序_AOE關鍵路徑 圖判環