无向图
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);
}
};