1,鄰接矩陣法中的殘留問題:ios
1,MatrixGraph 沒法動態添加/刪除頂點;編程
2,空間使用率低;數組
2,改進基本思想:函數
1,爲了進一步提升空間效率,能夠考慮使用鏈表替換數組,將鄰接矩陣變換爲鄰接鏈表;測試
2,佔用空間就是由於鄰接矩陣的問題,沒有鏈接也要佔用四個字節的空間,能夠考慮無鏈接不佔用空間的狀況;this
3,數組在定義的時候要指明有多少個元素,這樣可能致使浪費,能夠用鏈表,須要的時候再增長,不須要預約義一共有多少個元素;spa
3,鄰接鏈表法:設計
1,圖中的全部頂點按照編號存儲於同一個鏈表中;3d
1,原來存儲在數組中,這裏存儲在鏈表中;code
2,每個頂點對應一個鏈表,用於存儲始發於該頂點的邊;
1,鏈表中包含的信息見 3;
2,將數組改爲鏈表;
3,每一條邊的信息包含:起點,終值,權值;
4,無論多高深的設計方法,都是從最基本的設計方法中演變而來的,最基本的設計方法中,都有一些原則性的東西,好比這裏使用了將數組變換成鏈表來節省空間的原則;
5,鄰接鏈表的示例:
1,鏈表的鏈表,垂直的是一個鏈表,而鏈表每個數據元素當中還有另外一個鏈表做爲成員而存在;
6,設計與實現:
7,邊數據類型的設計:
1,由於鄰接鏈表裏面存儲的就是與邊相關的類型的對象了;
2,定義一個鄰接鏈表,鏈表裏面存儲的類型爲 Edge;
8,頂點數據類型的設計:
9,動態增長/刪除頂點:
1,int addVertex();
1,增長新的頂點,返回頂點編號;
2,增長新的頂點只能在末尾,由於不能打亂以前已經存在的頂點編號順序;
2,int addVertex(const V& value);
1,增長新頂點的同時附加數據元素;
3,void removeVertex();
1,刪除最近增長的頂點;
2,只能從末尾刪除,將最近添加的頂點刪除,不能說刪除一個頂點,其它頂點就亂套了;
10,圖的鄰接鏈表結構:
1 #ifndef LISTGRAPH_H 2 #define LISTGRAPH_H 3 4 #include "Graph.h" 5 #include "LinkList.h" 6 #include "Exception.h" 7 #include "DynamicArray.h" 8 9 namespace DTLib 10 { 11 12 /* 適用於內存資源受限場合 */ 13 template < typename V, typename E > 14 class ListGraph : public Graph<V, E> 15 { 16 protected: 17 /* 定義頂點 */ 18 struct Vertex : public Object 19 { 20 V* data; // 頂點的數據成員 21 LinkList< Edge<E> > edge; // 鄰接鏈表保存邊對象 22 Vertex() 23 { 24 data = NULL; 25 } 26 }; 27 28 /* 定義實際鄰接鏈表 */ 29 LinkList<Vertex*> m_list; // 實際的鄰接鏈表 30 public: 31 /* 構造一個新的有 n 個頂點的 ListGraph 對象 */ 32 ListGraph(unsigned int n = 0) 33 { 34 for(unsigned int i=0; i<n; i++) // 根據參數添加頂點 35 { 36 addVertex(); // 根據參數動態的增長頂點 37 } 38 } 39 40 /* 動態的增長一個新的頂點 */ 41 int addVertex() // O(n) 42 { 43 int ret = -1; 44 Vertex* v = new Vertex(); // 建立堆空間對象 45 46 if( v != NULL ) 47 { 48 m_list.insert(v); // 將頂點加入這個鏈表 O(n) 49 ret = m_list.length() - 1; // 返回新加結點在鏈表裏面的編號,在最後一個位置 50 } 51 else 52 { 53 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new vertex object ..."); 54 } 55 56 return ret; 57 } 58 59 /*動態的增長一個新的頂點的同時,將值設入 */ 60 int addVertex(const V& value) // O(n) 61 { 62 int ret = addVertex(); // 添加結點 63 64 if( ret >= 0 ) // 增長成功 65 { 66 setVertex(ret, value); // 頂點所在 ret 處值爲 value 67 } 68 69 return ret; 70 } 71 72 /* 設置頂點 i 處的值爲 value */ 73 bool setVertex(int i, const V& value) // O(n) 74 { 75 int ret = ( (0 <= i) && (i < vCount()) ); // 判斷 i 的合法性 76 77 if( ret ) 78 { 79 Vertex* vertex = m_list.get(i); // 將鏈表中 i 位置處元素取出來, O(n) 80 V* data = vertex->data; // data 指向具體的頂點數據元素 81 82 if( data == NULL ) // 沒有指向成功 83 { 84 data = new V(); // 動態的建立一個指向頂點相關的數據元素出來 85 } 86 87 if( data != NULL ) // 建立成功 88 { 89 *data = value; // 建立成功就將參數值 value 傳遞過去 90 vertex->data = data; // 將 data 保存下來 91 } 92 else 93 { 94 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new vertex value ..."); 95 } 96 } 97 98 return ret; 99 } 100 101 /* 將 i 位置處相關聯的頂點數據元素值返回 */ 102 V getVertex(int i) // O(n) 103 { 104 V ret; 105 106 if( !getVertex(i, ret) ) 107 { 108 THROW_EXCEPTION(InvalidParameterException, "Index i is invalid ..."); 109 } 110 111 return ret; 112 } 113 114 /* 將 i 位置處相關聯的頂點數據元素值賦值給引用 value */ 115 bool getVertex(int i, V& value) // O(n) 116 { 117 int ret = ( (0 <= i) && (i < vCount()) ); // 判斷 i 的合法性 118 119 if( ret ) 120 { 121 Vertex* v = m_list.get(i); // 獲得頂點相關數據 O(n) 122 123 if( v->data != NULL ) // 頂點關聯數據元素值 124 { 125 value = *(v->data); // 關聯就將數據值返回 126 } 127 else // 頂點沒有關聯數據 128 { 129 THROW_EXCEPTION(InvalidOperationException, "No value assigned to this vertex ..."); 130 } 131 } 132 133 return ret; 134 } 135 136 /* 刪除最近添加的頂點 */ 137 void removeVertex() // O(n*n) 138 { 139 if( m_list.length() > 0 ) // 當前圖中有頂點 140 { 141 int index = m_list.length() - 1; // 刪除最近添加的頂點,省得破壞圖的結構 142 Vertex* v = m_list.get(index); // 取出頂點相關的數據元素 O(n) 143 144 if( m_list.remove(index) ) // 首先刪除最後一個頂點 145 { 146 /* 這裏循環條件的先後分別利用了逗號表達式 */ 147 for(int i=(m_list.move(0), 0); !m_list.end(); i++, m_list.next() ) // 看其餘頂點當中有沒有和這個頂點相關聯的邊,有了要刪除 148 { 149 /* 當前結點臨界鏈表裏,查找與之關聯的邊,邊起點爲 i,終點爲 index,存在則 pos 非負*/ 150 int pos = m_list.current()->edge.find(Edge<E>(i, index)); // O(n),返回存在的下標 151 152 if( pos >= 0 ) 153 { 154 m_list.current()->edge.remove(pos); // 刪除當前頂點的元素,即相關聯的邊 155 } 156 } 157 158 delete v->data; // 釋放對應頂點數據元素值空間 159 160 delete v; // 頂點自身佔用的空間釋放掉 161 } 162 } 163 else 164 { 165 THROW_EXCEPTION(InvalidOperationException, "No vertex in current graph ..."); 166 } 167 } 168 169 /* 獲取從頂點 i 出發能夠抵達的頂點編號,以一個數組的方式返回;遍歷頂點 i 就能夠獲得與之相關的頂點了 */ 171 SharedPointer< Array<int> > getAdgacent(int i) // O(n) 172 { 173 DynamicArray<int>* ret = NULL; 174 175 if( (0 <= i) && (i < vCount()) ) 176 { 177 Vertex* vertex = m_list.get(i); // 從鏈表中獲取與頂點相關的數據元素 O(n) 178 179 ret = new DynamicArray<int>(vertex->edge.length()); // 建立返回值數組,個數是鄰接鏈表中邊的個數 180 181 if( ret != NULL ) 182 { 183 /* 這裏用了兩個逗號表達式 */ 184 for(int k=(vertex->edge.move(0), 0); !vertex->edge.end(); k++, vertex->edge.next()) // O(n) 185 { 186 ret->set(k, vertex->edge.current().e); // 獲取鄰接頂點邊的第二個元素,並將標號設置到要返回的數組中 O(1) 187 } 188 } 189 else 190 { 191 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create ret object ..."); 192 } 193 } 194 else 195 { 196 THROW_EXCEPTION(InvalidParameterException, "Index i is invalid ..."); 197 } 198 199 return ret; 200 } 201 202 /* 判斷 i 到 j 頂點邊是否鏈接,能找到就是鏈接的 */ 203 bool isAdjacent(int i, int j) 204 { 205 return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_list.get(i)->edge.find(Edge<E>(i, j)) >= 0); 206 } 207 208 E getEdge(int i, int j) // O(n) 209 { 210 E ret; 211 212 if( !getEdge(i, j, ret) ) 213 { 214 THROW_EXCEPTION(InvalidParameterException, "Edge <i, j> is invalid ..."); 215 } 216 217 return ret; 218 } 219 220 /* 獲取從 i 到 j 的邊的權值,放在 value 中 */ 221 bool getEdge(int i, int j, E& value) 222 { 223 int ret = ( (0 <= i) && (i < vCount()) && 224 (0 <= j) && (j < vCount()) ); 225 226 if( ret ) // O(n) 227 { 228 Vertex* vertex = m_list.get(i); // 獲得頂點 O(n) 229 int pos = vertex->edge.find(Edge<E>(i, j)); // 在頂點的鄰接鏈表中找找是否存在從 i 到 j 的邊,存在則 pos 非 O(n) 230 231 if( pos >= 0 ) 232 { 233 value = vertex->edge.get(pos).data; // 取出值O(n) 234 } 235 else 236 { 237 THROW_EXCEPTION(InvalidOperationException, "No valid assigned to this edge ..."); 238 } 239 } 240 241 return ret; 242 } 243 244 /* 設置從 i 到 j 的鄰接鏈表的值,存儲在 value 中 */ 245 bool setEdge(int i, int j, const E& value) // O(n) 246 { 247 int ret = ( (0 <= i) && (i < vCount()) && 248 (0 <= j) && (j < vCount()) ); 249 250 if( ret ) 251 { 252 Vertex* vertex = m_list.get(i); // 得到頂點 O(n) 253 int pos = vertex->edge.find(Edge<E>(i, j)); // 查找邊是否存在,存在則 pos 非零 O(n) 254 255 if( pos >= 0 ) 256 { 257 ret = vertex->edge.set(pos, Edge<E>(i, j, value)); // 設置鄰接鏈表當中對應位置處的值,用邊的構造函數直接賦值 258 } 259 else 260 { 261 ret = vertex->edge.insert(0, Edge<E>(i, j, value)); // 沒有邊時,插入一條邊和邊的權值,至於插入到那個位置,無所謂 262 } 263 } 264 265 return ret; 266 } 267 268 /* 刪除從 i 開始抵達 j 的邊;查找對應鄰接鏈表並刪除 */ 269 bool removeEdge(int i, int j) // O(n) 270 { 271 int ret = ( (0 <= j) && (i < vCount()) && 272 (0 <= j) && (j < vCount()) ); 273 274 if( ret ) 275 { 276 Vertex* vertex = m_list.get(i); // 取出感興趣的邊O(n) 277 278 int pos = vertex->edge.find(Edge<E>(i, j)); // 相應頂點中的邊是否存在,存在則 pos 非負 O(n) 279 280 if( pos >= 0 ) 281 { 282 ret = vertex->edge.remove(pos); // 刪除對應的點 O(n) 283 } 284 } 285 286 return ret; 287 } 288 289 /* 獲取圖中頂點個數,即鏈表中元素個數 */ 290 int vCount() // O(1) 291 { 292 return m_list.length(); // O(1) 293 } 294 295 /* 獲取邊的個數,獲取全部頂點的鄰邊個數之和*/ 296 int eCount() // O(n) 297 { 298 int ret = 0; 299 300 for(m_list.move(0); !m_list.end(); m_list.next()) 301 { 302 ret += m_list.current()->edge.length(); // 累加當前頂點的鄰邊個數 303 } 304 305 return ret; 306 } 307 308 /* 實現頂點 i 的入度函數 */ 309 int ID(int i) // O(n*n) 310 { 311 int ret = 0; 312 313 if( (0 <= i) && (i < vCount()) ) 314 { 315 for(m_list.move(0); !m_list.end(); m_list.next()) 316 { 317 LinkList< Edge<E> >& edge = m_list.current()->edge; // 定義當前鄰接鏈表別名,方便編程 318 for(edge.move(0); !edge.end(); edge.next()) 319 { 320 if( edge.current().e == i ) // 多少條終止頂點是頂點 i 321 { 322 ret++; 323 324 break; // 每一個頂點到另外一個頂點的邊的個數通常的只有一個,除非兩個頂點見有兩個權值不同的有向邊 325 } 326 } 327 } 328 } 329 else 330 { 331 THROW_EXCEPTION(InvalidParameterException, "Index i is invalid ..."); 332 } 333 334 return ret; 335 } 336 337 /* 獲取頂點 i 的出度 */ 338 int OD(int i) // O(n) 339 { 340 int ret = 0; 341 342 if( (0 <= i) && (i < vCount()) ) 343 { 344 ret = m_list.get(i)->edge.length(); // O(n) 345 } 346 else 347 { 348 THROW_EXCEPTION(InvalidParameterException, "Index i is invalid ..."); 349 } 350 351 return ret; 352 } 353 354 ~ListGraph() 355 { 356 while( m_list.length() > 0 ) 357 { 358 Vertex* toDel = m_list.get(0); 359 360 m_list.remove(0); 361 362 delete toDel->data; 363 364 delete toDel; 365 } 366 } 367 }; 368 369 } 370 371 #endif // LISTGRAPH_H
11,圖的鄰接鏈表法的測試代碼:
1 #include <iostream> 2 #include "ListGraph.h" 3 4 using namespace std; 5 using namespace DTLib; 6 7 int main() 8 { 9 ListGraph<char, int> g(4); 10 11 g.setVertex(0, 'A'); 12 g.setVertex(1, 'B'); 13 g.setVertex(2, 'C'); 14 g.setVertex(3, 'D'); 15 16 for(int i=0; i<g.vCount(); i++) 17 { 18 cout << i << " : " << g.getVertex(i) << endl; 19 } 20 21 ListGraph<char, int> g1; 22 23 g1.addVertex('A'); 24 g1.addVertex('B'); 25 g1.addVertex('C'); 26 g1.addVertex('D'); 27 28 // g1.removeVertex(); 29 30 for(int i=0; i<g1.vCount(); i++) 31 { 32 cout << i << " : " << g1.getVertex(i) << endl; 33 } 34 35 g1.setEdge(0, 1, 5); 36 g1.setEdge(0, 3, 5); 37 g1.setEdge(1, 2, 8); 38 g1.setEdge(2, 3, 2); 39 g1.setEdge(3, 1, 9); 40 41 cout << "W(0, 1) : " << g1.getEdge(0, 1) << endl; 42 cout << "W(0, 3) : " << g1.getEdge(0, 3) << endl; 43 cout << "W(1, 2) : " << g1.getEdge(1, 2) << endl; 44 cout << "W(2, 3) : " << g1.getEdge(2, 3) << endl; 45 cout << "W(3, 1) : " << g1.getEdge(3, 1) << endl; 46 47 cout << "eCount : " << g1.eCount() << endl; 48 49 // g1.removeEdge(3, 1); 50 // cout << "W(3, 1) : " << g1.getEdge(3, 1) << endl; 51 52 cout << "eCount : " << g1.eCount() << endl; 53 54 SharedPointer< Array<int> > aj = g1.getAdgacent(0); 55 56 for(int i=0; i<aj->length(); i++) 57 { 58 cout << (*aj)[i] << endl; 59 } 60 61 cout << "ID(1) : " << g1.ID(1) << endl; 62 cout << "OD(1) : " << g1.OD(1) << endl; 63 cout << "TD(1) : " << g1.TD(1) << endl; 64 65 g1.removeVertex(); 66 67 cout << "eCount : " << g1.eCount() << endl; 68 69 cout << "W(0, 1) : " << g1.getEdge(0, 1) << endl; 70 cout << "W(1, 2) : " << g1.getEdge(1, 2) << endl; 71 72 return 0; 73 }
12,同鏈表同樣,鏈表鄰接法對結點的操做也有編號,這個是對圖的結點操做的一個捷徑;
13,小結:
1,鄰接鏈表法使用鏈表對圖相關的數據進行存儲;
1,從鄰接矩陣改進而來,將數組改進爲鏈表;
2,每個頂點關聯一個鏈表,用於存儲邊相關的數據;
3,全部頂點按照編號被組織在同一個鏈表中;
4,鄰接鏈表法實現的圖可以支持動態添加和刪除頂點;