數據結構——圖
1.1 基礎知識:
1.2 圖的表達方式:
參考書籍: 算法(第4版).[美]Robert Sedgewick
必須要滿足以下兩點:
- 必須為可能在實際應用中碰到的各種類型的圖留出足夠的空間。
- Graph 的實例方法的實現一定要快,速度很重要!!!
1.鄰接矩陣:
1)鄰接矩陣簡介
邏輯結構分為兩部分:
Vexs[ ](存儲頂點)用一個一維數組存放圖中所有頂點數據;
Edges[ ][ ](鄰接矩陣)集合,用一個二維數組存放頂點間關系(可以是權重,也可以是0,1)的數據,這個二維數組稱為鄰接矩陣。
typedef struct AdjMatrix{ int *Vexs;int **Edges; int vexnum, edgenum; // 頂點數,邊數 }*Graph;
2)鄰接矩陣的特點:
- 對於無向圖來說,鄰接矩陣一定是對稱的;
- 如果是簡單圖,主對角線一定為零,副對角線不一定;
- 頂點Vi的度等於第i行非零元個數,或第i列非零元個數;(有向圖只能是行)
- 無向圖矩陣非零元總數等於邊數的2倍,有向圖相同;
3)完整實現:
規定輸入:
- 輸入頂點數 g.vexnum 和 邊數 g.edgenum;
- 輸入n個頂點;
- 輸入邊的偶對 (vi,vj) 或 <vi,vj> ;
#include <stdio.h> #include<string.h> #include <malloc.h> typedef struct AdjMatrix{ int *Vexs; int **Edges; int vexnum, edgenum; // 頂點數,邊數 }*Graph; //vn:vexnum;en:edgenum Graph init(int vn,int en) { Graph g = (Graph)malloc(sizeof(AdjMatrix)); g->vexnum = vn; g->edgenum = en; g->Vexs = (int*)malloc(sizeof(int)*vn); for (int i = 0; i < vn; i++) { scanf("%d",&g->Vexs[i]); } g->Edges = (int **)malloc(sizeof(int*)*vn); for (int i = 0; i < vn; i++) { g->Edges[i] = (int *)malloc(sizeof(int)*vn); memset(g->Edges[i], 0, sizeof(int)*vn); } for (int i = 0; i < en; i++) { int column,row; scanf("%d%d", &row,&column); g->Edges[row - 1][column - 1] = 1; //有向圖把下面一行註釋掉 g->Edges[column - 1][row - 1] = 1; } return g; } void traverse(Graph g) { for (int i = 0; i < g->vexnum; i++) { for (int j = 0; j < g->vexnum; j++) { printf("%4d ", g->Edges[i][j]); } printf("\n"); } } int main() { int vn, en;//頂點個數、邊個數 scanf("%d%d",&vn,&en); Graph g = init(vn, en); traverse(g); return 0; }
4)鄰接矩陣圖例:
寫到這也能看出來了鄰接矩陣可以快速判斷兩個頂點之間是否存在邊、添加邊或者刪除邊(通過索引 O(1))。
但是缺點也很致命存儲稀疏矩陣時,十分較浪費空間,這顯然不滿足第一個條件。也就引申出了鄰接表。
2. 鄰接表:
1)基本特點:
鄰接表是一種順序分配和鏈式分配相結合的存儲結構。可以說是簡化版的十字鏈表。
N個頂點,對應N個表頭節點,如果某個表頭結點,所對應的頂點存在相鄰頂點,則把相鄰頂點依次(按頂點順序)存放於表頭結點所指向的單向鏈表中。
2)完整實現(無向圖):
規定輸入:
- 邊數 g.edgenum;(根據輸入的邊關系,可以得到頂點。)
- 輸入邊的偶對 (vi,vj) 或 <vi,vj> ;
完整代碼(不帶權重):
#include <iostream> #include <map> #include <list> #include <queue> #include <set> using namespace std; class node { public: int data; node* next; node(int d) { data = d; next = NULL; } }; class AdjList { public: map<int, node*> listhead; int vexnum, edgenum; // 頂點數,邊數 AdjList(int vn, int en) { vexnum = vn; edgenum = en; for (int i = 0; i < en; i++) { int from, to; cin >> from >> to; if (!listhead[from]) { listhead[from] = new node(from); } if (!listhead[to]) { listhead[to] = new node(to); } node *s = new node(to); s->next = listhead[from]->next; listhead[from]->next = s; //有向圖就是把下面三行刪掉 s = new node(from); s->next = listhead[to]->next; listhead[to]->next = s; } } }; typedef AdjList* Graph; void print(Graph g) { map<int, node *>::iterator it = g->listhead.begin(); while (it != g->listhead.end()) { node *p = it->second->next; cout << it->second->data << " :"; while (p) { cout << p->data << " "; p = p->next; } cout << endl; it++; } }
鄰接表和鄰接矩陣這兩種方式是最常見的,但是鄰接表還是會有部分空間浪費,因為每條鏈表裏用的node是獨立的。
3. 自己實現的一種:
看了嚴蔚敏教授的數據結構教材書上的數據結構,我認為真的太繁瑣了,
#define N 200 typedef struct ArcNode { int adjvex;//該弧指向的頂點的位置 ArcNode *nextarc;//指向下一條弧的指針 int *info; //該弧的相關信息 }; typedef struct { VertexType data;//頂點信息 ArcNode *firstarc; //指向第一條依附該頂點的弧的指針 } VNode,AdjList[N]; typedef struct ALGraph { AdjList vertices; int vexnum,arcnum;//弧的當前頂點數和弧數 int kind;//圖的種類 };
自己寫了一個,借助map,直接可以用下標來找表頭了。
class node { public: int data;//該節點的數據 vector<node *>next; vector<int>weight; }; class AdjList { public: map<int, node *>Nodes; }
我覺得算是鄰接表的升級版吧,後面有完整實現,簡單分析了一下優劣(E:邊數,V:頂點數):
- 內存:相同data不會重復建立節點:O(V),這點上一定是優於鄰接表的:O(V+E)。
- 存取操作:首先要找到起點所在的表頭,鄰接表也可以用map實現表頭,這點就算無差異吧。
- 檢測兩條邊是否相鄰、遍歷V的所有相鄰節點:都是 V 頂點的度數,無差異。
- 關鍵是更能想象出畫面。
1.3 圖的遍歷方式:
1. 廣度優先遍歷(BFS):
廣度優先搜索算法(Breadth-First-Search,縮寫為 BFS),是一種利用隊列實現的搜索算法。其搜索過程和 “水滴落下產生波紋” 類似。
因為每條鏈表裏相同data的節點時獨立存在的,BFS、DFS要判斷當前節點是否輸出過,就要放到set裏,如果存節點的話,那肯定得用set<node *>;因為set<node>的話,set裏存的就不是指針了,占空間更多。但是存指針問題就來了,相同data的節點是獨立的,那麽set裏就只能添加node->data
對於遍歷而言,沒有有向圖、無向圖之分。這裏不考慮非連通圖。(非連通,再加一個大循環,很好實現)
void BFS() { queue<node *>queue; set<node*>set; node *n = Nodes.begin()->second; queue.push(n); set.insert(n); cout << n->data << " "; while (!queue.empty()) { node *p = queue.front(); queue.pop(); for (int i = 0; i < p->next.size(); i++) { if (set.find(p->next[i]) == set.end()) { cout << p->next[i]->data << ‘ ‘; set.insert(p->next[i]); queue.push(p->next[i]); } } } }
2. 深度優先遍歷(DFS):
深度優先搜索算法(Depth-First-Search,縮寫為 DFS),是一種利用遞歸(棧)實現的搜索算法。其過程像 “不撞南墻不回頭” 類似。
void DFS() { stack<node *>stack; set<node *>set; node *n = Nodes.begin()->second; stack.push(n); set.insert(n); while (!stack.empty()) { node *p = stack.top(); stack.pop(); cout << p->data << ‘ ‘; for (int i = 0; i < p->next.size(); i++) { if (set.find(p->next[i]) == set.end()) { set.insert(p->next[i]); stack.push(p->next[i]); } } } }
留檔一下:鄰接表的BFS、DFS
void BFS(Graph g) { queue<node *>q; set<int>s; map<int, node*>::iterator it = g->listhead.begin(); node *n = it->second; q.push(n); s.insert(n->data); cout << n->data << " "; while (!q.empty()) { node *p = q.front(); q.pop(); while (p) { if (s.find(p->data) == s.end()) { cout << p->data << " "; s.insert(p->data); q.push(g->listhead[p->data]); } p = p->next; } } } void DFS(Graph g) { stack<node *>stack; set<int>set; map<int, node*>::iterator it = g->listhead.begin(); node *p = it->second; stack.push(p); set.insert(p->data); while (!stack.empty()) { p = stack.top(); stack.pop(); cout << p->data << " "; while (p->next) { p = p->next; if (set.find(p->data) == set.end()) { stack.push(g->listhead[p->data]); set.insert(p->data); } } } }View Code
第三種實現的完整代碼:
class node { public: int data;//該節點的數據 vector<node *>next; vector<int>weight; node(int data) { this->data = data; } }; class AdjList { public: map<int, node *>Nodes; AdjList(int en) { while (en--) { int from, to, weight; cin >> from >> to >> weight; if (!Nodes[from]) { Nodes[from] = new node(from); } if (!Nodes[to]) { Nodes[to] = new node(to); } node *p = Nodes[from], *q = Nodes[to]; p->next.push_back(q); p->weight.push_back(weight); q->next.push_back(p); q->weight.push_back(weight); } } /*void BFS() { map<int, node* >::iterator it = Nodes.begin(); node *n = it->second; queue<node *>q; set<node *>s; q.push(n); s.insert(n); while (!q.empty()) { node *n = q.front(); q.pop(); cout << n->data << " "; for (int i = 0; i < n->next.size(); i++) { node *p = n->next[i]; if (s.find(p) == s.end()) { s.insert(p); q.push(p); } } } }*/ void BFS() { queue<node *>queue; set<node*>set; node *n = Nodes.begin()->second; queue.push(n); set.insert(n); cout << n->data << " "; while (!queue.empty()) { node *p = queue.front(); queue.pop(); for (int i = 0; i < p->next.size(); i++) { if (set.find(p->next[i]) == set.end()) { cout << p->next[i]->data << ‘ ‘; set.insert(p->next[i]); queue.push(p->next[i]); } } } } void DFS() { stack<node *>stack; set<node *>set; node *n = Nodes.begin()->second; stack.push(n); set.insert(n); while (!stack.empty()) { node *p = stack.top(); stack.pop(); cout << p->data << ‘ ‘; for (int i = 0; i < p->next.size(); i++) { if (set.find(p->next[i]) == set.end()) { set.insert(p->next[i]); stack.push(p->next[i]); } } } } }; typedef AdjList *Graph;View Code
寫了幾題BFS、DFS的練習題,才發現,DFS BFS是思想,基本上是碰不到能完全套模板的題目。
---------這---------是---------一---------條---------分---------割---------線---------
數據結構——圖