記事ディレクトリ
グラフ隣接行列
namespace Graph_Structure
{
//Vertex是代表顶点的数据类型,Weight是边的权值的数据类型,MAX_W是权值的上限值(表示不相两)
//Direction表示图是否为有向图
template<class Vertex, class Weight = int, Weight MAX_W = INT_MAX, bool Direction = false>
class Graph
{
typedef Graph<Vertex, Weight, MAX_W, Direction> Self;
public:
//使用编译器的默认构造函数
Graph() = default;
//给定一个存放顶点的数组用来初始化图
Graph(const Vertex* a, size_t n)
{
_vertexs.reserve(n);
_indexMap.rehash(n);
_matrix.resize(n, std::vector<Weight>(n, MAX_W));
for (size_t i = 0; i < n; ++i)
{
_vertexs.push_back(a[i]);
//建立顶点和数组下标的映射(目的是为了邻接矩阵的边存储)
_indexMap[a[i]] = i;
}
}
//获取顶点在邻接矩阵中对应的下标
size_t GetVertexIndex(const Vertex& vertex)
{
if (_indexMap.find(vertex) == _indexMap.end())
{
throw "invalued_para";
return -1;
}
return _indexMap[vertex];
}
void _AddEdge(size_t srci, size_t dsti, const Weight& w)
{
//判断是有向图还是无向图
if (Direction == false)
{
_matrix[dsti][srci] = w;
}
_matrix[srci][dsti] = w;
}
//给定起点和终点,在邻接矩阵中添加一条边
void AddEdge(const Vertex& src, const Vertex& dst, const Weight& w)
{
if (_indexMap.find(src) == _indexMap.end() || _indexMap.find(dst) == _indexMap.end())
{
throw "invalued_para";
}
size_t srci_index = GetVertexIndex(src);
size_t dst_index = GetVertexIndex(dst);
_AddEdge(srci_index, dst_index, w);
}
//将图的邻接矩阵打印出来
void Print()
{
for (auto e : _vertexs)
{
std::cout << e << '[' << _indexMap[e] << ']' << std::endl;
}
std::cout << " ";
for (int i = 0; i < _vertexs.size(); ++i)
{
std::cout << i << " ";
}
std::cout << std::endl;
int i = 0;
for (auto arry : _matrix)
{
std::cout << i++ << ' ';
for (auto e : arry)
{
if (e == MAX_W)
{
printf("%4c ", '*');
}
else
{
printf("%4d ", e);
}
}
std::cout << std::endl;
}
}
//图的广度优先遍历
void BFS(const Vertex& src)
{
size_t begin = GetVertexIndex(src);
std::queue<int> QNode;
std::vector<bool> Label(_vertexs.size(), false);
QNode.push(begin);
Label[begin] = true;
size_t Level = 0;
while (!QNode.empty())
{
size_t LevelSize = QNode.size();
for (size_t i = 0; i < LevelSize; ++i)
{
size_t front = QNode.front();
QNode.pop();
std::cout << _vertexs[front] << '[' << front << ']' << std::endl;
for (int j = 0; j < _vertexs.size(); ++j)
{
if (Label[j] == false && _matrix[front][j] != MAX_W)
{
QNode.push(j);
Label[j] = true;
}
}
}
}
}
//图的深度优先遍历
void DFS(const Vertex& src)
{
std::vector<bool> visited(_vertexs.size(), false);
_DFS(GetVertexIndex(src), visited);
}
private:
void _DFS(size_t srci, std::vector<bool>& visited)
{
visited[srci] = true;
std::cout << _vertexs[srci] << '[' << srci << ']' << std::endl;
for (int i = 0; i < _vertexs.size(); ++i)
{
if (_matrix[srci][i] != MAX_W && visited[i] == false)
{
_DFS(i, visited);
}
}
}
private:
std::vector<Vertex> _vertexs; // 顶点集合
std::unordered_map<Vertex, size_t> _indexMap; // 顶点映射下标
std::vector<std::vector<Weight>> _matrix; // 邻接矩阵
};
}
有向重み付きグラフ (負の重みを持つパス、しかし存在することはできません総重量が負のサイクル)、フロイド・ウォーシャルアルゴリズムが見つかります。任意の 2 つの頂点の間最短の道
1. フロイド・ウォーシャルアルゴリズムの考え方(動的計画法に基づく)
-
N
グラフに頂点があり、その頂点に次のように0~N-1
番号が付けられているとします。 -
アルゴリズムで使用される二次元配列
Dist
点間の最短経路長を記録するには、Dist[i][j]
i 番目の頂点から j 番目の頂点までの最短経路長を示します。Dist
配列の初期状態のためにグラフの隣接行列のコピー -
i
任意の 2 つの頂点j
間の最短パス上に0 ~ N-2
頂点が存在する場合があります。 -
頂点から
i
頂点へのj
最短パスは次のとおりであると仮定します。最も大きい番号の頂点はk
頂点、i
の間のパスk
はp1
、k
そしてj
の間のパスは です(これが頂点から頂点への最短パスであること、また頂点から頂点への最短パスであることp2
を証明することは難しくありません)p1
i
k
p2
k
j
-
したがって、状態遷移方程式:
Dist[i][j] = Dist[i][k] + Dist[k][j]
-
最短パスの合計は次のよう
p1
にするp2
こともできます。サブパスも同様に分割します. パスが分割できなくなるまでパスの分割を繰り返します。最小状態、それぞれから最小状態始める状態遷移を行うi
頂点から頂点までの最短パスを取得できますj
。 -
状態遷移懇願する任意の 2 点間の最短経路このプロセスは次のループを通じて完了できます。
//动态规划求最优解
for (int k = 0; k < _vertexs.size(); ++k)
{
for (int i = 0; i < _vertexs.size(); ++i)
{
for (int j = 0; j < _vertexs.size(); ++j)
{
if (Dist[i][k] != MAX_W && Dist[k][j] != MAX_W &&
Dist[i][k] + Dist[k][j] < Dist[i][j])
{
Dist[i][j] = Dist[i][k] + Dist[k][j];
}
}
}
}
- 他の 2 点間の最短経路を決定するプロセスも同様です。
2.フロイド・ウォーシャルアルゴリズムインターフェース
//多源最短路径算法(允许带负权路径存在)
//Dist数组用于记录顶点间的最短路径的长度
//ParentPath数组用于记录最短路径上某个顶点的前驱结点编号
void FloydWarShall(std::vector<std::vector<Weight>>& Dist, std::vector<std::vector<int>>& ParentPath)
{
Dist.resize(_vertexs.size(), std::vector<Weight>(_vertexs.size(), MAX_W));
ParentPath.resize(_vertexs.size(), std::vector<int>(_vertexs.size(), -1));
//根据图的邻接矩阵初始化Dist数组
for (int i = 0; i < _matrix.size(); ++i)
{
for (int j = 0; j < _matrix.size(); ++j)
{
if (i == j)
{
Dist[i][j] = 0;
}
else if(_matrix[i][j] != MAX_W)
{
Dist[i][j] = _matrix[i][j];
ParentPath[i][j] = i;
}
}
}
//动态规划求各个最短路径
for (int k = 0; k < _vertexs.size(); ++k)
{
for (int i = 0; i < _vertexs.size(); ++i)
{
for (int j = 0; j < _vertexs.size(); ++j)
{
if (Dist[i][k] != MAX_W && Dist[k][j] != MAX_W &&
Dist[i][k] + Dist[k][j] < Dist[i][j])
{
Dist[i][j] = Dist[i][k] + Dist[k][j];
//i到j最短路径上,j顶点的前驱为k到j最短路径上j的前驱
ParentPath[i][j] = ParentPath[k][j];
}
}
}
}
}
注付録: 単一ソースの最短パス – Bellman-Ford アルゴリズム
Bellman-Ford
アルゴリズムは次のとおりです。負のウェイト パスを含むグラフ内単一ソースからの最短パスの問題を解決するDist
記録には1 次元配列が使用されますソースポイントが他の頂点を指す最短パスの長さ:ソースポイントからノードまでの最短パスの長さをDist[i]
表します。i
- 1 次元配列
ParentPath
array は、最短パス上の特定の頂点の先行ノード番号を記録するために使用されます。これは、ParentPath[i]
最短パス上のi
ノードの先行ノードの番号を表します。
1. Bellman-Ford アルゴリズム インターフェイスのコア部分
for (int i = 0; i < _vertexs.size() - 1; ++i)
{
for (int j = 0; j < _vertexs.size(); ++j)
{
for (int k = 0; k < _vertexs.size(); ++k)
{
if (_matrix[j][k] != MAX_W && dist[j] != MAX_W &&
_matrix[j][k] + dist[j] < dist[k])
{
dist[k] = _matrix[j][k] + dist[j];
parentPath[k] = j;
}
}
}
- 上記のループを通過できることが証明できます可能な最短経路最短経路の場合、内部二重循環
N-1
最短パス上の少なくとも 1 つのエッジを記録できるため、すべての最短パスを横断するために最も外側のループを1 回 (N
グラフの頂点の数)実行するだけで済みます。 Bellman-Ford
アルゴリズムは、総重量が負のサイクル、存在する総重量が負のサイクルグラフでは最短経路問題は解けない
2.ベルマン・フォードアルゴリズムインターフェース
//带负权路径的单源最短路径算法
bool BellmanFord(const Vertex& src, std::vector<Weight>& dist, std::vector<int>& parentPath)
{
dist.resize(_vertexs.size(), MAX_W);
parentPath.resize(_vertexs.size(), -1);
int srci = GetVertexIndex(src);
dist[srci] = Weight();
bool flag = true;
for (int i = 0; i < _vertexs.size() - 1; ++i)
{
for (int j = 0; j < _vertexs.size(); ++j)
{
for (int k = 0; k < _vertexs.size(); ++k)
{
if (_matrix[j][k] != MAX_W && dist[j] != MAX_W &&
_matrix[j][k] + dist[j] < dist[k])
{
//经过j结点,更新源点到k结点的路径长度
dist[k] = _matrix[j][k] + dist[j];
parentPath[k] = j;
flag = false;
}
}
}
if (flag)
{
//路径不再发生更新,则说明所有最短路径都已经确定
return false;
}
flag = true;
}
//检验图中是否存在负权环路
//如果存在负权环路,则Dist数组会继续被更新
flag = false;
for (int j = 0; j < _vertexs.size(); ++j)
{
for (int k = 0; k < _vertexs.size(); ++k)
{
if (_matrix[j][k] != MAX_W && dist[j] != MAX_W &&
_matrix[j][k] + dist[j] < dist[k])
{
dist[k] = _matrix[j][k] + dist[j];
flag = true;
}
}
}
return flag;
}