您的位置:首页 > 其它

并查集

2015-09-03 19:42 246 查看
百度笔试题: 编写一个Java应用程序,对于给定的一个字符串的集合,格式如:

  {aaa bbb ccc}, {bbb ddd},{eee fff},{ggg},{ddd hhh}

要求将其中交集不为空的集合合并,要求合并完成后的集合之间无交集,例如上例应输出:

{aaa bbb ccc ddd hhh},{eee fff}, {ggg}


解法1

思路是:

1 首先创建一个邻接表,hash值从字符串中得到,邻接链表存放的是集合编号(从0开始),根据字符串分组,将同一个集合放在一起。

2 生成一个集合个数的数组A,初始化为-1;遍历整个邻接表,通过编号将一起的集合组合在一起,做法是:依次找找邻接表的中的项,一个集合编号的list,首先遍历这几个编号看是否之前出现过,如果有出现过,那么都设置成编号的最小值,也就是归入之前最早出现的的集合中(这里有个注意点,比如A为【0101】,当12也在一起的时候很明显0123都该在一起的,因为0和2在一起,1和3在一起的;因此要多一步额外的扫描整个数组,将和1集合相等的3也设置成0);如果没有出现过,也就是所有数组A中的对应的元素都为-1,则设置成计数器的新值。

最后根据数据组A的编号将元素分组,用Set存放可以挑出重合的元素。

[java] view
plaincopy

import java.util.*;

import java.util.Map.Entry;

public class Test{

public static void solve(String[][] a){

//遍历元素,elemnts中key值为字符串,value值该字符串所在集合中的编号

//集合编号从0开始

//其实这个画图出来就是一个邻接表

Map<String,List<Integer>> elements = new HashMap<String,List<Integer>>();

for(int i=0;i<a.length;i++){

for(int j=0;j<a[i].length;j++){

if(elements.get(a[i][j])==null){

List<Integer> list = new ArrayList<Integer>();

list.add(i);

elements.put(a[i][j],list);

}else{

List<Integer> list = elements.get(a[i][j]);

list.add(i);

}

}

}

int[] collections = new int[a.length];//每个位置对应一个集合

for(int i=0;i<collections.length;i++){

collections[i] = -1;

}

//遍历整个集合将有相同字符串的集合设置成同一数字

int count = 0; //计数器,相同值就是同一个集合

for(Iterator<Entry<String,List<Integer>>> it = elements.entrySet().iterator();it.hasNext();){

//在同一个list中的集合表明有相同的字符串,因此属于同一个集合

List<Integer> list = it.next().getValue();

//遍历查找这个list中的集合是否已经和其他集合合并

//如果合并了,找出最小的集合编号

int min = -2;

for(int i=0;i<list.size();i++){

if(collections[list.get(i)]!=-1&&(min==-2||collections[list.get(i)]<min)){

min = collections[list.get(i)];

}

}

if(min==-2){//所有集合都没出现过

for(int i=0;i<list.size();i++){

collections[list.get(i)] = count;

}

count++;

}else{

for(int i=0;i<list.size();i++){

for(int j=0;j<collections.length;j++)

if(collections[j]==collections[list.get(i)]&&collections[j]!=-1)

collections[j] = min;

collections[list.get(i)] = min;

}

}

}

//根据collections的编号分组将分组中的元素加入对应的位置

Map<Integer,Set<String>> results = new HashMap<Integer,Set<String>>();

for(int i=0;i<collections.length;i++){

Set<String> set;

if(results.get(collections[i])==null){

results.put(collections[i], new HashSet<String>());

}

set = results.get(collections[i]);

for(int j=0;j<a[i].length;j++){

set.add(a[i][j]);

}

}

//打印出来

for(Iterator<Entry<Integer,Set<String>>> it = results.entrySet().iterator();it.hasNext();){

Entry entry = it.next();

System.out.print(entry.getKey()+": ");

for(Iterator<String> iter = ((Set<String>)entry.getValue()).iterator();iter.hasNext();){

System.out.print(iter.next()+" ");

}

System.out.println();

}

}

public static void main(String[] args){

String[][] a = {

{"aaa", "bbb", "ccc"},

{"bbb","ddd"},

{"eee", "fff"},

{"ggg"},

{"ddd","hhh"}

};

solve(a);

}

}


解法2 并查集

http://www.roading.org//algorithm/introductiontoalgorithm/Disjoint_set.html

并查集包括3个基本操作:

将每个元素看成单独的集合,设置不同序号 make_set

查找自己所处的集合 find_set

合并两个集合 union


数组式并查集

对于数组式并查集,数组下标代表元素,内容代表集合编号,因此

make_set函数中,直接将数组内容都设置成下标,表示不同元素初始化在不同的集合中;
find_set函数中,直接根据下标返回内容
union函数中,设置其中一个元素的集合编号为参考点,然后遍历数组,将要合并的集合中的元素所有元素的集合编号都设置成这个参考点,因为遍历,因此每次合并都是O(n)复杂度

具体代码见:
http://hi.baidu.com/gropefor/item/054967409f55e2af61d7b9b6
对于这个问题,首先将字符串映射成数字,然后申请最大数字长度的数组,然后将集合中相邻元素所在的集合依次合并在一起,最后用hasMap将相同集合的元素放在一起输出。代码如下:

[java] view
plaincopy

import java.util.*;

import java.util.Map.Entry;

public class Test{

private static int[] elements;

private static Map<String,Integer> map;

//初始化

static void make_set(String[][] a){

int count = 0;

Map<String,Integer> map = new HashMap<String,Integer>();

for(int i=0;i<a.length;i++){

for(int j=0;j<a[i].length;j++){

if(map.get(a[i][j])==null){

map.put(a[i][j], count++);

}

}

}

Test.map = map;

elements = new int[count];

for(int i=0;i<count;i++){

elements[i] = i;

}

}

//将相邻元素所在的集合合并

static void union(String[][] a){

for(int i=0;i<a.length;i++){

for(int j=0;j<a[i].length-1;j++){

int n1 = elements[map.get(a[i][j])];

int n2 = elements[map.get(a[i][j+1])];

for(int k=0;k<elements.length;k++){

if(elements[k]==n2){

elements[k] = n1;

}

}

}

}

}

//根据elements中的结果将元素合并并打印

static void print(){

HashMap<Integer,List<String>> results = new HashMap<Integer,List<String>>();

//迭代map找到字符串和对应的下标

for(Iterator<Entry<String,Integer>> it = Test.map.entrySet().iterator();it.hasNext();){

Entry<String,Integer> entry = it.next();

String s = entry.getKey();

int collectionNumber = elements[entry.getValue()]; //所属的集合

if(results.get(collectionNumber)==null){

results.put(collectionNumber, new ArrayList<String>());

}

List<String> list = results.get(collectionNumber);

list.add(s);

}

for(Iterator<List<String>> it=results.values().iterator();it.hasNext();){

List<String> list = it.next();

for(String s:list){

System.out.print(s+" ");

}

System.out.println();

}

}

public static void main(String[] args){

String[][] a = {

{"aaa", "bbb", "ccc"},

{"bbb","ddd"},

{"eee", "fff"},

{"ggg"},

{"ddd","hhh"}

};

make_set(a);

union(a);

print();

}

}


树状并查集

对于树状并查集,存储还是用的数组(数组结构存放树,加一个父亲编号从孩子单向映射到父亲,这个比堆或者完全二叉树还有适用一些;和堆不同的是,堆根据下标确定在树结构中的位置,内容是具体的值;而这个树形结构下标只是位置,内容是父亲编号和自身具体的内容,这里特殊一些,下标既表示位置又表示内容),数组下标代表元素,数组内容是自己的父亲节点的编号,因此

make_set函数中,将数组内容设计成自身下标,表示父亲还是自己;
find_set函数中,一直往上查找父亲,父亲编号区分集合;同时用路径压缩,其实就是将自身到父亲经过的所有节点直接连接到父亲上减少树的高度
union函数中,找到父亲编号,然后拼接在一起

具体代码见:
http://hi.baidu.com/gropefor/item/02ecb7e912e50c3e86d9deb9
这里有个错的地方是,元素个数不代表树的高度,比如a下面有bcdefg孩子,因此这棵树AA有7个节点,而h下面有ij孩子,j下面有k,因此这棵树BB有4个孩子,而拼接的时候还是要将AA拼接在BB的下面,而不是相反。

对于这个问题,初始化过程和数组式并查集一样;然后遍历集合将相邻元素代表的集合用union合并;最后遍历数组根据最高父节点将元素合并,然后输出。

[java] view
plaincopy

import java.util.*;

import java.util.Map.Entry;

public class Test{

private static int[] elements;

private static Map<String,Integer> map;

//初始化

static void make_set(String[][] a){

int count = 0;

Map<String,Integer> map = new HashMap<String,Integer>();

for(int i=0;i<a.length;i++){

for(int j=0;j<a[i].length;j++){

if(map.get(a[i][j])==null){

map.put(a[i][j], count++);

}

}

}

Test.map = map;

elements = new int[count];

for(int i=0;i<count;i++){

elements[i] = i;

}

}

//将相邻元素所在的集合合并

static void union(String[][] a){

for(int i=0;i<a.length;i++){

for(int j=0;j<a[i].length-1;j++){

int n1 = map.get(a[i][j]);

int n2 = map.get(a[i][j+1]);

while(n1!=elements[n1])

n1 = elements[n1];

elements[n1] = n2;

}

}

}

//根据elements中的结果将元素合并并打印

static void print(){

HashMap<Integer,List<String>> results = new HashMap<Integer,List<String>>();

//迭代map找到字符串和对应的下标

for(Iterator<Entry<String,Integer>> it = Test.map.entrySet().iterator();it.hasNext();){

Entry<String,Integer> entry = it.next();

String s = entry.getKey();

int n1 = entry.getValue();

//根据n1寻找父亲

while(n1!=elements[n1])

n1 = elements[n1];

if(results.get(n1)==null){

results.put(n1, new ArrayList<String>());

}

List<String> list = results.get(n1);

list.add(s);

}

for(Iterator<List<String>> it=results.values().iterator();it.hasNext();){

List<String> list = it.next();

for(String s:list){

System.out.print(s+" ");

}

System.out.println();

}

}

public static void main(String[] args){

String[][] a = {

{"aaa", "bbb", "ccc"},

{"bbb","ddd"},

{"eee", "fff"},

{"ggg"},

{"ddd","hhh"}

};

make_set(a);

union(a);

print();

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: