参考:最小生成树(Kruskal(克鲁斯卡尔)和Prim(普里姆))算法动画演示
运营商的挑战
- 在下图标出的城市间架设一条通信线路
要求:
- 任意两个城市间都能够通信
- 将假设成本降至最低
问题:如何在图中选择 n-1 条边使得 n 个顶点间两两可达,并且这 n-1 条边的权值之和最小?
最小生成树
- 仅使用图中的 n-1 条边连接图中的 n 个顶点
- 不能使用产生回路的边
- 各边上的权值的总和达到最小
寻找最小生成树
示例分析:手工寻找最小生成树
最小生成树算法步骤(Prim)
- 选择某一顶点 v
0
作为起始顶点,使得 T = {v0
}, F={v1
,v2
,...,vn
}, E={}- 每次选择一条边,这条边是所有 (u, v) 中权值最小的边,且 u ∈ T, v ∈ F
- 修改 T, F, E :
T = T + {v}, F = F - {v}, E = E + {(u,v)}
- 当 F != NULL 时,且 (u, v) 存在,转2; 否则结束
最小生成树的原材料
类型 | 变量 | 用途 |
Array<bool> | mark | 标记顶点所属的集合 |
Array[E] | cost | 记录T集合到F集合中顶点的最小权值 |
Array<int> | adjVex | 记录 cost 中权值的对应顶点 |
Queue<Edge> | ret | 记录最小生成树中的边 |
注: 如何 T 集合到 F 集合中同一个顶点的连接有多条边,那么选择权值最小的连接
步骤描述:
注意事项
- 最小生成树仅针对无向图有意义
- 必须判断图对象是否能够看作无向图
问题:什么样的有向图能够看作无向图
有向图的任意两顶点若存在连接,则两点需互相可达且权值相等
图类型 (Graph) 中新增成员函数
virtual bool isAdjacent(int i, int j) = 0;
- 判断在当前图中顶点 i 到顶点 j 是否邻接
bool asUndirected();
- 判断当前的有向图是否能够看作无向图
// 邻接矩阵图结构
bool isAdjacent(int i, int j) const override
{
return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_edges[i][j] != nullptr);
}
// 邻接链表图结构
bool isAdjacent(int i, int j) const override
{
return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_list.get(i)->edge.find(Edge<E>(i, j)) >=0);
}
bool asUndirected()
{
bool ret = true;
for (int i=0; i<vCount() && ret; ++i)
{
for (int j=0; j<vCount() && ret; ++j)
{
if (isAdjacent(i, j))
{
ret = isAdjacent(j, i) && (getEdge(i, j) == getEdge(j, i));
}
}
}
return ret;
}
编程实验:最小生成树算法
文件:Graph.h
#ifndef GRAPH_H
#define GRAPH_H
#include "Object.h"
#include "SharedPointer.h"
#include "DynamicArray.h"
#include "LinkQueue.h"
#include "LinkStack.h"
namespace DTLib
{
template <typename E>
struct Edge : public Object
{
int b;
int e;
E data;
Edge(int i=-1, int j=-1) : b(i), e(j)
{
}
Edge(int i, int j, const E &value) : b(i), e(j), data(value)
{
}
bool operator == (const Edge &obj)
{
return (b == obj.b) && (e == obj.e);
}
bool operator != (const Edge &obj)
{
return !(*this == obj);
}
};
template <typename V, typename E>
class Graph : public Object
{
public:
virtual V getVertex(int i) const = 0;
virtual bool getVertex(int i, V &value) const = 0;
virtual bool setVertex(int i, const V &value) = 0;
virtual SharedPointer<Array<int>> getAdjacent(int i) const = 0;
virtual bool isAdjacent(int i, int j) const = 0;
virtual E getEdge(int i, int j) const = 0;
virtual bool getEdge(int i, int j, E &value) const = 0;
virtual bool setEdge(int i, int j, const E &value) = 0;
virtual bool removeEdge(int i, int j) = 0;
virtual int vCount() const = 0;
virtual int eCount() = 0;
virtual int OD(int i) = 0;
virtual int ID(int i) = 0;
virtual int TD(int i)
{
return OD(i) + ID(i);
}
bool asUndirected()
{
bool ret = true;
for (int i=0; i<vCount() && ret; ++i)
{
for (int j=0; j<vCount() && ret; ++j)
{
if (isAdjacent(i, j))
{
ret = isAdjacent(j, i) && (getEdge(i, j) == getEdge(j, i));
}
}
}
return ret;
}
SharedPointer<Array<int>> BFS(int i)
{
DynamicArray<int> *ret = nullptr;
if ((0 <= i) && (i < vCount()))
{
LinkQueue<int> q;
LinkQueue<int> r;
DynamicArray<bool> visited(vCount());
for (int j=0; j<visited.length(); ++j)
{
visited[j] = false;
}
q.add(i);
while (q.length() > 0)
{
int v = q.front();
q.remove();
if (!visited[v])
{
SharedPointer<Array<int>> aj = getAdjacent(v);
for (int j=0; j<aj->length(); ++j)
{
q.add((*aj)[j]);
}
r.add(v);
visited[v] = true;
}
}
ret = toArray(r);
}
else
{
THROW_EXCEPTION(InvalidParameterExcetion, "Parameter i is invalid ...");
}
return ret;
}
#ifdef DFS_R
SharedPointer<Array<int>> DFS(int i) // 递归版深度优先遍历
{
DynamicArray<int> *ret = nullptr;
if ((0 <= i) && (i < vCount()))
{
LinkQueue<int> r;
DynamicArray<bool> visited(vCount());
for (int j=0; j<vCount(); ++j)
{
visited[j] = false;
}
DFP(i, visited, r);
ret = toArray(r);
}
else
{
THROW_EXCEPTION(InvalidParameterExcetion, "Parameter i is invalid ...");
}
return ret;
}
#else
SharedPointer<Array<int>> DFS(int i)
{
DynamicArray<int> *ret = nullptr;
if ((0 <= i) && (i < vCount()))
{
LinkStack<int> s;
LinkQueue<int> r;
DynamicArray<bool> visited(vCount());
for (int j=0; j<visited.length(); ++j)
{
visited[j] = false;
}
s.push(i);
while (s.size() > 0)
{
int v = s.top();
s.pop();
if (!visited[v])
{
SharedPointer<Array<int>> aj = getAdjacent(v);
for (int j=aj->length()-1; j>=0; --j)
{
s.push((*aj)[j]);
}
r.add(v);
visited[v] = true;
}
}
ret = toArray(r);
}
else
{
THROW_EXCEPTION(InvalidParameterExcetion, "Parameter i is invalid ...");
}
return ret;
}
#endif
SharedPointer<Array<Edge<E>>> prim(const E &LIMIT, bool MINIMUM = true)
{
LinkQueue<Edge<E>> ret;
if (asUndirected())
{
DynamicArray<int> adjVex(vCount());
DynamicArray<bool> mark(vCount());
DynamicArray<E> cost(vCount());
SharedPointer<Array<int>> aj = nullptr;
bool end = false;
int v = 0;
for (int i=0; i<vCount(); ++i)
{
adjVex[i] = -1;
mark[i] = false;
cost[i] = LIMIT;
}
mark[v] = true;
aj = getAdjacent(v);
for (int i=0; i<aj->length(); ++i)
{
cost[(*aj)[i]] = getEdge(v, (*aj)[i]);
adjVex[(*aj)[i]] = v;
}
for (int i=0; i<vCount() && !end; ++i)
{
E m = LIMIT;
int k = -1;
for (int j=0; j<vCount(); ++j)
{
if (!mark[j] && (MINIMUM ? (m > cost[j]) : (m < cost[j])))
{
m = cost[j];
k = j;
}
}
end = (k == -1);
if (!end)
{
ret.add(Edge<E>(adjVex[k],k, getEdge(adjVex[k],k)));
mark[k] = true;
aj = getAdjacent(k);
for (int j=0; j<aj->length(); ++j)
{
if (!mark[(*aj)[j]] && (MINIMUM ? (getEdge(k, (*aj)[j]) < cost[(*aj)[j]]) : (getEdge(k, (*aj)[j]) > cost[(*aj)[j]])))
{
cost[(*aj)[j]] = getEdge(k, (*aj)[j]);
adjVex[(*aj)[j]] = k;
}
}
}
}
}
else
{
THROW_EXCEPTION(InvalidOpertionExcetion, "Prim operator is for undirected grap only ...");
}
if (ret.length() != (vCount() - 1))
{
THROW_EXCEPTION(InvalidOpertionExcetion, "No enough edge for prim operation ...");
}
return toArray(ret);
}
protected:
template <typename T>
DynamicArray<T>* toArray(LinkQueue<T> &queue)
{
DynamicArray<T> *ret = new DynamicArray<T>(queue.length());
if (ret != nullptr)
{
for (int i=0; i<ret->length(); ++i, queue.remove())
{
ret->set(i, queue.front());
}
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create ret obj ...");
}
return ret;
}
#ifdef DFS_R
void DFP(int i, DynamicArray<bool> &visited, LinkQueue<int>& queue)
{
if (!visited[i])
{
queue.add(i);
visited[i] = true;
SharedPointer<Array<int>> aj = getAdjacent(i);
for (int j=0; j<aj->length(); ++j)
{
DFP((*aj)[j], visited, queue);
}
}
}
#endif
};
}
#endif // GRAPH_H
文件:main.cpp
#include <iostream>
#include "MatrixGraph.h"
#include "ListGraph.h"
using namespace std;
using namespace DTLib;
template< typename V, typename E >
Graph<V, E>& GraphEasy()
{
static MatrixGraph<4, V, E> g;
g.setEdge(0, 1, 1);
g.setEdge(1, 0, 1);
g.setEdge(0, 2, 3);
g.setEdge(2, 0, 3);
g.setEdge(1, 2, 1);
g.setEdge(2, 1, 1);
g.setEdge(1, 3, 4);
g.setEdge(3, 1, 4);
g.setEdge(2, 3, 1);
g.setEdge(3, 2, 1);
return g;
}
template< typename V, typename E >
Graph<V, E>& GraphComplex()
{
static ListGraph<V, E> g(9);
g.setEdge(0, 1, 10);
g.setEdge(1, 0, 10);
g.setEdge(0, 5, 11);
g.setEdge(5, 0, 11);
g.setEdge(1, 2, 18);
g.setEdge(2, 1, 18);
g.setEdge(1, 8, 12);
g.setEdge(8, 1, 12);
g.setEdge(1, 6, 16);
g.setEdge(6, 1, 16);
g.setEdge(2, 3, 22);
g.setEdge(3, 2, 22);
g.setEdge(2, 8, 8);
g.setEdge(8, 2, 8);
g.setEdge(3, 8, 21);
g.setEdge(8, 3, 21);
g.setEdge(3, 6, 24);
g.setEdge(6, 3, 24);
g.setEdge(3, 7, 16);
g.setEdge(7, 3, 16);
g.setEdge(3, 4, 20);
g.setEdge(4, 3, 20);
g.setEdge(4, 5, 26);
g.setEdge(5, 4, 26);
g.setEdge(4, 7, 7);
g.setEdge(7, 4, 7);
g.setEdge(5, 6, 17);
g.setEdge(6, 5, 17);
g.setEdge(6, 7, 19);
g.setEdge(7, 6, 19);
return g;
}
void func1()
{
cout << "func1: ---------------------" << endl;
Graph<int, int>& g = GraphEasy<int, int>();
SharedPointer< Array< Edge<int> > > sa = g.prim(65535);
int w = 0;
for(int i=0; i<sa->length(); i++)
{
w += (*sa)[i].data;
cout << (*sa)[i].b << " " << (*sa)[i].e << " " << (*sa)[i].data << endl;
}
cout << "Weight: " << w << endl;
}
void func2()
{
cout << "func2: ---------------------" << endl;
Graph<int, int>& g = GraphComplex<int, int>();
SharedPointer< Array< Edge<int> > > sa = g.prim(65535);
int w = 0;
for(int i=0; i<sa->length(); i++)
{
w += (*sa)[i].data;
cout << (*sa)[i].b << " " << (*sa)[i].e << " " << (*sa)[i].data << endl;
}
cout << "Weight: " << w << endl;
}
void func3()
{
cout << "func3: ---------------------" << endl;
Graph<int, int>& g = GraphComplex<int, int>();
SharedPointer< Array< Edge<int> > > sa = g.prim(0, false);
int w = 0;
for(int i=0; i<sa->length(); i++)
{
w += (*sa)[i].data;
cout << (*sa)[i].b << " " << (*sa)[i].e << " " << (*sa)[i].data << endl;
}
cout << "Weight: " << w << endl;
}
int main()
{
func1();
func2();
func3();
return 0;
}
输出:
func1: ---------------------
0 1 1
1 2 1
2 3 1
Weight: 3
func2: ---------------------
0 1 10
0 5 11
1 8 12
8 2 8
1 6 16
6 7 19
7 4 7
7 3 16
Weight: 99
func3: ---------------------
0 5 11
5 4 26
4 3 20
3 6 24
3 2 22
3 8 21
6 7 19
2 1 18
Weight: 161
小结
- 最小生成树使得顶点间的连通代价最小
- Prim 算法通过顶点的动态标记寻找最小生成树
- Prim 算法的关键是集合概念的运用 (T集合, F集合)
- 利用 Prim 算法的思想也能寻找图的 "最大生成树"
以上内容整理于狄泰软件学院系列课程,请大家保护原创!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。