實驗6 圖的應用
實驗六 圖的應用
[問題描述]
應用圖的ADT的物理實現來解決圖的應用問題。
某國的軍隊由N個部門組成,為了提高安全性,部門之間建立了M條通路,每條通路只能單向傳遞資訊,即一條從部門a到部門b的通路只能由a向b傳遞資訊。資訊可以通過中轉的方式進行傳遞,即如果a能將資訊傳遞到b,b又能將資訊傳遞到c,則a能將資訊傳遞到c。一條資訊可能通過多次中轉最終到達目的地。
由於保密工作做得很好,並不是所有部門之間都互相知道彼此的存在。只有當兩個部門之間可以直接或間接傳遞資訊時,他們才彼此知道對方的存在。部門之間不會把自己知道哪些部門告訴其他部門。
現在請問,有多少個部門知道所有N個部門的存在。或者說,有多少個部門所知道的部門數量(包括自己)正好是N。
[輸入形式]
輸入的第一行包含兩個整數N, M,分別表示部門的數量和單向通路的數量。所有部門從1到N標號。
接下來M行,每行兩個整數a, b,表示部門a到部門b有一條單向通路。
[輸出形式]
輸出一行,包含一個整數,表示答案。
一、問題分析
要處理的物件(資料):
從鍵盤鍵入的一組有向圖的邊儲存的資料所代表的整數。
要實現的功能:
將所有邊的資訊讀取完成後,輸出能通往所有頂點的頂點數和所有頂點都能彙總到的頂點數之和。
處理後的結果如何顯示:
將計算好符合條件的頂點數輸出到螢幕。
二、資料結構和演算法設計
1.抽象資料型別設計
【圖ADT的表示】 ADT IntegerSet{ 資料物件:一組有向圖的邊儲存的資料所代表的整數 資料關係:輸入的整數滿足有向圖中的頂點關係,可以用圖的ADT實現。 基本操作: Graph() {} // 預設建構函式 virtual void Init(int n) = 0;//初始化一個有n個頂點的圖 virtual int n() = 0;//返回頂點的數量 virtual int e() = 0;//返回邊的數量 virtual int first(int v) =0;// 返回v的第一個鄰居 virtual int next(int v, int w) =0;// 返回v的下一個鄰居 virtual int locateVex(char u) = 0;//找到(包含實際資訊的)頂點在圖中的位置 virtual int getVex(int v) = 0;//返回某個頂點的值(實際資訊) virtual void putVex(int v, int value) = 0;//給某個頂點賦值 virtual void setEdge(int v1, int v2, int wght) = 0; //設定邊的權重 virtual int weight(int v1, int v2) = 0;// Return:邊i、j或0的權值 virtual int getMark(int v) =0;//獲取頂點的標記值 virtual void setMark(int v, int val) =0;//設定頂點的標記值 }
2.物理資料物件設計
物理儲存方式:
由於使用圖的鄰接矩陣實現,採用二維指標實現整型的儲存,將邊和頂點的資訊儲存在計算機中。
3.演算法思想的設計:
通過迴圈讀取螢幕中輸入的每一條邊來構造一個鄰接矩陣,然後利用迴圈對n個頂點分別進行一次深度優先搜尋,用另一個二維陣列記錄下與該頂點有聯絡的點(也就是DFS可以到的點),比如頂點s和頂點e有聯絡(正向DFS可以到達的),那麼頂點e和頂點s就也有聯絡(反向DFS可以到達的),則利用二維陣列對應鄰接矩陣邊的位置上分別標記他們的聯絡為1,無關係則為初始值0,最後利用兩次迴圈統計一下每個頂點(包括自己)相關聯的點的個數,如果有n個,則能通往所有頂點的頂點數和所有邊數都能彙總到的頂點數的計數器加1,迴圈結束後輸出計數器所記錄的值。
4.樣例求解過程:
樣例輸入:
4 4
1 2
1 3
2 4
3 4
用鄰接矩陣構造圖:
開始利用迴圈對每一個頂點進行DFS,並記錄頂點之間的關係:
a.對1進行DFS,並改變二維陣列元素的值:
DFS結果:1-2-4-3
二維陣列元素改變:
b.對2進行DFS,並改變二維陣列元素的值:
DFS結果:2-4
二維陣列元素改變:
c.對1進行DFS,並改變二維陣列元素的值:
DFS結果:3-4
二維陣列元素改變:
d.對1進行DFS,並改變二維陣列元素的值:
DFS結果:4
二維陣列元素改變:
統計每個頂點與其他頂點關係,發現1與4都與其他頂點存在關係,計數器為2,輸出能通往所有頂點的頂點數和所有頂點都能彙總到的頂點數之和為2
5.關鍵功能的演算法步驟:
【構造鄰接矩陣】
//將輸入代表有向邊的兩個整數s1,s2在鄰接矩陣中對應的元素位置找出,利用基本操作將邊的權值置為1
G1->setEdge(G1->locateVex(s1), G1->locateVex(s2), 1);
【DFS演算法將二維陣列進行改變】
//傳入該圖和有關係的兩個頂點
void DFS(Graph *G,int v,int s)
{
//DFS演算法標記遍歷過該頂點
G->setMark(v,1);
//有關係的兩個頂點在二維陣列上標記
du[v+1][s+1]=du[s+1][v+1]=1;
//DFS演算法進入下一條未被訪問過的頂點
for(int w=G->first(v);w<G->n();w=G->next(v,w))
if(G->getMark(w)==0&&G->weight(v, w)!=0)//沒被訪問過且存在邊(w,v)
DFS(G,w,s);
}
【計數器通過迴圈計數】
for(int i=1;i<=n;i++){//雙重迴圈
int j;
for(j=1;j<=n;j++){
if(du[i][j]==0) break;
}
if(j==n+1) cmp++;//如果該頂點與每個頂點都有關係,則計數器加1
}
三、演算法效能分析
【構造鄰接矩陣】
for (int i = 0; i < m; i++)
{
int s1, s2;
cin >> s1 >> s2 ;
G1->setEdge(G1->locateVex(s1), G1->locateVex(s2), 1);
}
時間複雜度:利用迴圈構造,迴圈次數與邊數m有關,則時間複雜度為θ(m)
空間複雜度:利用二維指標儲存邊資訊,與邊數n有關,空間複雜度為θ(n*n)
【DFS演算法將二維陣列進行改變】
void DFS(Graph *G,int v,int s)
{
G->setMark(v,1);
du[v+1][s+1]=du[s+1][v+1]=1;
for(int w=G->first(v);w<G->n();w=G->next(v,w))
if(G->getMark(w)==0&&G->weight(v, w)!=0)
DFS(G,w,s);
}
時間複雜度:利用DFS遞迴遍歷,遞迴遍歷了n個頂點,時間複雜度為θ(n)
空間複雜度:利用二維陣列儲存頂點與頂點資訊,與邊數n有關,空間複雜度為θ(n*n)
【計數器通過迴圈計數】
for(int i=1;i<=n;i++){
int j;
for(j=1;j<=n;j++){
if(du[i][j]==0) break;
}
if(j==n+1) cmp++;
}
時間複雜度:利用雙層迴圈,迴圈次數與邊數n有關,其他操作所用時間開銷與輸入規模無關,則時間複雜度為θ(n*n)
空間複雜度:利用一個臨時變數進行計數,空間複雜度為θ(1)
四.原始碼設計
【ADT設計】
#ifndef GRAPH
#define GRAPH
class Graph{
public:
Graph() {} // 預設建構函式
//初始化一個有n個頂點的圖
virtual void Init(int n) = 0;
//返回:頂點和邊的數量
virtual int n() = 0;
virtual int e() = 0;
// 返回v的第一個鄰居
virtual int first(int v) =0;
// 返回v的下一個鄰居
virtual int next(int v, int w) =0;
//找到(包含實際資訊的)頂點在圖中的位置
virtual int locateVex(char u) = 0;
//返回某個頂點的值(實際資訊)
virtual int getVex(int v) = 0;
//給某個頂點賦值
virtual void putVex(int v, int value) = 0;
//設定邊的權重
virtual void setEdge(int v1, int v2, int wght) = 0;
// Return:邊i、j或0的權值
virtual int weight(int v1, int v2) = 0;
//獲取並設定頂點的標記值
// v:頂點
// val:要設定的值
virtual int getMark(int v) =0;
virtual void setMark(int v, int val) =0;
};
#endif
【ADT實現】
#include "iostream"
#include "graph.h"
using namespace std;
class Graphm : public Graph{
private:
int numVertex, numEdge; //頂點數,邊數
int *vexs;//儲存頂點資訊
int *mark; //標記陣列的指標
int **matrix; //鄰接矩陣的指標
public:
Graphm(int numVert) // 建構函式
{
Init(numVert);
}
//初始化一個有n個頂點的圖
void Init(int n)override
{
int i;
numVertex = n;
numEdge = 0;
vexs=(int *) new int[numVertex];
mark=(int *) new int[numVertex];
matrix = (int**) new int*[numVertex];
for (i=0; i<numVertex; i++)
{
mark[i]=0;
matrix[i] = new int[numVertex];
}
for (i=0; i< numVertex; i++)
for (int j=0; j<numVertex; j++)
matrix[i][j] = 0;
}
int n()override { return numVertex; }
int e()override { return numEdge; }
// 返回v的第一個鄰居
int first(int v)override {
for (int i=0; i<numVertex; i++)
if (matrix[v][i] != 0) return i;
return numVertex; // Return n if none
}
// 返回v在w之後的鄰居
int next(int v, int w)override {
for(int i=w+1; i<numVertex; i++)
if (matrix[v][i] != 0) return i;
return numVertex; // Return n if none
}
/**返回頂點在圖中的位置**/
int locateVex(char u)override {
for (int i = 0; i < numVertex; i++)
{
if (u==vexs[i]) return i;
}
return -1;
}
/**返回某個頂點的值(實際資訊) **/
int getVex(int v)override {
return vexs[v];
}
/**給某個頂點賦值**/
void putVex(int v, int value)override {
vexs[v] = value;
}
//將邊(v1, v2)設為“wt”
void setEdge(int v1, int v2, int wt)override {
if (matrix[v1][v2] == 0)
numEdge++;
matrix[v1][v2] = wt;
}
// Return:邊i、j或0的權值
int weight(int v1, int v2)override { return matrix[v1][v2]; }
//獲取並設定頂點的標記值
// v:頂點
// val:要設定的值
int getMark(int v)override { return mark[v]; }
void setMark(int v, int val)override { mark[v] = val; }
};
【主函式設計】
#include "graphm.h"
#include "graph.h"
using namespace std;
int du[100][100];
void DFS(Graph *G,int v,int s)
{
G->setMark(v,1);
du[v+1][s+1]=du[s+1][v+1]=1;
for(int w=G->first(v);w<G->n();w=G->next(v,w))
if(G->getMark(w)==0&&G->weight(v, w)!=0)//沒被訪問過且存在邊(v,j)
DFS(G,w,s);
}
int main() {
int n, m;
cin >> n >> m;
Graphm *G1 = new Graphm(n);
for (int i = 1; i <= n; i++)
{
G1->putVex(i-1, i);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
du[i][j]=0;
}
for (int i = 0; i < m; i++)
{
int s1, s2;
cin >> s1 >> s2 ;
G1->setEdge(G1->locateVex(s1), G1->locateVex(s2), 1);
}
int cmp = 0;
for (int i = 0; i < n; i++)
{
for(int j=0;j<n;j++)
{
G1->setMark(j,0);
}
DFS(G1,i,i);
}
for(int i=1;i<=n;i++){
int j;
for(j=1;j<=n;j++){
if(du[i][j]==0) break;
}
if(j==n+1) cmp++;
}
cout << cmp;
return 0;
}