文章目录
- 连通图与连通分量
- 强连通图与强连通分量
- 图的连通性判断
- 生成树
- 深度优先生成树
- 邻接表
- 邻接矩阵
- 广度优先生成树
- 邻接表
- 邻接矩阵
- 生成森林
- 获取边弧的权值
- 源代码
连通图与连通分量
在无向图中, 若从顶点v到顶点w有路径存在, 则称v和w是连通的. 若图G中任意两个顶点都是连通的, 则称图G为连通图, 否则称为非连通图. 无向图中的极大连通子图称为连通分量, 在图(a)中, 图G有3个连通分量如图(b)所示.
假设一个图有n个顶点, 如果边数小于n-1, 那么此图必是非连通图. 如果图是非连通图, 那么最多可以有多少条边?
强连通图与强连通分量
在有向图中, 如果有一对顶点v和w, 从v到w和从w到v之间都有路径, 则称这两个顶点是强连通的. 若图中任何一对顶点都是强连通的, 则称此图为强连通图. 有向图中的极大强连通子图称为有向图的强连通分量, 图G的强连通分量如图(b)所示。
假设一个有向图有n个顶点, 如果是强连通图, 那么最少需要有多少条边.
图的连通性判断
图的遍历算法可以用来判断图的连通性.
对于无向图来说, 若无向图是连通的, 则从任一结点出发, 仅需一次遍历就能够访问图中的所有顶点; 若无向图是非连通的, 则从某一个顶点出发, 一次遍历只能访问到该顶点所在连通分量的所有顶点, 而对于图中其他连通分量的顶点, 则无法通过这次遍历访问.
对于有向图来说, 若从某一顶点到图中的每个顶点都有路径, 则能够访问到图中的所有顶点, 否则不能访问到所有顶点. 因此判断有向图的强连通性, 需要依次从所有顶点出发遍历, 只有从所有顶点出发遍历到其它所有顶点, 该有向图才具有强连通性, 如果从某一个顶点出发遍历, 不能连续遍历到其它顶点, 则该有向图不具有强连通性.
邻接表
bool _IsConnected(int srci)
{
int n = static_cast<int>(_vertexSet.size());
vector<bool> markbit(n, false);
queue<int> q;
q.push(srci);
markbit[srci] = true;
// 类似广度优先遍历的思想
while (!q.empty())
{
auto front = q.front();
q.pop();
Edge* curr = _table[front];
while (curr != nullptr)
{
if (!markbit[curr->_dsti])
{
q.push(curr->_dsti);
markbit[curr->_dsti] = true;
}
curr = curr->_next;
}
}
// 如果还有顶点没有遍历到,说明不是连通图
for (int i = 0; i < n; i++)
{
if (!markbit[i])
{
return false;
}
}
return true;
}
bool IsConnected()
{
if (_vertexSet.empty())
return true;
if (Directed) // 有向图的强连通性判断
{
int n = static_cast<int>(_vertexSet.size());
// 需要依次从所有顶点出发遍历
for (int i = 0; i < n; i++)
{
if (!_IsConnected(i))
{
return false;
}
}
return true;
}
else // 无向图的连通性判断
{
return _IsConnected(0);
}
}
邻接矩阵
bool _IsConnected(int srci)
{
int n = static_cast<int>(_vertexSet.size());
vector<bool> markbit(n, false);
queue<int> q;
q.push(srci);
markbit[srci] = true;
while (!q.empty())
{
auto front = q.front();
q.pop();
for (int i = 0; i < n; i++)
{
if (_matrix[front][i] != W_MAX && !markbit[i])
{
q.push(i);
markbit[i] = true;
}
}
}
for (int i = 0; i < n; i++)
{
if (!markbit[i])
{
return false;
}
}
return true;
}
bool IsConnected()
{
if (_vertexSet.empty())
return false;
if (Directed)
{
int n = static_cast<int>(_vertexSet.size());
for (int i = 0; i < n; i++)
{
if (!_IsConnected(i))
{
return false;
}
}
return true;
}
else
{
return _IsConnected(0);
}
}
生成树
一个连通图的生成树是一个极小连通子图, 它含有图中全部n个顶点, 但只有足以构成一棵树的n-1条边. 在生成树中添加一条边之后, 必然会形成回路/环. 一般来说, 一个连通图的生成树并不是唯一的, 除非原图本身就是一棵树.
深度优先生成树
采用DFS算法遍历图所得到的生成树称为深度优先生成树.
邻接表
邻接矩阵
广度优先生成树
采用BFS算法遍历图所得到的生成树称为广度优先生成树.
邻接表
邻接矩阵
生成森林
若无向图G是非连通图, 从图中某一顶点出发遍历图, 不能访问到该图的所有顶点, 需要依次对图中的每一个连通分量进行深度优先遍历或者广度优先遍历, 即需要从多个顶点出发进行DFS或者BFS.
在遍历过程中, 如果将每次前进途中路过的顶点与边记录下来, 将会得到多棵树, 从而构成森林.
- 采用DFS算法遍历图所得到的生成森林称为广度优先生成森林.
- 采用BFS算法遍历图所得到的生成森林称为广度优先生成森林.
获取边弧的权值
邻接表
// 获取两个相连顶点之间边的权值
const W& GetEdgeWeight(const V& src, const V& dst)
{
int srci = GetVertexIndex(src);
int dsti = GetVertexIndex(dst);
if (srci == -1 || dsti == -1)
{
return W(); // 可以选择抛异常
}
Edge* curr = _table[srci];
while (curr!=nullptr)
{
if (curr->_dsti == dsti)
{
return curr->_weight;
}
curr=curr->_next;
}
return W();
}
邻接矩阵
// 获取两个相连顶点之间边的权值
const W& GetEdgeWeight(const V& src, const V& dst)
{
int srci = GetVertexIndex(src);
int dsti = GetVertexIndex(dst);
if (srci == -1 || dsti == -1)
{
return W(); // 可以选择抛异常
}
return _matrix[srci][dsti];
}
源代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <vector>
#include <string>
#include <queue>
#include <map>
using namespace std;
namespace AdjacentMatrix
{
template<typename V, typename W, W W_MAX, bool Directed = false>
class Graph
{
private:
std::vector<V> _vertexSet;
std::map<V, int> _vertexIndex;
std::vector<std::vector<W>> _matrix;
public:
typedef Graph<V, W, W_MAX, Directed> Self;
Graph() = default;
int GetVertexIndex(const V& v)
{
typename std::map<V, int>::iterator pos = _vertexIndex.find(v);
if (pos != _vertexIndex.end())
{
return pos->second;
}
else
{
return -1;
}
}
// 获取两个相连顶点之间边的权值
const W& GetEdgeWeight(const V& src, const V& dst)
{
int srci = GetVertexIndex(src);
int dsti = GetVertexIndex(dst);
if (srci == -1 || dsti == -1)
{
return W(); // 可以选择抛异常
}
return _matrix[srci][dsti];
}
bool AddVertex(const V& v)
{
// 顶点存在不需要继续增加
if (GetVertexIndex(v) != -1)
return false;
_vertexSet.push_back(v);
_vertexIndex.insert(std::make_pair(v, _vertexSet.size() - 1));
// 先在原有的行上一列
for (int i = 0; i < _matrix.size(); i++)
{
_matrix[i].push_back(W_MAX);
}
// 增加一行
_matrix.push_back(std::vector<W>(_vertexSet.size(), W_MAX));
return true;
}
bool AddEdge(const V& src, const V& dst, const W& weight)
{
int srci = GetVertexIndex(src);
int dsti = GetVertexIndex(dst);
// 顶点不在图中,添加边失败
if (srci == -1 || dsti == -1)
return false;
_matrix[srci][dsti] = weight;
// 如果为无向图,则需要再添加一条dst->src的边
if (!Directed)
{
_matrix[dsti][srci] = weight;
}
return true;
}
bool bfsSpanningTree(Self& spanningTree, const V& src)
{
int srci = GetVertexIndex(src);
// 遍历源点不存在,不能进行遍历
if (srci == -1)
{
return false;
}
// 只有图为无向图且是连通图才能选出生成树
if (Directed && !IsConnected())
return false;
// 将spanningTree拷贝成与当前图相同顶点的零图
spanningTree._vertexSet = _vertexSet;
spanningTree._vertexIndex = _vertexIndex;
spanningTree._matrix.resize(_vertexSet.size(), vector<W>(_vertexSet.size(), W_MAX));
vector<bool> visited(_vertexSet.size(), false);
// pair.first是pair.second的上一层顶点下标
queue<pair<int,int>> q;
// 将遍历源点下标入队,并标记访问过
q.push({-1,srci});
visited[srci] = true;
while (!q.empty())
{
pair<int,int> front = q.front();
q.pop();
int first = front.first; // 当前遍历顶点的上一层顶点
int second = front.second; // 当前遍历顶点
if (first != -1)
{
// 将选出的边添加到spanningTree中
std::cout << _vertexSet[first] << "<--->" << _vertexSet[second] << std::endl;
spanningTree.AddEdge(_vertexSet[first], _vertexSet[second], _matrix[first][second]);
}
// 将与front.second相连的顶点且没有被访问的顶点入队
for (int i = 0; i < static_cast<int>(_vertexSet.size()); i++)
{
if (_matrix[second][i] != W_MAX && !visited[i])
{
// 入队之后标记访问过
q.push({second,i});
visited[i] = true;
}
}
}
return spanningTree.IsConnected();
}
void _dfsSpanningTree(Self& spanningTree, int srci, int& previ, vector<bool>& visited)
{
if (previ != -1)
{
// 将选出的边添加到spanningTree中
std::cout << _vertexSet[previ] << "<--->" << _vertexSet[srci] << std::endl;
spanningTree.AddEdge(_vertexSet[previ], _vertexSet[srci],_matrix[previ][srci]);
}
// 标记访问过
visited[srci] = true;
// 更新前一个顶点的下标
previ = srci;
// 与DFS遍历的思想相同
for (int i = 0; i < static_cast<int>(_vertexSet.size()); i++)
{
if (_matrix[srci][i] != W_MAX && !visited[i])
{
_dfsSpanningTree(spanningTree, i, previ, visited);
}
}
}
bool dfsSpanningTree(Self& spanningTree, const V& src)
{
int srci = GetVertexIndex(src);
// 遍历源点不存在,不能进行遍历
if (srci == -1)
{
return false;
}
// 只有图为无向图且是连通图才能选出生成树
if (Directed && !IsConnected())
return false;
// 将spanningTree拷贝成与当前图相同顶点的零图
spanningTree._vertexSet = _vertexSet;
spanningTree._vertexIndex = _vertexIndex;
spanningTree._matrix.resize(_vertexSet.size(), vector<W>(_vertexSet.size(), W_MAX));
vector<bool> visited(_vertexSet.size(), false);
int previ = -1; // 遍历访问的前一个顶点下标
_dfsSpanningTree(spanningTree, srci, previ, visited);
return spanningTree.IsConnected(); // 如果spanningTree是连通图则说明构建生成树成功
}
bool _IsConnected(int srci)
{
int n = static_cast<int>(_vertexSet.size());
vector<bool> markbit(n, false);
queue<int> q;
q.push(srci);
markbit[srci] = true;
while (!q.empty())
{
auto front = q.front();
q.pop();
for (int i = 0; i < n; i++)
{
if (_matrix[front][i] != W_MAX && !markbit[i])
{
q.push(i);
markbit[i] = true;
}
}
}
for (int i = 0; i < n; i++)
{
if (!markbit[i])
{
return false;
}
}
return true;
}
bool IsConnected()
{
if (_vertexSet.empty())
return false;
if (Directed)
{
int n = static_cast<int>(_vertexSet.size());
for (int i = 0; i < n; i++)
{
if (!_IsConnected(i))
{
return false;
}
}
return true;
}
else
{
return _IsConnected(0);
}
}
};
void TestMatrix3()
{
AdjacentMatrix::Graph<std::string, int, INT_MAX> g;
g.AddVertex("A");
g.AddVertex("B");
g.AddVertex("C");
g.AddVertex("D");
g.AddVertex("E");
g.AddVertex("F");
g.AddVertex("G");
g.AddEdge("A", "B", 1);
g.AddEdge("A", "C", 1);
g.AddEdge("B", "D", 1);
g.AddEdge("B", "E", 1);
g.AddEdge("C", "E", 1);
g.AddEdge("C", "F", 1);
g.AddEdge("D", "G", 1);
g.AddEdge("E", "G", 1);
g.AddEdge("F", "G", 1);
AdjacentMatrix::Graph<std::string, int, INT_MAX> spanningTree;
//g.dfsSpanningTree(spanningTree, "A");
g.bfsSpanningTree(spanningTree, "A");
std::cout << spanningTree.IsConnected() << std::endl;
}
}
namespace AdjacentList
{
template<typename W>
struct Edge
{
int _dsti;
W _weight;
struct Edge<W>* _next;
Edge(int dsti, const W& weight)
:_dsti(dsti)
, _weight(weight)
, _next(nullptr)
{}
};
template<typename V, typename W, bool Directed = false>
class Graph
{
using Edge = Edge<W>;
private:
std::vector<V> _vertexSet; // 顶点的集合
std::map<V, int> _vertexIndex; // 顶点映射下标
std::vector<Edge*> _table; // 出度边表
public:
typedef Graph<V, W,Directed> Self;
Graph() = default;
int GetVertexIndex(const V& v)
{
typename std::map<V, int>::iterator pos = _vertexIndex.find(v);
if (pos != _vertexIndex.end())
{
return pos->second;
}
else
{
return -1;
}
}
bool AddVertex(const V& v)
{
if (GetVertexIndex(v) != -1)
return false;
_vertexSet.push_back(v);
_vertexIndex.insert(std::make_pair(v, _vertexSet.size() - 1));
_table.push_back(nullptr);
return true;
}
bool AddEdge(const V& src, const V& dst, const W& weight)
{
int srci = GetVertexIndex(src);
int dsti = GetVertexIndex(dst);
// 顶点不在图中,添加边失败
if (srci == -1 || dsti == -1)
return false;
Edge* edge = new Edge(dsti, weight);
// 头插
edge->_next = _table[srci];
_table[srci] = edge;
// 无向图
if (!Directed)
{
edge = new Edge(srci, weight);
edge->_next = _table[dsti];
_table[dsti] = edge;
}
return true;
}
bool _IsConnected(int srci)
{
int n = static_cast<int>(_vertexSet.size());
vector<bool> markbit(n, false);
queue<int> q;
q.push(srci);
markbit[srci] = true;
// 类似广度优先遍历的思想
while (!q.empty())
{
auto front = q.front();
q.pop();
Edge* curr = _table[front];
while (curr != nullptr)
{
if (!markbit[curr->_dsti])
{
q.push(curr->_dsti);
markbit[curr->_dsti] = true;
}
curr = curr->_next;
}
}
// 如果还有顶点没有遍历到,说明不是连通图
for (int i = 0; i < n; i++)
{
if (!markbit[i])
{
return false;
}
}
return true;
}
bool IsConnected()
{
if (_vertexSet.empty())
return true;
if (Directed) // 有向图的强连通性判断
{
int n = static_cast<int>(_vertexSet.size());
// 需要依次从所有顶点出发遍历
for (int i = 0; i < n; i++)
{
if (!_IsConnected(i))
{
return false;
}
}
return true;
}
else // 无向图的连通性判断
{
return _IsConnected(0);
}
}
// 获取两个相连顶点之间边的权值
const W& GetEdgeWeight(const V& src, const V& dst)
{
int srci = GetVertexIndex(src);
int dsti = GetVertexIndex(dst);
if (srci == -1 || dsti == -1)
{
return W(); // 可以选择抛异常
}
Edge* curr = _table[srci];
while (curr!=nullptr)
{
if (curr->_dsti == dsti)
{
return curr->_weight;
}
curr=curr->_next;
}
return W();
}
bool bfsSpanningTree(Self& spanningTree, const V& src)
{
int srci = GetVertexIndex(src);
// 遍历源点不存在,不能进行遍历
if (srci == -1)
{
return false;
}
// 只有图为无向图且是连通图才能选出生成树
if (Directed && !IsConnected())
return false;
// 将spanningTree拷贝成与当前图相同顶点的零图
spanningTree._vertexSet = _vertexSet;
spanningTree._vertexIndex = _vertexIndex;
spanningTree._table.resize(_vertexSet.size(), nullptr);
vector<bool> visited(_vertexSet.size(), false);
// pair.first是pair.second的上一层顶点下标
queue<pair<int, int>> q;
// 将遍历源点下标入队,并标记访问过
q.push({ -1,srci });
visited[srci] = true;
while (!q.empty())
{
pair<int, int> front = q.front();
q.pop();
int first = front.first; // 当前遍历顶点的上一层顶点
int second = front.second; // 当前遍历顶点
if (first != -1)
{
// 将选出的边添加到spanningTree中
std::cout << _vertexSet[first] << "<--->" << _vertexSet[second] << std::endl;
spanningTree.AddEdge(_vertexSet[first], _vertexSet[second],
GetEdgeWeight(_vertexSet[first], _vertexSet[second]));
}
Edge* curr = _table[second];
// 将与front.second相连的顶点且没有被访问的顶点入队
while (curr != nullptr)
{
if (!visited[curr->_dsti])
{
q.push({ second,curr->_dsti });
visited[curr->_dsti] = true;
}
curr = curr->_next;
}
}
return spanningTree.IsConnected();
}
void _dfsSpanningTree(Self& spanningTree, int srci, int& previ, vector<bool>& visited)
{
if (previ != -1)
{
// 将选出的边添加到spanningTree中
std::cout << _vertexSet[previ] << "<--->" << _vertexSet[srci] << std::endl;
spanningTree.AddEdge(_vertexSet[previ], _vertexSet[srci], GetEdgeWeight(_vertexSet[previ], _vertexSet[srci]));
}
// 标记访问过
visited[srci] = true;
// 更新前一个顶点的下标
previ = srci;
Edge* curr = _table[srci];
while (curr != nullptr)
{
if (!visited[curr->_dsti])
{
_dfsSpanningTree(spanningTree, curr->_dsti, previ, visited);
}
curr = curr->_next;
}
}
// spanningTree为输出型参数,src为遍历源点
bool dfsSpanningTree(Self& spanningTree, const V& src)
{
int srci = GetVertexIndex(src);
// 遍历源点不存在,不能进行遍历
if (srci == -1)
{
return false;
}
// 只有图为无向图且是连通图才能选出生成树
if (Directed && !IsConnected())
return false;
// 将spanningTree拷贝成与当前图相同顶点的零图
spanningTree._vertexSet = _vertexSet;
spanningTree._vertexIndex = _vertexIndex;
spanningTree._table.resize(_vertexSet.size(), nullptr);
vector<bool> visited(_vertexSet.size(), false);
int previ = -1; // 遍历访问的前一个顶点下标
_dfsSpanningTree(spanningTree, srci, previ, visited);
return spanningTree.IsConnected(); // 如果spanningTree是连通图则说明构建生成树成功
}
};
void TestList3()
{
AdjacentList::Graph<std::string, int> g;
g.AddVertex("A");
g.AddVertex("B");
g.AddVertex("C");
g.AddVertex("D");
g.AddVertex("E");
g.AddVertex("F");
g.AddVertex("G");
g.AddEdge("A", "B", 1);
g.AddEdge("A", "C", 1);
g.AddEdge("B", "D", 1);
g.AddEdge("B", "E", 1);
g.AddEdge("C", "E", 1);
g.AddEdge("C", "F", 1);
g.AddEdge("D", "G", 1);
g.AddEdge("E", "G", 1);
g.AddEdge("F", "G", 1);
AdjacentList::Graph<std::string, int> spanningTree;
//g.dfsSpanningTree(spanningTree, "A");
g.bfsSpanningTree(spanningTree, "A");
std::cout << spanningTree.IsConnected() << std::endl;
}
}
int main(int, char**, char**)
{
AdjacentList::TestList3();
//AdjacentMatrix::TestMatrix3();
return 0;
}