無向圖
1.無向圖的定義
無向圖的定義:由一組頂點和一組能將兩個頂點相連的沒有方向的邊組成
自環:一條連接一個頂點和自身的邊
平行邊:連接同一對頂點的兩條邊
頂點的度數:依附於這個點的邊的總數
子圖:一幅圖的所有邊的一個子集
連通圖:從任意一個頂點都存在一條路徑到達另一個任意頂點,則該圖則為連通圖
2.無向圖的數據結構
常用的圖的表示方法
-
鄰接矩陣
通過一個V*V的布爾矩陣來存儲圖。當頂點v和頂點w相連接時,定義v行w列的元素為true,否則為false。這樣實現的圖需要的空間較大而且很難拓展。
-
邊的數組
使用一個Edge類,含有兩個int類型的變量來表示連接的兩個頂點。
-
鄰接表數組
使用一個以頂點為索引的列表數組,每個元素都是和該頂點相連的頂點。
代碼實現(以鄰接表實現):
class Graph
{
private:
int vertax; //頂點數
int edge; //邊數
vector<vector<int>> adjList; //鄰接表
public:
Graph(int V) //創建不含邊有V個頂點的圖
{
vertax = V;
edge = 0;
adjList.resize(V, vector<int>());
}
Graph(string in) //從文件輸入流讀入一幅圖
{
ifstream infile(in);
string ch;
int i = 0;
while (getline(infile, ch))
{
istringstream ss(ch);
int v, w;
if (i == 0) //讀入頂點數
{
ss >> vertax;
adjList.resize(vertax, vector<int>());
}
else if (i == 1) //讀入邊數
ss >> edge;
else //讀入邊
{
ss >> v >> w;
addEdge(v, w);
}
i++;
}
}
int V() { return vertax; } //返回頂點數
int E() { return edge; } //返回邊數
void addEdge(int v, int w) //添加一條邊v-w
{
adjList[v].push_back(w);
adjList[w].push_back(v);
}
vector<int> adj(int v) { return adjList[v]; } //返回頂點v的所有相鄰頂點
string toString() //字符串輸出圖
{
string s = to_string(vertax) + " vertices, " + to_string(edge) + " egdes\n";
for (int v = 0; v < vertax; v++)
{
s += to_string(v) + ": ";
for (int w : adjList[v])
s += to_string(w) + " ";
s += "\n";
}
return s;
}
int degree(int v) { return adjList[v].size(); } //返回頂點v的度數
int maxDegree() //返回度數最大的頂點
{
int max = 0;
for (int v = 0; v < vertax; v++)
{
if (degree(v) > max)
max = degree(v);
}
return max;
}
double avgDrgree() { return 2.0 * edge / vertax; } //返回平均頂點
int numsOfLoops() //統計自環的數量
{
int cnt = 0;
for (int v = 0; v < vertax; v++)
{
for (auto w : adjList[v])
if (v == w)
cnt++;
}
return cnt / 2;
}
};
3.深度優先搜索
深度優先搜索可以用很簡單一句話來概括就是:從一個頂點出發,不斷移動到盡頭,然后返回到上一個分支點,去另外一個方向,直到整張圖都被走過。
深度優先搜索可以解決類似從一個頂點是否能到達另一個頂點的問題。
對於下面這張圖,進行深度優先搜索,尋找所有從0為起點的路徑
從0出發有2和5兩個頂點可以選擇,我們先標記marked[0]=true,然后到達頂點2。將marked[2]標記為true,此時有1、3、4三個頂點可以選擇。我們先選擇頂點1,將marked[1]標記為true,此時1之后只有頂點0可以走,marked[0]==true 因此返回頂點2。我們再選擇頂點3,將marked[3]標記為true。不斷進行上述過程,直到將所有頂點全部遍歷。
代碼實現:
#include "UndiGraph.h"
class DepthFirstPaths //DFS尋找路徑
{
private:
vector<bool> marked; //標記走過的頂點
vector<int> edgeTo; //從起點到一個頂點的已知路徑上的最后一個頂點
int start; //起點
public:
DepthFirstPaths(Graph G, int s)
{
marked.resize(G.V(), false);
edgeTo.resize(G.V());
start = s; //起始點確定
dfs(G, s); //深度優先搜索
}
void dfs(Graph G, int v)
{
marked[v] = true;
for (auto w : G.adj(v))
{
if (!marked[w]) //對未遍歷的結點遞歸的進行深度優先搜索
{
edgeTo[w] = v;
dfs(G, w);
}
}
}
bool hasPathTo(int v) //從起始點能否到達v頂點
{
return marked[v];
}
void pathTo(stack<int> &path, int v) //從起始點到達頂點v的路徑
{
for (int x = v; x != start; x = edgeTo[x])
path.push(x);
path.push(start);
}
};
4.廣度優先搜索
當我們通過深度優先搜索尋找路徑的時候,很容易想知道從一個點到另一個點的最短路徑,而解決這種問題的最經典的算法就是廣度優先搜索。
從圖中的某一頂點出發,遍歷每一個頂點時,依次遍歷其所有的鄰接點,然后再從這些鄰接點出發,同樣依次訪問它們的鄰接點。按照此過程,直到圖中所有被訪問過的頂點的鄰接點都被訪問到。最后查看圖中是否存在尚未被訪問的頂點,若有,則以該頂點為起始點,重復上述遍歷的過程。
對於下面這張圖,進行深度優先搜索,尋找所有從0為起點的路徑
遍歷的過程如下圖
代碼實現:
#include "UndiGraph.h"
#include <queue>
class BreadFirstPath //BFS尋找最短路徑
{
private:
vector<bool> marked; //標記走過的頂點
vector<int> edgeTo; //從起點到一個頂點的已知路徑上的最后一個頂點
int start; //起點
public:
BreadFirstPath(Graph G, int s) //讀入圖和起始點
{
marked.resize(G.V(), false);
edgeTo.resize(G.V(), 0);
start = s;
bfs(G, s);
}
void bfs(Graph G, int v)
{
queue<int> q;
marked[v] = true;
q.push(start);
while (!q.empty()) //當隊列不為空時,將頭部的頂點壓出,將它的鄰接頂點壓入
{
int v = q.front();
q.pop();
for (int w : G.adj(v))
{
if (!marked[w])
{
edgeTo[w] = v;
marked[w] = true;
q.push(w);
}
}
}
}
bool hasPathTo(int v) //從起始點能否到達v頂點
{
return marked[v];
}
void pathTo(vector<int> &p, int v) //從起始點到達頂點v的最短路徑
{
for (int x = v; x != start; x = edgeTo[x])
p.push_back(x);
p.push_back(start);
}
};