有N个罪犯要抢银行,这些罪犯,有些单独作案,自己抢自己的,而有些合伙作案,抢到的钱合在一起。用 <i , j> (i != j)表示第i个罪犯和第j个罪犯为一组,例如<2 , 9> <5 , 9> 表示2、5、9三个罪犯为同一组。现在有m个这样的 <i , j> 数据,表示N个罪犯的分组情况。现在抢银行已经结束,第 k 个罪犯抢到的钱为 money[ k ],求抢到钱最多的那一组的总钱数。(单独作案的,自己一个人为一个组)
这道题解决思路的关键是确定每个罪犯的分组。我们可以用一个集合表示一种分组,不断查找m个 <i , j> 数据,来确定所有分组的情况。显然这种“暴力”的方式不是我们想要的,其实这是一个经典的“并查集”问题。像抢银行这种寻找集合的问题,都是“并查集”问题,比如亲属关系问题、图的最小生成树问题。
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。并查集的核心代码只有3行,非常巧妙的且极大的降低了该类问题的时间复杂度。
并查集解题思路是(以抢银行为例):
1、用一个数组boss[]表示N个罪犯的各自的老大(即抢银行团伙组长),第i个罪犯的老大即boss[i];
2、先将每个个体作为一个单独的集合,(自己是自己的老大)第i个罪犯的老大(boss[i] = i)是自己;
3、利用一对数据 <i , j> (i != j),合并两个罪犯的老大(即令第 i 个罪犯的老大 是 第j个罪犯的老大 的老大)boss[ boss[j] ] =boss[i] ,俗话“一山不容二虎”,顶级老大不能是两个,讲究“先来后道”,让先来i的老大成为当前分组的顶级老大;
4、更新自己的boss[j],让第j个罪犯的boss[j]更新为当前分组的顶级老大;
5、如果还有数据 <i , j>,调转到3执行,否则,执行6;
6、分组结束,处理其它情况,这里是统计最大总钱数的团伙的钱数。
代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 并查集,3行核心代码
int getboss(vector<int>& boss, int k)
{
if (boss[k] == k) return k;// 如果第 k 个罪犯的老大是他自己,那么就返回他自己,即返回当前分组的顶级老大
boss[k] = getboss(boss, boss[k]);// 如果第 k 个罪犯的老大不是他自己,那么就去获得老大的到大,即获得当前分组的顶级老大
return boss[k];// 更新自己的顶级老大,返回当前分组的顶级老大
}
int calculateMoney(vector<vector<int> >& crimPair, vector<int>& money)
{
vector<int> boss;// 记录老大的数组
for (int i = 0; i <= money.size(); i++)
boss.push_back(i);// 初始化老大数组,自己是自己的老大
for (int i = 0, boss1 = 0, boss2 = 0; i < crimPair.size(); i++) {
boss1 = getboss(boss, crimPair[i][0]);// 得到 0 罪犯所在分组的顶级老大
boss2 = getboss(boss, crimPair[i][1]);// 得到 1 罪犯所在分组的顶级老大
if (boss1 != boss2) boss[boss2] = boss1;// 如果两个老大不同,则合并老大,让先来的老大成为当前分组的顶级老大
}
for (int i = 1; i <= money.size(); i++) {
boss[i] = getboss(boss, boss[i]);// 更新自己的顶级老大,然后进行其它处理
if (boss[i] != i)
money[boss[i] - 1] += money[i - 1];
}
return *max_element(money.begin(), money.end());
}
int main()
{
vector<vector<int> > crimPair(6, vector<int>(2));
crimPair[0][0] = 1; crimPair[0][1] = 2;
crimPair[1][0] = 2; crimPair[1][1] = 3;
crimPair[2][0] = 7; crimPair[2][1] = 8;
crimPair[3][0] = 5; crimPair[3][1] = 6;
crimPair[4][0] = 7; crimPair[4][1] = 9;
crimPair[5][0] = 4; crimPair[5][1] = 5;
vector<int> money;
money.push_back(25);
money.push_back(34);
money.push_back(23);
money.push_back(45);
money.push_back(16);
money.push_back(51);
money.push_back(29);
money.push_back(38);
money.push_back(47);
cout << calculateMoney(crimPair, money) << endl;
return 0;
}