1,运营商的挑战:
1,在下图标出的城市间架设一条通信线路;
2,要求:
1,任意两个城市间都能够通信;
2,将架设成本降至最低;
2,问题抽象:
1,如何在图中选择 n - 1 条边使得 n 个顶点间两两可达,并且这 n - 1 条边的权值之和最小?
3,最小(大)生成树:
1,仅使用图中的 n - 1 条边连接图中的 n 个顶点;
2,不能使用产生回路的边;
3,各边上的权值总和达到最小(大);
4,寻找最小生成树:
5,使用 prim 方法手工寻找最小生成树:
6,最小生成树算法步骤(prim):
1,选择某一顶点 v0 作为起始顶点,使得 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;否则,结束;
7,最小生成树算法的原材料:
1,如果 T 集合到 F 集合中同一个顶点的连接有多条,那么选取权值最小的连接;
8,最小生成树算法流程图:
9,注意事项:
1,最小生成树仅针对无向图有意义;
2,必须判断图对象是否能够看做无向图;
1,如果可以才能够用 prim 算法;
10,有向图可看做无向图的充分条件:
1,有向的任意两顶点之间若存在连接,则两顶点相互可达、权值相等;
11,图类型(Graph)中的新增成员函数:
1,virtual bool isAdjacent(int i, int j) = 0;
1,判断在当前图中顶点 i 到顶点 j 是否邻接;
2,bool asUndirected();
1,判断当前的有向图是否能够看做无向图;
12,最小生成树 prim 算法实现:
1,判断邻接的实现:
1,邻接矩阵的实现:
1 /* 判断 i 到 j 顶点边是否连接,值不为空就连接 */ 2 bool isAdjacent(int i, int j) 3 { 4 return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_edges[i][j] != NULL); 5 }
2,邻接链表的实现:
1 bool isAdjacent(int i, int j) 2 { 3 return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_list.get(i)->edge.find(Edge<E>(i, j)) >= 0); 4 }
2,判断是否为无向图的实现:
1 /* 最小生成树只能用于无向图,而我们针对的是有向图,所以要判断有向图什么时候能够被当做无向图 */ 2 bool asUndirected() 3 { 4 bool ret = true; 5 6 for(int i=0; i<vCount(); i++) 7 { 8 for(int j=0; j<vCount(); j++) 9 { 10 /* i 到 j 是连接的,并且 j 到 i 也是连接的,然后权值也要相等 */ 11 if( isAdjacent(i, j) ) 12 { 13 ret = ret && isAdjacent(j, i) && (getEdge(i, j) == getEdge(j, i)); 14 } 15 } 16 } 17 18 return ret; 19 }
3,Prim算法实现:
1 /* Prim 法实现最小、大生成树;返回值是数组,因为最小、大生成树的结果就是一系列的边,所以返回边的数组;参数表示理论上的最大权值; */ 2 SharedPointer< Array< Edge<E> > > prim(const E& LIMIT, const bool MINIMUM = true) // 返回一个指向存储边的数组指针 3 { 4 LinkQueue< Edge<E> > ret; // 返回边队列,本质是 E 集合 5 6 /* 执行 prim 算法 */ 7 if( asUndirected() ) // 无向图 8 { 9 DynamicArray<int> adjVex(vCount());//保存最小权值的边的 F集合中顶点 10 DynamicArray<bool> mark(vCount()); // 保存 T 集合或者 F 集合的标记 11 DynamicArray<E> cost(vCount()); // 保存最小权值的顶点中 E 集合中顶点,寻找最小值要配合 mark 使用 12 SharedPointer< Array<int> > aj = NULL; // 保存某个顶点邻接数组 13 bool end = false; // 用于标记判断 prim 是否要中断执行 14 int v = 0; // 代表习惯性的从 0 顶点生成最小生成树 15 16 /* 执行初始化 */ 17 for(int i=0; i<vCount(); i++) 18 { 19 adjVex[i] = -1; // 没有边被访问 20 mark[i] = false; // 顶点都没有被访问 21 cost[i] = LIMIT; // 参数传递理论上的最大权值 22 } 23 24 mark[v] = true; // 初始顶点做标记 25 26 aj = getAdgacent(v); // 获取初始顶点的邻接顶点 27 28 /* 设置初始顶点对应的位置 */ 29 for(int j=0; j<aj->length(); j++) 30 { 31 cost[(*aj)[j]] = getEdge(v, (*aj)[j]); // 保存/到对应顶点的响应权值 32 adjVex[(*aj)[j]] = v; // 记录权值所对应的顶点,即能够得到边 33 } 34 35 /* 真正循环找边 */ 36 for(int i=0; (i<vCount()) && !end; i++) // 最多循环顶点次;也可能条件不满足,提前结束,所以有 !end 37 { 38 E m = LIMIT; 39 int k = -1; // 记录最小值的顶点 40 41 /* 通过 cost 数组找最小值 */ 42 for(int j=0; j<vCount(); j++) 43 { 44 if( !mark[j] && (MINIMUM ? (cost[j] < m) : (cost[j] > m)) ) // !makr[j] 条件是因为选取最小权值时本质上是选取连接的最小边,对应的顶点是在 F集合,此时 mark 中对应的值为假当中的,则在 mark 数组中对应的就为假,所以要这个条件,这里有最小值最大值的设置 45 { 46 m = cost[j]; 47 k = j; // 得到记录的最小值的顶点号 48 } 49 } 50 end = (k == -1); // 是否找到合法最小权值,因为有可能在上面 if 条件中没有找到合法的最小权值 51 if( !end ) 52 { 53 ret.add(Edge<E>(adjVex[k], k, getEdge(adjVex[k], k))); // 在 adjVex 中找到这条边 54 55 mark[k] = true; // 标记顶点进入了 T 集合 56 57 aj = getAdgacent(k); // 找新的集合连接 58 59 /* 找到之后更新 cost 数组和 adgVex 数组 */ 60 for(int j=0; j<aj->length(); j++) 61 { 62 if( !mark[(*aj)[j]] && (MINIMUM ? (getEdge(k, (*aj)[j]) < cost[(*aj)[j]]) : (getEdge(k, (*aj)[j]) > cost[(*aj)[j]])) ); //只对 F 集合操作 63 { 64 cost[(*aj)[j]] = getEdge(k ,(*aj)[j]); //如果 T 到 F 集合新连接权值较小,则记录到 cost 数组中,新加入的点 k 和之前 T 集合里的点到 F 集合里的点的权值要比较呢;如果在 k 到 F 集合中找不到合适的点,则用T中的点代替 65 adjVex[(*aj)[j]] = k; // 将最小权值的起始点设入到邻接边中 66 } 67 } 68 } 69 } 70 } 71 else 72 { 73 THROW_EXCEPTION(InvalidOperationException, "Prim operation is for undirected graph only ..."); 74 } 75 76 /* 判断边的数目是否够,即 n-1 条边 */ 77 if( ret.length() != (vCount() - 1) ) 78 { 79 THROW_EXCEPTION(InvalidOperationException, "No enough edge for prim operation ..."); 80 } 81 return toArray(ret); // 返回值是边的数组 82 }
14,prim 算法测试代码:
1 #include <iostream> 2 #include "MatrixGraph.h" 3 #include "ListGraph.h" 4 5 using namespace std; 6 using namespace DTLib; 7 8 template< typename V, typename E > 9 Graph<V, E>& GraphEasy() 10 { 11 static MatrixGraph<4, V, E> g; 12 13 g.setEdge(0, 1, 1); 14 g.setEdge(1, 0, 1); 15 g.setEdge(0, 2, 3); 16 g.setEdge(2, 0, 3); 17 g.setEdge(1, 2, 1); 18 g.setEdge(2, 1, 1); 19 g.setEdge(1, 3, 4); 20 g.setEdge(3, 1, 4); 21 g.setEdge(2, 3, 1); 22 g.setEdge(3, 2, 1); 23 24 return g; 25 } 26 27 template< typename V, typename E > 28 Graph<V, E>& GraphComplex() 29 { 30 static ListGraph<V, E> g(9); 31 32 g.setEdge(0, 1, 10); 33 g.setEdge(1, 0, 10); 34 g.setEdge(0, 5, 11); 35 g.setEdge(5, 0, 11); 36 g.setEdge(1, 2, 18); 37 g.setEdge(2, 1, 18); 38 g.setEdge(1, 8, 12); 39 g.setEdge(8, 1, 12); 40 g.setEdge(1, 6, 16); 41 g.setEdge(6, 1, 16); 42 g.setEdge(2, 3, 22); 43 g.setEdge(3, 2, 22); 44 g.setEdge(2, 8, 8); 45 g.setEdge(8, 2, 8); 46 g.setEdge(3, 8, 21); 47 g.setEdge(8, 3, 21); 48 g.setEdge(3, 6, 24); 49 g.setEdge(6, 3, 24); 50 g.setEdge(3, 7, 16); 51 g.setEdge(7, 3, 16); 52 g.setEdge(3, 4, 20); 53 g.setEdge(4, 3, 20); 54 g.setEdge(4, 5, 26); 55 g.setEdge(5, 4, 26); 56 g.setEdge(4, 7, 7); 57 g.setEdge(7, 4, 7); 58 g.setEdge(5, 6, 17); 59 g.setEdge(6, 5, 17); 60 g.setEdge(6, 7, 19); 61 g.setEdge(7, 6, 19); 62 63 return g; 64 } 65 66 int main() 67 { 68 Graph<int, int>& g = GraphEasy<int, int>(); 69 SharedPointer< Array< Edge<int> > > sa = g.prim(65535); 70 71 int w = 0; 72 73 for(int i=0; i<sa->length(); i++) 74 { 75 w += (*sa)[i].data; 76 77 cout << (*sa)[i].b << " " << (*sa)[i].e << " " << (*sa)[i].data << endl; 78 } 79 80 cout << "Weight: " << w << endl; 81 82 return 0; 83 }
15,小结:
1,最小生成树使得顶点间的连通代价最小;
2,Prim 算法通过顶点的动态标记寻找最小生成树;
3,Prim 算法的关键是集合概念的运用(T 集合,F 集合);
4,利用 Prim 算法的思想也能寻找图的“最大生成树”;