文章目錄
1. 圖簡介
1.1 圖的分類
圖分爲
- 有向圖 (digraph): 即所有邊皆有向的圖
- 無向圖 (undigraph): 即所有邊皆無向的圖
- 混合圖(mixed graph): 即含有無向以及有向邊的圖
雖然圖分爲上面三種, 但是我們重點分析有向圖, 因爲所有的圖都可以由有向圖簡化得到
1.2 圖的描述
基於任意一個圖都可以由點集V 與邊集 E組成 , 我們可以對圖 G 做出如下描述
- V(vertex): V 代表圖的點集, 我們令 ,n代表頂點的個數
- E(edge): E 代表圖的邊集, 我們令, e代表邊的個數
1.2.1 邊的描述
我們使用 來描述一條邊
- 若u,v的次序無所謂, 則稱爲 無向邊(undirected edge), 容易看出,所有邊均無方向即爲無向圖
- 若u,v的次序需要考慮,則(u,v)稱爲有向邊(directed edge)
- u爲邊的尾(tail)
- v爲邊的頭(head)
1.2.2 路徑(path)/環路(circle)
路徑描述
所謂路徑, 就是我們所說的路徑, 它由有向邊構成, 所以我們可以做出如下的數學描述, 一條路徑可以由k個頂點的有序序列描述 , 爲路徑的起點, 爲路徑的終點
路徑分類
- path:
- simple path:即沒有重複節點的路徑, 即不存在 i j , 使得
- circle(環路): 即,起點與終點相同的路徑
- simple circle: 即不包含重複節點的circle環路
- DAG(有向無環圖): 有向但是不存在環路的圖 , 稱爲有向無環圖
- Hamiltonian tour(哈密爾頓環路): 每個頂點經過且只經過一次的環路, 稱爲哈密爾頓環路
- Eulerian tour(歐拉環路): 覆蓋了所有的邊,經過每一個邊一次且只經過一次的環路, 稱爲歐拉環路
2. 如何使用代碼描述圖
與一般的線性結構,樹型結構不同, 圖由邊和頂點組成, 那麼計算機中如何使用代碼描述圖呢?
2.1 鄰接矩陣和關聯矩陣
描述圖一般有兩種方式
- 鄰接矩陣
- 關聯矩陣
2.1.1 鄰接矩陣(adjacency matrix)
鄰接矩陣就是利用矩陣元素間關係來描述頂點關係的一種方式
行爲所有的頂點, 列也爲所有的頂點
若位置的元素爲1,則表示第個頂點爲起點, 第j個頂點爲終點的邊
A | B | C | D | |
---|---|---|---|---|
A | 1 | 1 | ||
B | 1 | |||
C | 1 | |||
D |
例如上面的矩陣, 代表的圖如下
2.1.2 關聯矩陣(incidence matrix)
在關聯矩陣中,** 行代表頂點 , 列 代表邊** , 矩陣中元素的值表明某一頂點與某一邊是否有關聯
注意因爲一條邊最多與兩個節點關聯, 因此如果1表示有關, 0表示無關,關聯矩陣中 的列(邊)最多有兩個1,其他均爲0
2.1.3 鄰接矩陣的優點與缺點
使用鄰接矩陣來描述圖, 直觀, 有效, 我們可以很方便的描述各種圖(有向圖, 網絡等),他的空間複雜度爲, 空間利用率極其低下
對於平面圖(邊與邊不相交)而言, 實際上我們的邊數都會遠小於頂點數, 邊數與頂點數由以下公式制約
---------------------------------1750-大數學家歐拉
3. 圖類定義
3.1 頂點模板類
頂點應該至少有如下屬性
- 頂點有三種狀態
UNDISCOVERED
DISCOVERED
VISITED
- 入度與出度:
- 時間標籤:
- 在遍歷樹中的父節點(int)
- 在遍歷樹中的優先級(最短通路, 極短跨邊等)(int)
代碼如下
typedef enum { UNDISCOVERED, DISCOVERED, VISITED } VStatus; //頂點狀態
template <typename Tv> struct Vertex{
Tv data;
int inDegree, outDegree;
VStatus status;
int dTime, fTime;
int parent;
int priority;
Vertex(Tv const & d):
data(d), inDegree(0),outDegree(0), status(UNDISCOVERED),
dTime(-1), fTime(-1), parent(-1),
priority(INT_MAX){}
};
3.2 邊模板類
邊模板類應該具有如下屬性
- 邊的狀態
UNDETERMINED
TREE
CROSS
FORWARD
BACKWARD
- 數據
- 權重
代碼如下
typedef enum { UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD }EStatus; //邊在遍歷樹中所屬的類型
template <typename Te> struct Edge{
Te data;
int weiget;
EStatus status;
Edge(Te const & d, int w):
data(d), weiget(w), status(UNDETERMINED){}
};
3.3 圖模板類
typedef enum { UNDISCOVERED, DISCOVERED, VISITED } VStatus; //頂點狀態
typedef enum { UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD } EType; //邊在遍歷樹中所屬的類型
template <typename Tv, typename Te> //頂點類型、邊類型
class Graph { //圖Graph模板類
private:
void reset() { //所有頂點、邊的輔助信息復位
for ( int i = 0; i < n; i++ ) { //所有頂點的
status ( i ) = UNDISCOVERED; dTime ( i ) = fTime ( i ) = -1; //狀態,時間標籤
parent ( i ) = -1; priority ( i ) = INT_MAX; //(在遍歷樹中的)父節點,優先級數
for ( int j = 0; j < n; j++ ) //所有邊的
if ( exists ( i, j ) ) type ( i, j ) = UNDETERMINED; //類型
}
}
void BFS ( int, int& ); //(連通域)廣度優先搜索算法
void DFS ( int, int& ); //(連通域)深度優先搜索算法
void BCC ( int, int&, Stack<int>& ); //(連通域)基於DFS的雙連通分量分解算法
bool TSort ( int, int&, Stack<Tv>* ); //(連通域)基於DFS的拓撲排序算法
template <typename PU> void PFS ( int, PU ); //(連通域)優先級搜索框架
public:
// 頂點
int n; //頂點總數
virtual int insert ( Tv const& ) = 0; //插入頂點,返回編號
virtual Tv remove ( int ) = 0; //刪除頂點及其關聯邊,返回該頂點信息
virtual Tv& vertex ( int ) = 0; //頂點v的數據(該頂點的確存在)
virtual int inDegree ( int ) = 0; //頂點v的入度(該頂點的確存在)
virtual int outDegree ( int ) = 0; //頂點v的出度(該頂點的確存在)
virtual int firstNbr ( int ) = 0; //頂點v的首個鄰接頂點
virtual int nextNbr ( int, int ) = 0; //頂點v的(相對於頂點j的)下一鄰接頂點
virtual VStatus& status ( int ) = 0; //頂點v的狀態
virtual int& dTime ( int ) = 0; //頂點v的時間標籤dTime
virtual int& fTime ( int ) = 0; //頂點v的時間標籤fTime
virtual int& parent ( int ) = 0; //頂點v在遍歷樹中的父親
virtual int& priority ( int ) = 0; //頂點v在遍歷樹中的優先級數
// 邊:這裏約定,無向邊均統一轉化爲方向互逆的一對有向邊,從而將無向圖視作有向圖的特例
int e; //邊總數
virtual bool exists ( int, int ) = 0; //邊(v, u)是否存在
virtual void insert ( Te const&, int, int, int ) = 0; //在頂點v和u之間插入權重爲w的邊e
virtual Te remove ( int, int ) = 0; //刪除頂點v和u之間的邊e,返回該邊信息
virtual EType & type ( int, int ) = 0; //邊(v, u)的類型
virtual Te& edge ( int, int ) = 0; //邊(v, u)的數據(該邊的確存在)
virtual int& weight ( int, int ) = 0; //邊(v, u)的權重
// 算法
void bfs ( int ); //廣度優先搜索算法
void dfs ( int ); //深度優先搜索算法
void bcc ( int ); //基於DFS的雙連通分量分解算法
Stack<Tv>* tSort ( int ); //基於DFS的拓撲排序算法
void prim ( int ); //最小支撐樹Prim算法
void dijkstra ( int ); //最短路徑Dijkstra算法
template <typename PU> void pfs ( int, PU ); //優先級搜索框架
};
3.4 鄰接矩陣類模板
鄰接矩陣至少應該由兩個集合:
- 頂點集: 由頂點集合組成
- 邊集: 由鄰接矩陣所表示的邊, 爲二維向量
template <typename Tv, typename Te>
class GraphMatrix: public Graph<Tv,Te>
{
private:
vector<Vertex<Tv>> V; //頂點集
vector< vector< Edge<Te >*> >E; // 由鄰接矩陣表示的邊集
public:
int n ;
int e;
GraphMatrix(){n = e = 0;}
~GraphMatrix(){
for(int j = 0; j < n; j++)
for(int k = 0 ; k< n ; k++)
delete E[j][k];
}
};
4. 圖類實現
4.1 頂點操作
4.1.1 鄰居枚舉
鄰居枚舉中也利用到了哨兵
的思想, 由於頂點總數爲 n
, 雖然在二維鄰接矩陣中最大索引爲n-1
, 我們不妨定義n爲一個哨兵, 來助於我們進行訪問與遍歷
template <typename Tv, typename Te>
int GraphMatrix<Tv,Te>::nextNbr(int i , int j){
while( (-1<j) && !exists(i,--j) );
return j;
}
注意虛函數要實現必須在子類中聲明
4.1.2 頂點插入
頂點插入我們需要做出下面三個步驟
- 更新鄰接矩陣的列數
- 更新鄰接矩陣的行數
- 創建並且插入節點, 可以由下面表示的圖表示
template <typename Tv, typename Te>
int GraphMatrix<Tv,Te>::insert(Tv const & j){
for(int j = 0; j <n ; j++) E[j].insert(NULL); n++;
E.insert(vector< Edge<Te>*> ( n, n ,NULL));
return V.insert(Vertex<Tv>(vertex()));
}
4.2 邊操作
4.2.1 判斷邊是否存在
template <typename Tv, typename Te>
bool GraphMatrix<Tv, Te>::exists(int i , int j){
return (0<=i && i<n) && (0<=j && j<n) && \
E[i][j] !=NULL;
}
4.2.2 插入/刪除一條邊
我們需要做出如下操作
- 如果已經有邊則直接返回 , 不操作
- 如果沒有邊, 則新建邊插入, 並且更新對應頂點的出度與入度, 以及圖的總數
template <typename Tv, typename Te>
void GraphMatrix<Tv, Te>::insert(Te const & edge, int w, int i ,int j){
if(exists(i,j)) return ;
E[i][j] = new Edge<Te>(edge, w);
e++;
this->V[i].outDegree++;
this->V[i].inDegree++;
}
刪除只需執行相反的操作
template <typename Tv, typename Te>
Te GraphMatrix<Tv, Te>::remove(int i ,int j){
Te eBak = edge(i,j);
delete E[i][j]; E[i][j] = NULL; //刪除邊
e--;
this->V[i].outDegree--;
this->V[i].inDegree--;
}