1、最小生成树(Prim)
运营商的挑战
-在下图标出的城市间架设一条通信线路
要求:
任意两个城市间都能够通信
将架设成本降至最低
解决方案
问题
如何在图中选择n-1条边使得n个顶点间两两可达,
并且这n-1条边的权值之和最小?
最小生成树
-仅使用图中的n-1条边连接图中的n个顶点
-不能使用产生回路的边
-各边上的权值的总和达到最小 寻找最小生成树
最小生成树算法步骤(Prim)
1. 选择某一顶点v。作为起始顶点,使得T = { v0 }, F = { v1, v2, … vn }, E = { }
2. 每次选择一条边,这条边是所有(u, v)中权值最小的边,且u ∈ T, v ∈ F
3. 修改T, F, E:
T = T + { v }, F = F - { v }, E = E + { (u, v) }
4. 当 F ! = NULL时,且 (u, v)存在,转2; 否则,结束 最小生成树算法的原材料
如果T集合到F集合中同一个顶点的连
接有多条,那么选取权值最小的连接。
注意事项
-最小生成树仅针对无向图有意义
-必须判断图对象是否能够看做无向图
问题:
什么样的有向图能够看做无向图?
图类型(Graph)中的新增成员函数
- virtual bool isAdjacent(int i, int j) = 0;
判断在当前图中顶点i到顶点j是否邻接
- bool asUndirected() ;
判断当前的有向图是否能够看做无向图2、编程实验
最小生成树算法 PrimGraph.h
新增public成员
virtual bool isAdjacent(int i, int j) = 0; //判断i,j是否有连接
ListGraph.h
新增isAdjacent的实现
bool isAdjacent(int i, int j)
{
return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_list.get(i)->edge.find(Edge<E>(i, j)) >= 0);
}
Matrixgrap.h
新增isAdjacent的实现
bool isAdjacent(int i, int j)
{
return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_edges[i][j] != NULL);
}
Graph.h
新增public成员
bool asUndirected()
{
bool ret = true;
for(int i=0;i<vCount();i++)
{
for(int j=0;j<vCount();j++)
{
if( isAdjacent(i, j) )
{
ret = ret && isAdjacent(j, i) && (getEdge(i, j) == getEdge(j, i));
}
}
}
return ret;
}
Graph新增prim算法
SharedPointer< Array< Edge<E> > > prim(const E& LIMIT, const bool MINIMUM=true) //返回一个智能指针,指向一个边的数组,
{
LinkQueue< Edge<E> > ret; //E集合
if( asUndirected() ) //可以看作无向图
{
DynamicArray<int> adjVex(vCount()); //记录cost中权值的对应顶点
DynamicArray<bool> mark(vCount()); //标记顶点所属集合T,F
DynamicArray<E> cost(vCount()); //T集合到F集合最小权值
SharedPointer< Array<int> > aj = NULL; //保存某个顶点邻接顶点数组
bool end = false; //标记判断prim是否中断执行
int v = 0; //习惯从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 j=0; j<aj->length(); j++)
{
cost[(*aj)[j]] = getEdge(v, (*aj)[j]); //获取v到邻接所有顶点的权值 ,保存到cost数组
adjVex[(*aj)[j]] = v; //记录边
}
for(int i=0; i<vCount() && !end; i++)
{
E m = LIMIT;
int k = -1; //记录相应最小值的顶点
for(int j=0; j<vCount(); j++) //遍历cost数组找最小权值
{
if( !mark[j] && (MINIMUM ? (cost[j] < m) : (cost[j] > m))) //本质获取连接的最小边,对应的顶点在F集合
{
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]]) ) )//T集合到F集合新连接权值较小,记录到cost数组
{
cost[(*aj)[j]] = getEdge(k, (*aj)[j]);
adjVex[(*aj)[j]] = k;
}
}
}
}
}
else
{
THROW_EXCEPTION(InvalidOperationException,"Prim operation is for undirected graph only ...");
}
if( ret.length() != (vCount() - 1) )
{
THROW_EXCEPTION(InvalidOperationException,"No enough edge for prim operation ...");
}
return toArray(ret);
}
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;
}
int main()
{
Graph<int, int>& g = GraphEasy<int, int>();
SharedPointer< Array< Edge<int> > > sa = g.prim(65535);
int w = 0;
cout<<"最小生成树"<<endl;
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;
cout<<endl<<endl;
cout<<"最大生成树"<<endl;
Graph<int, int>& gc = GraphComplex<int, int>();
SharedPointer< Array< Edge<int> > > sp = gc.prim(0, false);
int ww = 0;
for(int i=0; i<sp->length(); i++)
{
ww += (*sp)[i].data;
cout << (*sp)[i].b << " " << (*sp)[i].e << " " << (*sp)[i].data << endl;
}
cout << "Weight: " << ww << endl;
return 0;
}
3、小结
最小生成树使得顶点间的连通代价最小
Prim算法通过顶点的动态标记寻找最小生成树
Prim算法的关键是集合概念的运用(T集合,F集合)
利用 Prim算法的思想也能寻找图的“ 最大生成树"