算法学习 并查集(笔试题目:找同伙)

题目背景太长,记得不清楚,暂参考《啊哈算法》一书,根据笔试题目大意改编如下:

警察正要捉获某地区的犯罪团伙,由于强盗人数过大,想查清楚有几个团伙非常困难。

根据上级指示,需要首先尽快抓获强盗A所在的团伙,这需要掌握 1 所在团伙的人数。先有资料如下:

强盗1 和 强盗2 是同伙

强盗3 和 强盗4 是同伙

强盗2 和 强盗5 是同伙

强盗3 和 强盗2 是同伙

注意,强盗的同伙的同伙也是同伙,问  强盗1 的同伙(不包括1自己)有多少人?

该题形式化表示如下:

每个测试实例首先包括2个整数:N(1 <= N <= 1000),M(0 <= M <= N*(N-1)/2),代表现有N个人(用1~N编号)和M组关系;
在接下来的M行里,每行包括2个整数,a,b,代表a跟b是同伙;
当N = 0,M = 0输入结束;

测试样例:

5 4

1 2

2 5

3 2

1 2

3 1

3 2

0 0

输出:

4

0

参考代码如下:

# include <iostream>
# include <vector>
using namespace std;

// 将所有人的master归属为自己
void init_person_table(int person_table[], int nums_person)
{
    for (int i = 0; i < nums_person+1; i++)
        person_table[i] = i;
}

// 使用递归方式沿着已知线索 不断往上查找目标人员的leader,
// 直到找到真正的BOSS为止 即找到“最高领导人”
int find_BOSS(int person_table[], int target)
{    
    // 首领为自己(树的根节点)
    if (person_table[target] == target)        
        return target;
    else
    {
        // 进行路径压缩,每次函数返回的时候顺带将
        // “遇到的人的leader”改为大BOSS的编号
        // 可以提高下一次找 BOSS 时的查找速度!!!
        person_table[target] = find_BOSS(person_table, person_table[target]);
        return person_table[target];
    }
}

// 在人员表中,连结两人的关系, 左边为leader,右边为下属
void merge_relation(int person_table[],int leader, int sub)
{
    // 分别找两人的所属BOSS
    int a = find_BOSS(person_table, leader);
    int b = find_BOSS(person_table, sub);
    if (a != b)            
    {    
        // 按照最小原则,把序号较大者所在的集合并入序号较大者的集合
        // 较小者成为较大者的boss
        person_table[b] = a;        
        // 经过路径压缩后,table[sub]的根的值赋值为leader的祖先table[leader]
    }
}

int main()
{    
    // 人数与的关系数
    int nums_person = 0, nums_relations = 0;    
    vector <int> ans;    // 用于统一打印答案
    while (1) 
    {
        nums_person = 0, nums_relations = 0;
        cin >> nums_person >> nums_relations;
        // 输入人数与关系数,输入0,0结束
        if (nums_person <= 1 || nums_relations == 0)    
        {    // 可能存在异常输入
            if (nums_person == 0 && nums_relations == 0)// 正常结束
                break;
            else
                // 默认返回0, 并重新输入
                ans.push_back(0);
                //cout << 0 << endl;                        
            continue;
        }
        // 根据人数初始化人员列表
        int *person_table = new int[nums_person + 1];    
        init_person_table(person_table, nums_person);    

        for (int i = 0; i < nums_relations; i++)
        {
            // 输入两个关系者,默认较小者为per1 (Leader)
            // 方便统计时找到 “1” 的同伴人数
            int per1 = 0, per2 = 0;                    
            cin >> per1 >> per2;                    
            if (per2 < per1) { int t = per1; per1 = per2; per2 = t; }
            // 合并两个人的关系集合,较小者作为合并主体
            merge_relation(person_table, per1, per2);        
        }

        // 统计 1 的同伴个数(从2开始找)
        int count = 0;
        for (int i = 2; i <= nums_person; i++)
        {
            // 集中阶段,因为会存在某些个体的讯号只是他的leader而不是BOSS
            // 由于 1 的数字最小,不需要归并
            // 其实只要满足(person_table[i] != i && person_table[i] != 1)就可以了
            if (person_table[i] != i)    
                person_table[i] = find_BOSS(person_table, i);
            if (person_table[i] == 1)
                count ++;
        }

        /*
        cout << count << endl;
        for (int i = 1; i <= nums_person;i++)
            cout << person_table[i] <<" ";
        cout << endl;
        */
        ans.push_back(count);
        // 释放当前内存,等待下批数据
        delete[] person_table;                    
    }
    int ans_size = ans.size();
    for (int i = 0; i < ans_size; i++)
        cout << ans[i] << endl;
    return 0;
}

 

posted on 2015-10-01 22:24  hanahimi  阅读(674)  评论(0编辑  收藏  举报

导航