算法题目
-
集合类
-
并集
题目:有10台电脑,编号如下:{1,2,3,4,5,6,7,8,9,10},已知下列电脑间完成了连接,
1和2,2和4,3和5,4和7,5和8,6和9,6和10 ;
如1和2,2和4连接,则认为1和4也连接上了,
求2和7之间,5和9之间是否连接?
c-check检查,
i–input 输入
思想:
-
-
将这10台电脑看成10个集合,{1},{2},{3},{4}…{9},{10}
-
谁和谁连接了,则认为是谁和谁,进行并集;
-
查询x和y是否连接,则看x,y是否同个集合;
考虑:
- 集合如何表示;考虑树的思想,利于查找和并集
- 树采用如何数据类型?链表还是数组进行构建?考虑到需要查找,故用数组表示树利用查找,同时题目所述,给定了数组长度,不需要拓展数组长度。非常适合;
- 数组中,自定node对象,对象包含data(电脑编号),parent(父结点的index,-1表示该结点为根结点)
- 并运算,改变其数组中的node对象的值,即可完成题目要求;具体代码如下
public class ComputerConnect {
private Node[] computers;
public ComputerConnect(int[] pcs) {
this.computers = new Node[pcs.length];
for (int i = 0; i < pcs.length; i++) {
computers[i]=new Node(pcs[i],-1);
}
}
class Node{
int data;
int parent;
public Node() {
}
public Node(int data, int parent) {
this.data = data;
this.parent = parent;
}
}
public int find(int pcNum){
int i;
// 寻找该computers数组内的data是否有该pc,若有则i为其index;
for (i = 0; i < computers.length && computers[i].data== pcNum; i++) ;
//若满足没有在数组中找到该pc的编号,则返回-1;
if (i>=computers.length) return -1;
//找到其根结点;
for (;computers[i].parent>=0;i=computers[i].parent);
//返回其根结点的索引
return i;
}
public void union(int pcNum1, int pcNum2){
int root1 = find(pcNum1);
int root2 = find(pcNum1);
if (root1!=root2) computers[root2].parent = root1;
}
}
优化
忘记改进点所述,回归上面代码本身 ;
执行
union(2,1)
union(3,1)
union(4,1)
union(5,1)后
树会越来越高,拖慢的查找的效率;
改进点:
不断的union可能导致树太高,导致查找过慢,故可以将小树并到大树上,迎来新的问题,如何区分大小树?
可以修改parent的值,如-1表示其有一个子结点,-6表示共有6个子结点;
随后修改union方法即可;
按秩归并
此方法称为按秩归并,根据高度或者规模大小,归并在一起,
同时此题,计算机编号都为数字类型,可以作为设计为索引,不需要Node的data域,或者设计解码算法,换算为索引类型可以简化find方法中
for (i = 0; i < computers.length && computers[i].data== pcNum; i++) ;
此段的循环量 ;
路径压缩
核心思想:修改find代码,当找一个结点,把该结点提升到第二层树的位置;图示:
public int find(int pcNum){
int i;
for (i = 0; i < computers.length && computers[i].data== pcNum; i++) ;
if (i>=computers.length) return -1;
for (;computers[i].parent>=0;i=computers[i].parent);
computers[temp].perent = i;
return i;
}