在我之前的文章裡,提到 Graph 可用 Adjacency Matrix 或 Adjacency Lists 表示。
在這裡要討論的,是採用 C++ 物件導向建立 Graph 類別,讓此 Graph 內的 Node 支援任意型態的物件,並讓此 Graph 物件可當作有向或無向圖,且 Edge 可以分配權重,或設為無權重,也可將 Edge 用任意物件表示。
在此採用動態陣列儲存 Node 的資訊。不採用靜態的陣列來描述 Graph 有什麼優點呢?由於靜態陣列的大小是事先預定的,通常我們無法確切知道要處理的資料量到底有多大,因此為 Graph 採用動態新增 Node 的方式,是比較實際的作法。Graph 的 Class 實作方法很多,我通常將 Adjacency Lists 改為用動態指標陣列維護,並新增 function 來操作它們。
我習慣 Template 的編寫方式,讓 Graph 可以支援各種型態的資料:
T 用來表示要存到 Node 裡的物件類別,使用此 Graph 時,任意型態 T 都可當作此 Graph 的中的 Node。Edge 的預設型態是 double,代表預設 Graph 的每條邊用浮點數來設定權重,當然,也可用其他型態來當作 Edge,這是 Template 的好處。
Graph 的物件實作很簡單。在 Class 內定義以下 private 成員,就完成基本的 Graph 架構:
用 list 儲存 Node,而不用 vector 的原因,在於用迴圈走訪 Graph 中的 Node 時,如果經常一邊走訪一邊刪除 Node,此時 list 的 remove 函式較方便:
假如第 0 個 Node(簡稱 Node_0)有 3 個鄰居,則這 3 個鄰居的 index 即存在 neighbor_vec[0][0]、neighbor_vec[0][1] 跟 neighbor_vec[0][2] 內。
於是第 m 個 Node 的第 n 個鄰居的 index 為 neighbor_vec[m][n]。
而 edge_vec 存有每個 Node 關聯到的 Edge 指標,假如 Node_0 關聯到兩條 Edge,則 edge_vec[0][0] 和 edge_vec[0][1] 內就會儲存指向這兩個 Edge 的指標。
如上述程式碼所表示的,我們可以用 iterator 走訪 Graph。我們也可以輕易透過 index 的對應關係,來刪除 Node 並回收沒被用到的記憶體空間。
完整的 Graph 通常俱備以下功能:
Reference
Horowitz, E., Sahni, S., & Mehta D. (Eds.). (2006). Fundamental Of Data Structures in C++ (2nd ed.). New Jersey USA: Silicon Press.
在這裡要討論的,是採用 C++ 物件導向建立 Graph 類別,讓此 Graph 內的 Node 支援任意型態的物件,並讓此 Graph 物件可當作有向或無向圖,且 Edge 可以分配權重,或設為無權重,也可將 Edge 用任意物件表示。
在此採用動態陣列儲存 Node 的資訊。不採用靜態的陣列來描述 Graph 有什麼優點呢?由於靜態陣列的大小是事先預定的,通常我們無法確切知道要處理的資料量到底有多大,因此為 Graph 採用動態新增 Node 的方式,是比較實際的作法。Graph 的 Class 實作方法很多,我通常將 Adjacency Lists 改為用動態指標陣列維護,並新增 function 來操作它們。
我習慣 Template 的編寫方式,讓 Graph 可以支援各種型態的資料:
template < typename T, typename Edge = double > class MyGraph { // ... };
T 用來表示要存到 Node 裡的物件類別,使用此 Graph 時,任意型態 T 都可當作此 Graph 的中的 Node。Edge 的預設型態是 double,代表預設 Graph 的每條邊用浮點數來設定權重,當然,也可用其他型態來當作 Edge,這是 Template 的好處。
Graph 的物件實作很簡單。在 Class 內定義以下 private 成員,就完成基本的 Graph 架構:
list<T*> node_list; vector<vector<unsigned long> > neighbor_vec; vector<vector<E*> > edge_vec;node_list 內存著指標,每個指標指向 Graph 內每個 Node。
用 list 儲存 Node,而不用 vector 的原因,在於用迴圈走訪 Graph 中的 Node 時,如果經常一邊走訪一邊刪除 Node,此時 list 的 remove 函式較方便:
int node_index = 0; for(list<T*>::iterator it = node_list.begin(); it != node_list.end(); it++) { if(some_condition) delete *it; *it = NULL; node_index++; } node_list.remove((T*)NULL);而 neighbor_vec 存著每個 Node 相鄰的其他 Node 的 index。
假如第 0 個 Node(簡稱 Node_0)有 3 個鄰居,則這 3 個鄰居的 index 即存在 neighbor_vec[0][0]、neighbor_vec[0][1] 跟 neighbor_vec[0][2] 內。
於是第 m 個 Node 的第 n 個鄰居的 index 為 neighbor_vec[m][n]。
而 edge_vec 存有每個 Node 關聯到的 Edge 指標,假如 Node_0 關聯到兩條 Edge,則 edge_vec[0][0] 和 edge_vec[0][1] 內就會儲存指向這兩個 Edge 的指標。
如上述程式碼所表示的,我們可以用 iterator 走訪 Graph。我們也可以輕易透過 index 的對應關係,來刪除 Node 並回收沒被用到的記憶體空間。
完整的 Graph 通常俱備以下功能:
- 測試是否沒有任何 Node,即 Graph 是否為空。
- 回傳 Node 數目。
- 回傳 Edge 數目。
- 清空 Graph。
- 查詢某 Edge 是否存在。
- 加入 Node。
- 加入 Edge。
- 刪除 Node。
- 刪除 Edge。
- 訪問特定的 Node。
- 訪問特定 Node 的鄰居。
Reference
Horowitz, E., Sahni, S., & Mehta D. (Eds.). (2006). Fundamental Of Data Structures in C++ (2nd ed.). New Jersey USA: Silicon Press.
留言
張貼留言