1.问题分析
若某个家族人员过于庞大,要判断两个人是否是亲戚,确实很不容易。给出某个亲戚关系图,现在任意给出两个人,判断其是否具有亲戚关系。规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x和y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也是x的亲戚。
那么如何很快判断两个人是否是亲戚?
2.算法设计
并查集是一种树形的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。主要有下面三种操作:
(1)初始化
把每个点所在集合初始化为其自身
(2)查找
查找两个元素所在的集合,即就是找祖宗。注意:查找时,采用递归的方法找其祖宗,祖宗集合号等于自己时即停止。在回归时,把当前节点到祖宗路径上的所有结点统一为祖宗的集合号。
(3)合并
如果两个元素的集合号不同,将两个元素合并为一个集合。注意:合并时只需要把一个元素的祖宗集合号,改成另一个元素的祖宗集合号。
3.详细图解
假设现在有7个人,通过输入亲戚关系图,判断两个人是否有亲戚关系。
(1)初始化
把每个人集合号初始化为其自身编号。如图所示:
(2)输入亲戚关系2和7。
(3)查找。查找2所在的集合号为2,7所在的集合号为7
(4)合并。两个元素集合号不同,将两个元素合并为一个集合。在此约定将小的集合号赋值给大的集合号,因此修改father[7]=2。
(5)输入亲戚关系4和5。
(6)查找。查找4所在的集合号4,5所在的集合号5。
(7)合并。两个元素集合号不同,将两个元素合并为一个集合。修改father[5]=4。
(8)输入亲戚关系3和7。
(9)查找。查找3所在的集合号3,7所在的集合号2。
(10)合并。两个元素集合号不同,将两个元素合并为一个集合。修改father[3]=2。
(11)输入亲戚关系4和7。
(12)查找。查找4所在的集合号为4,7所在的集合号是2。
(13)合并。两个元素集合号不同,将两个元素合并为一个集合。修改father[4]=2。集合号为4的结点有两个,在此只需要修改这两个节点中的祖宗即可。并不需要把集合号4的所有节点都遍历一遍。
(14)输入亲戚关系3和4。
(15)查找。3所在的集合号是2,4所在的集合好是2.
(16)合并。两个元素集合号一样,什么都不做。
(17)输入亲戚关系5和7。
(18) 查找。查找5的集合号时,因为5的集合号不是5,所以,找父亲的集合号是4,4的父亲集合号是2,2的父亲集合号是2,此时停止。在查找返回时,把当前结点到祖宗路径上的所有结点集合统一为祖宗的集合号。
(19)合并。集合号一样,什么都不做。
(20)输入亲戚关系5和6。
(21)查找。查找5所在的集合号是2,6所在的集合号是6。
(22)合并。两个元素集合号不同,将两个元素合并为一个集合。修改father[6]=2。
(23)输入亲戚关系2和3。
(24)查找。查找2所在的集合号是2,3所在的集合号是2。
(25)合并。集合号一样,什么都不做。
(26)输入亲戚关系1和2。
(27)查找。1所在集合号是1,2所在集合号是2。将两个元素合并为一个集合。father[2]=1;
4.实际问题分析
快过年了,犯罪分子们也开始为年终奖“奋斗”了。由于强盗人数过于庞大,作案频繁,警方想查清楚到底有几个犯罪团伙实在是太不容易了,不过警方还是搜集到了一些线索。需要咱们帮忙分析一下:
现在有十个强盗:
1号强盗与2号强盗是同伙。
3号强盗与4号强盗是同伙。
5号强盗与2号强盗是同伙。
4号强盗与6号强盗是同伙。
2号强盗与6号强盗是同伙。
8号强盗与7号强盗是同伙。
9号强盗与7号强盗是同伙。
1号强盗与6号强盗是同伙。
2号强盗与4号强盗是同伙。
有一点需要注意:强盗同伙的同伙也是同伙。你能帮助警方查出有多少个独立的犯罪团伙吗?
5.源代码
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> using namespace std; const int N=1111; int father[N]={0}; int n; int m; int k; int sum=0; void initialize() //初始化函数,将father数组每个元素的下标等于自身的数字 { for (int i=1;i<=n;i++) { father[i]=i; } } int getfather(int v) //找祖宗递归函数 { if(father[v]==v) //如果编号等于祖宗的编号(或是自己的)算法结束 { return v; //返回祖宗编号 } else { //在返回时,把路上遇到的人的编号改为最后找到祖宗的编号 //这是最最最祖宗的编号。 //这样可以提高找到最最最最后的祖宗编号的速度。 father[v]=getfather(father[v]); //否则递归查找祖宗编号 return father[v]; //最终返回祖宗编号。 //返回father[v]是可以用于递归查找。 } } void Merge(int v,int u) { int t1; int t2; t1=getfather(v); //第一个结点的编号 t2=getfather(u); //第二个结点的编号 if(t1!=t2) //如果编号不相等 { father[t2]=t1; //将较小的编号赋予较大的编号,靠左原则。 } } int main() { int x; int y; int i; cout << "请输入盗贼的人数和目前知道的情报的条数" << endl; cin >> n>> m; initialize(); cout << "请输入情报:谁和谁是同伙" << endl; for (i=1;i<=m;i++) { cin >> x>> y; //输入关系 Merge(x,y); //将最终编号一样的结点合并。 } for (i=1;i<=n;i++) { if(father[i]==i) { sum++; } } cout <<"犯罪团伙的个数有:" << endl; cout << sum; return 0; }