a very interesting union

Let's look at an example first:

First give you several towns on the map, these towns can be regarded as points, and then tell you which pairs of towns are directly connected by roads. The last thing to solve is the connectivity problem of the whole graph. For example, randomly give you two points, let you judge whether they are connected, or ask you how many connected branches the whole graph has, that is, it is divided into several independent blocks. For example, in the case of unimpeded engineering, asking how many roads still need to be built, the essence is to ask for several connecting branches. If it is 1 connected branch, it means that the points on the whole graph are connected, and there is no need to build roads; if it is 2 connected branches, just build 1 more road, and select a point from each of the two branches, Connect them, then all the points are connected; if it is 3 connected branches, then you only need to build two more roads...

Enter the data with the following set of data to illustrate

4 2 1 3 4 3

The first line tells you that there are a total of 4 points and 2 roads. The next two lines tell you that there is a way between 1 and 3, and a way between 4 and 3. Then the whole picture is divided into 1-3-4 and 2 parts. As long as another road is added, 2 is connected to any other point, and the smooth project is realized, then the output result of this group of data is 1. Well, let's program this function now. There are hundreds of towns, and there are no known how many roads, and there may be loops. How can this be good?

I didn't know it before, since I used and checked the set, hey, the effect is really good! Our whole family uses it!

The union search set consists of an integer array and two functions. The array pre[] records what the leading point of each point is, the function find is a search, and the join is a merge.

int pre[1000 ];
int find(int x) //find the root node
{
    int r=x;
    while ( pre[r ] != r ) //return root node r
          r=pre[r ];
 
    int i=x , j ;
    while( i != r ) //path compression
    {
         j = pre[ i ]; // use temporary variable j to record the value of the superior before changing his
         pre[ i ]= r ; //Change the parent to the root node
         i=j;
    }
    return r ;
}
 
 
void join(int x,int y) //Determine whether xy is connected,
                                           //If it is already connected, don't worry about it. If it is not connected, merge the connected branches where they are located.
{
    int fx=find(x),fy=find(y);
    if(fx!=fy)
        pre[fx ]=fy;
} 

To explain the principle of union lookup, I'll give a more loving example. It is said that there are thousands of heroes of all kinds scattered on the rivers and lakes. They don't have a proper occupation, and they walk around with their swords on their backs all day. When they encounter people who are not the same as them, they will inevitably have a fight. But the heroes have an advantage in that they are loyal and never beat their friends. And they believe in "a friend's friend is my friend", as long as they can be connected through friendship, no matter how many turns they turn, they are considered their own. In this way, a community is formed on the rivers and lakes, which are connected in series through the friendship between the two. People who are not in the same group can't be connected through friendship anyway, so they can rest assured and fight to the death. But how can two people who didn't know each other know whether they belong to a circle of friends?

We can recommend a relatively famous person in each circle of friends as the representative of the circle, so that each circle can be named "Zidane Friends Team" "Ronaldo Friends Team"... two As long as people check with each other whether their captain is the same person, they can determine the relationship between friend and foe.

But there is still a problem. Heroes only know who their direct friends are. Many people don't know the captain at all. To judge who their captain is, they can only ask aimlessly through the friendship of their friends: "You Is it the captain? Are you the captain?" In this way, the captain can't keep his face, and the efficiency is too low, and he may fall into an infinite loop. So the captain ordered to regroup. Everyone in the team implements a hierarchical system to form a tree-like structure. My captain is the root node, and the following are the second-level players and the third-level players. Everyone just needs to remember who their superiors are. When faced with judging friends and enemies, as long as you ask layer by layer to the top, you can determine who the captain is in a short time. Since all we care about is whether two people are connected, it doesn't matter how they are connected, what is the structure inside each circle, or even who the captain is. So we can let the captain regroup at will, as long as we don't make a mistake in the relationship between friend and foe. Thus, sects were born.


Let's take a look at the implementation of the union query. int pre[1000]; This array records who is the superior of each hero. The heroes are numbered from 1 or 0 (depending on the meaning of the question), and pre[15]=3 means that the superior of the 15th hero is the 3rd hero. If a person's superior is himself, it means that he is the head, and the search ends here. There are also lonely people who form their own faction, such as Ouyang Feng, then his superior is himself. Everyone only recognizes their superiors. For example, Hu Qingniu only knew that his superior was Yang Zuoshi. Who is Zhang Wuji? do not know! If you want to know who your head is, you can only check it level by level. The function find is used to find the master, and the meaning cannot be clearer (the path compression algorithm is ignored, and I will talk about it later).

int find(int x) //find the head of me (x)
{
    int r=x; //Entrust r to find the head
    while (pre[r ]!=r)                                                        //如果r的上级不是r自己(也就是说找到的大侠他不是掌门 = =)
    r=pre[r ] ;                                                                   // r 就接着找他的上级,直到找到掌门为止。
    return  r ;                                                                   //掌门驾到~~~
}

再来看看join函数,就是在两个点之间连一条线,这样一来,原先它们所在的两个板块的所有点就都可以互通了。这在图上很好办,画条线就行了。但我们现在是用并查集来描述武林中的状况的,一共只有一个pre[]数组,该如何实现呢? 还是举江湖的例子,假设现在武林中的形势如图所示。虚竹小和尚与周芷若MM是我非常喜欢的两个人物,他们的终极boss分别是玄慈方丈和灭绝师太,那明显就是两个阵营了。我不希望他们互相打架,就对他俩说:“你们两位拉拉勾,做好朋友吧。”他们看在我的面子上,同意了。这一同意可非同小可,整个少林和峨眉派的人就不能打架了。这么重大的变化,可如何实现呀,要改动多少地方?其实非常简单,我对玄慈方丈说:“大师,麻烦你把你的上级改为灭绝师太吧。这样一来,两派原先的所有人员的终极boss都是师太,那还打个球啊!反正我们关心的只是连通性,门派内部的结构不要紧的。”玄慈一听肯定火大了:“我靠,凭什么是我变成她手下呀,怎么不反过来?我抗议!”抗议无效,上天安排的,最大。反正谁加入谁效果是一样的,我就随手指定了一个。这段函数的意思很明白了吧?

void join(int x,int y)                                                                   //我想让虚竹和周芷若做朋友
{
    int fx=find(x),fy=find(y);                                                       //虚竹的老大是玄慈,芷若MM的老大是灭绝
    if(fx!=fy)                                                                               //玄慈和灭绝显然不是同一个人
    pre[fx ]=fy;                                                                           //方丈只好委委屈屈地当了师太的手下啦
}

再来看看路径压缩算法。建立门派的过程是用join函数两个人两个人地连接起来的,谁当谁的手下完全随机。最后的树状结构会变成什么胎唇样,我也完全无法预计,一字长蛇阵也有可能。这样查找的效率就会比较低下。最理想的情况就是所有人的直接上级都是掌门,一共就两级结构,只要找一次就找到掌门了。哪怕不能完全做到,也最好尽量接近。这样就产生了路径压缩算法。 设想这样一个场景:两个互不相识的大侠碰面了,想知道能不能揍。 于是赶紧打电话问自己的上级:“你是不是掌门?” 上级说:“我不是呀,我的上级是谁谁谁,你问问他看看。” 一路问下去,原来两人的最终boss都是东厂曹公公。 “哎呀呀,原来是记己人,西礼西礼,在下三营六组白面葫芦娃!” “幸会幸会,在下九营十八组仙子狗尾巴花!” 两人高高兴兴地手拉手喝酒去了。 “等等等等,两位同学请留步,还有事情没完成呢!”我叫住他俩。 “哦,对了,还要做路径压缩。”两人醒悟。 白面葫芦娃打电话给他的上级六组长:“组长啊,我查过了,其习偶们的掌门是曹公公。不如偶们一起及接拜在曹公公手下吧,省得级别太低,以后查找掌门麻环。” “唔,有道理。” 白面葫芦娃接着打电话给刚才拜访过的三营长……仙子狗尾巴花也做了同样的事情。 这样,查询中所有涉及到的人物都聚集在曹公公的直接领导下。每次查询都做了优化处理,所以整个门派树的层数都会维持在比较低的水平上。路径压缩的代码,看得懂很好,看不懂也没关系,直接抄上用就行了。总之它所实现的功能就是这么个意思。


再回过头看起初的路问题:

#include int pre[1000 ];
int find(int x)
{
    int r=x;
   while (pre[r ]!=r)
   r=pre[r ];
   int i=x; int j;
   while(i!=r)
   {
       j=pre[i ];
       pre[i ]=r;
       i=j;
   }
   return r;
}
int main()
{
   int n,m,p1,p2,i,total,f1,f2;
   while(scanf("%d",&n) && n)         //读入n,如果n为0,结束
   {                                                    //刚开始的时候,有n个城镇,一条路都没有 //那么要修n-1条路才能把它们连起来
       total=n-1;
       //每个点互相独立,自成一个集合,从1编号到n //所以每个点的上级都是自己
       for(i=1;i<=n;i++) { pre[i ]=i; }                //共有m条路
       scanf("%d",&m); while(m--)
       { //下面这段代码,其实就是join函数,只是稍作改动以适应题目要求
           //每读入一条路,看它的端点p1,p2是否已经在一个连通分支里了
           scanf("%d %d",&p1,&p2);
           f1=find(p1);
           f2=find(p2);
               //如果是不连通的,那么把这两个分支连起来
               //分支的总数就减少了1,还需建的路也就减了1
           if(f1!=f2)
            {
               pre[f2 ]=f1;
               total--;
           }
           //如果两点已经连通了,那么这条路只是在图上增加了一个环 //对连通性没有任何影响,无视掉
       }
//最后输出还要修的路条数
       printf("%d\n",total);
   }
   return 0;
}
转自http://blog.csdn.net/dellaserss/article/details/7724401

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324876608&siteId=291194637