FIG minimum spanning tree minimum spanning tree of FIG.

Minimum Spanning Trees

Minimum Spanning Trees

For a map, we have a theorem: n points connected to the n-1 sides, the pattern may be formed only tree. We can interpret it this way: Each node of the tree has a unique father, that is, there are at least n edges, but except for the root node, so that n-1 edges. There is an understanding: tree ring not present, then it is necessary to connect the n points can not form a ring, with only the n-1 sides.

So, for a weighted graph n points, spanning tree which is to use the n-1 sides of which are connected to the n points, then the minimum spanning tree is the right side of the n-1 sides and a minimum of kinds of programs, simple to understand, this diagram is used so that only the n-1 sides, while minimizing the sum of the edge weights which the n-1 sides. As shown below:

To this end red edge that is the minimum spanning tree diagram, note that we currently only discuss the minimum spanning tree directed graph.


Minimum spanning tree process, we can be understood as to build a tree. To the right side of the minimum sum, we can think of can be thought of greed: Let the minimum spanning tree where each side are as small as possible, so we have two ideas, corresponding to the two algorithms:

(1)Kruskal:

We can easily think of one kind of greedy strategy: the right side of each edge are small, and that the sum of the edge weights of the edges connected must also be small. So, we can expect a minimum of pick and choose, and then choose the next smallest, third smallest ...... until we pick n-1 edges. Therefore, we can follow these side edge of the right sort, and then began to pick sides. Why choose the side of it? The smaller the better side do not, why choose?

Small edge weight is good, but do not forget we have a premise: we want to build a tree, it can not exist inside the ring. In other words, if I see an edge, but this edge attached to two points I built tree has been connected, and that this edge also need to add in? Obviously not. Say this may be a bit incomprehensible, we take a look at a map to specific operations:

After ordering our side order of small to large (right side of the node junction points):

①1 2 1②1 3 1③4 6 3④5 6 4⑤2 3 6⑥4 5 7⑦3 4 9⑧2 4 11⑨3 5 13

To build a minimum spanning tree begins to grow this:

our operation is to the side of the tree Riga, its operation as shown below:

So far, everything is going well. We turn to the fifth side 236 a. We found that 2 and 3 has been connected, as by a relay, we can not at this time able to 236 was added to this edge of our tree. Similarly, a lower edge of the same number ⑥ also skipped because the 4 and 5 has been connected. Next, we added this edge 349, as shown in FIG:

We found that we built in each node of the tree have been connected, with just five sides, that is, we say the n-1 sides The algorithm ends.

Implementation of the algorithm is not difficult, the most difficult is how to determine whether the two connected. We can search deep or wide search to solve, but it is clear that efficiency is very low, therefore, we need the help of a powerful data structure: disjoint-set. And the investigation of the most powerful feature set is the ability to quickly determine whether two elements (the ancestors are the same) in the same collection, so we use it to determine whether two points are connected.

The main code:

struct edge
{
    int u,v,w;
}a[100001]; //边集数组 int boss[10001];//并查集,boss[i]表示i的祖先 int find(int x) { if(boss[x]==x)return x;//找到祖先 else { boss[x]=find(boss[x]);//路径压缩 return boss[x]; } } void Kurscal() { int i; for(i=1;i<=n;i++)boss[i]=i;//初始化 //n个结点,每个结点的祖先默认为它自己,也就是每个结点自己一个集合 stable_sort(a+1,a+1+m,cmp);//m条边,将边按照边权从小到大排序 int cnt=0;//当前最小生成树里边的数量 int len=0;//当前最小生成树边权总和 for(i=1;i<=m;i++) {); int x=find(a[i].u),y=find(a[i].v //x表示a[i].u的祖先,y表示a[i].v的祖先 if(x!=y) //说明两点不在同一集合内,即这两点不连通 { boss[x]=y;//标记祖先 cnt++;//边数增加 len+=a[i].w;//边权和增加 } if(cnt==n-1)break; //如果已经选了n-1条边,那最小生成树就建好了 } }

(2)prim:

In addition to achievements by adding side, we have no other way out? Kurscal, we added edge, is done in the case of our fixed-taking point. That is, at this time we do not care about these nodes, we only care about their connection sides. Well, we can not care about it at these nodes (Rain descends)? At this time, our achievements in the process is not added edge, but to add points. That beginning, our tree should be a long way (a little embarrassed):

We started should first find a root, the root node can be any node, since the last communication with each node will each two of which as a root it does not matter. So, every time we have to choose a point to join the minimum spanning tree, what conditions must be met at this point it? It must be the distance from the current node to its nearest distance from the tree, each node of the tree from their respective distance from the nearest node in the minimum, in fact, each of said secondary is found a distance "Smallest tree "nearest node. We are still to emulate the example above:

First, point 1 is the root, this time from the tree (point 1) is the most recent 2 o'clock, we will join No. 2:00 tree:

Next, from the nearest tree node is 3 points, we will be adding 3 points tree:

Next Steps reader to manually simulation, simulation algorithm only manually immersive feelings to the true meaning of its ideas (Digress

加入点的顺序应该是:1->2->3->4->5->6。注意我们每次选点的时候要选的是树以外的结点,否则一开始就会出现一种非常尴尬的局面:第一轮,找结点,发现根结点到根结点距离为0,选择根结点;第二轮,发现根结点到根结点距离为0,又选根结点......这就陷入了死循环。对于已经在树里的结点,我们是没有必要再去动它了,因为我们的目的就是将所有点插入到树里,那你已经在树里的我还管你干什么?所以我们需要有一个数组来记录结点是否已经在树里。

在选点的过程中,我们需要按照上面说的一大串话那样,比较树外的每一个结点到树上的每一个结点的距离吗?我们在那下面说了:“实际上说的就是每一次要找到一个距离“最小生成树”最近的结点。”我们有没有办法来记录每一个结点到最小生成树的距离呢?当然有!我们可以开一个数组dis,有没有发现,和我们的最短路算法中的Dijkstra算法有异曲同工之妙?不懂得可以看一下最短路算法分析。这两种算法都是不断加入点进行拓展,从而得出整张图的最短路或最小生成树。Dijkstra中dis表示的是结点到源点的距离,这里就是把源点扩大成了一棵树,其思想并没有任何改变,我们仍旧可以把那棵树当作一个点来看待。那么,在加入点后,我们需要用这个点来刷新一下其它非树结点(不在树上的结点)到树的距离,这和Dijkstra的松弛是一模一样的!令人赞叹的是,这两种算法解决的问题不同,它们的过程竟然完全一样!

主要代码:

struct edge
{
    int last,to,len;
}a[100001]; int first[10001],len=0; //邻接表 bool f[10001];//记录是否在树上 int dis[10001];//记录结点到树的距离 void add......//存边 void prim() { int i; for(i=1;i<=n;i++)dis[i]=999999;//初始化 int cnt=0;//树内点的数量 int sum=0;//树内边权总和 dis[1]=0; f[1]=1; cnt=1; //先确定根结点,一般以1作为根结点 while(cnt<n)//直到n个结点均在树上 { int id,minn=1000001; //id记录找到的结点的编号,minn是它到树的距离 for(i=1;i<=n;i++) if(f[i]==0&&dis[i]<minn) { id=i; minn=dis[i]; } f[id]=1; cnt++; //将这个点加入树 sum+=dis[id]; //刷新边权总和 for(i=first[id];i;i=a[i].last) //刷新结点到树的距离 if(f[a[i].to]==0&&a[i].len<dis[a[i].to]) dis[a[i].to]=a[i].len; } }

总结一下两种算法:Kurscal算法是将森林里的树逐渐合并,prim算法是在根结点的基础上建起一棵树。

可能有的同学会误解:dis代表结点到树的距离,那这个距离一定只包含一条边吗?在这里,距离只能有一条边。为什么呢?我们每一次是要往树里加一个点的,那如果这个距离经过了不止一条边,那就不满足我们的需求了。这一点要和Dijkstra区别开,Dijkstra是单纯的距离,而prim是只经过一条边的距离。这样的话,即使在存在负边权,求得的dis不是真正意义上的最短距离,也不会影响我们最终的结果。

我们的过程实际上是每一次添加一个点,然后逐渐建起一棵树,我们并不是真的希望这个点到我们的树是最近的,我们只希望这个点加入我们的最小生成树后可以满足我们贪心的要求:局部最优导致整体最优,这个局部指的是我们最小生成树的边权,而并不是真正意义上的距离。这一点一定要好好理解!

同样,prim算法也可以堆优化,那么堆里存的就是结点的编号和它到树的距离,和Dijkstra的堆优化基本一样,希望读者自己尝试去实现。


因为Kurscal涉及大量对边的操作,所以它适用于稀疏图;普通的prim算法适用于稠密图,但堆优化的prim算法更适用于稀疏图,因为其时间复杂度是由边的数量决定的。

Guess you like

Origin www.cnblogs.com/Leo_wl/p/10993141.html