上一篇博客笔记的末尾提到,碰到数据量较大的图,需要计算图的连通分支个数时,如果使用邻接矩阵来保存图,会发生内存不足的情况。下面记录一下使用并查集来解决该问题的办法。
题目如下:
本题与‘热点营销-1’题型一致,仅改变微信用户数量(100000)和关系列表。
请问,若需要使一个广告让这100000个微信用户都看到,请问初始至少将这条广告传播个几个人?
首先创建一个User类来表示用户
public class User{
private int data;
//@param rank is used to store the height of the set-tree
//when this is the root user
private int rank;
private User parent;
//status is used to judge if this is the root user
private Boolean status;
public User(int data){
this.data = data;
this.parent = this;
this.rank = 0;
this.status = false;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public int getRank() {
return rank;
}
public void rankInc() {
this.rank++;
}
public User getParent() {
return parent;
}
public void setParent(User parent) {
this.parent = parent;
}
public Boolean isRoot() {
return status;
}
public void setStatus(Boolean status) {
this.status = status;
}
}
并查集的查找操作
然后是写并查集的根节点查找算法,这个过程有点绕,需要仔细研究一下。该算法中还加了路径压缩过程,需要特别注意
/**
* find certain user's root parent and do path compression
* @param user
* @return the root parent of the user
*/
public static User find(User user){
User root = user;
//in union-find root's parent is itself
//if root's parent is not himself
//then find his parent until root's parent is himself
while(root.getParent() != root ){
root = root.getParent();
}
//do path compression to make the parent of each user in the path to be the root
User copy = user,temp;
while(copy != root){
temp = copy.getParent();
copy.setParent(root);
copy = temp;
}
//return the root parent of the user
return root;
}
并查集的合并操作
接下来是写并查集的合并操作算法,只需要找到想要合并的两个目标集合树的根节点,然后把矮的树添加为高的数的根节点的子树就行了(矮树的根节点的父节点指向高树的根节点)
/**
* merge two set-tree
* @param x
* @param y
*/
public static void union(User x, User y){
User xRoot = find(x);
User yRoot = find(y);
if(xRoot.getRank() > yRoot.getRank()){
yRoot.setParent(xRoot);
}else{
xRoot.setParent(yRoot);
if(xRoot.getRank() == yRoot.getRank())
yRoot.rankInc();
}
}
使用并查集求连通分支个数
- 开始的时候每一个用户就是一个集合,根节点是他自己,(并查集根节点的父节点是自己)
- 如果两个用户有联系,则合并这两个用户所在的集合
- 合并完成之后需要将根节点的
status
置为true
,表示它是根节点,便于统计连通分支个数 - 统计连通分支个数,即计算根节点个数
public static void main(String[] args){
//initial every user's parent to themselves
for(int i = 0; i < 100000; i++){
users[i] = new User(i);
}
try{
FileReader fr =
new FileReader("E:/dev/java/javaweb/qlcoder/144341511030664.txt");
BufferedReader br = new BufferedReader(fr);
String in = null;
while((in = br.readLine()) != null){
String[] fragment = in.split(" ");
int one = Integer.valueOf(fragment[0]) - 1;
int two = Integer.valueOf(fragment[1]) - 1;
union(users[one], users[two]);
}
br.close();
}catch(IOException e){
e.printStackTrace();
}
for(int i = 0; i < 100000; i++){
find(users[i]).setStatus(true);
}
for(int i = 0; i < 100000; i++){
if(users[i].isRoot()){
count++;
}
}
System.out.println(count);
}