朴素的想法是两两求交集,然后得到结果,又因为是有序的,两个有序结合求交集的方法,之前想的是遍历其中小的集合,在另一个集合里做二分查找,这样时间复杂度是O(m*lgn),m是小的集合的大小,n是大的集合的大小。
这样做只利用到了n集合是有序,但是没有利用到m集合的有序特性,假如是两个无序集合,对大的排序,小的在里面做二分是可用的。
下面我们说一种充分利用两个都有序的方法。
假设 m={1,2,3} n={2,3,4} 那么 m 交 n = {2,3}
假设我们从n开始遍历,n[0]是2,那么我们可以知道交集中的结果,最小也就是2,不可能比2再小;当然如果看n[2]是4,我们也可以知道交集中的结果不可能比4再大。现在基于刚才那个原理,如果以n为基准,从小到大遍历,先检测n[0],那么m集合从小往大开始遍历,碰到比待检测的数小的都丢弃,碰到和待检测数一样的就说明这个数是交集,碰到比待检测数大的,说明n需要向后移动一个指针。当m或者n任何中的一个数组遍历完毕的时候找交集就算结束了。
这种方法充分利用了两个数组的有序,m和n中的两个指针都是向前移动,没有回溯,时间复杂度最坏也是O(m+n)
知道两个数组取交集,那么我们看多个数组取交集的情况,其实和两个是完全一样的,只不过,待检测的元素放入交集的条件是,每个集合遍历的时候都要有这个元素。
还有一个问题需要考虑的就是选取哪个为基准数组,我们之前说退出的条件是其中任何一个数组遍历完毕,就都算退出了,所以选取的根据是让其中任何一个数组快速能遍历完。
所以我觉得选取策略有两方面
如果是想让基准数组快点退出,那么我们肯定是选基准数组长度最小的。
如果是想让待检测的数组快点退出,那么我们应该选取基准数组中第一个元素最大的,这样第一个能跳过的数字能特别多。
还有,一种选法是基准数组中的数据十分离散,这样也可以跳过很多数。
具体哪种策略最快,还要看数组的特性。
代码实现,对比使用Java的HashSet
i use 110 ms
i2 use 182 ms
i3 use 845 ms
@Test public void test() { List<SortedSet<Integer>> sets = Lists.newArrayList(); Set<Integer> common = new HashSet<Integer>(); for (int i=0;i<10; i++) { common.add(new Random().nextInt(50000)); } for (int i=0;i<10;i++) { sets.add(i, new TreeSet<Integer>()); for (int j=0;j<100;j++) { sets.get(i).add(new Random().nextInt(50000)); } sets.get(i).addAll(common); System.out.println(sets.get(i)); } Long begin = 0l; Long end = 0l; Pair<Integer[], Integer[][]> pair = intersectHelper(sets); begin = System.currentTimeMillis(); for (int i=0; i <10000; i++) { if (i==0) { System.out.println(intersect(pair.left, pair.right)); } else { intersect(pair.left, pair.right); } } end = System.currentTimeMillis(); System.out.println("i use " + (end-begin) + " ms"); begin = System.currentTimeMillis(); for (int i=0; i <10000; i++) { if (i==0) { System.out.println(intersect2(sets)); } else { intersect2(sets); } } end = System.currentTimeMillis(); System.out.println("i2 use " + (end-begin) + " ms"); begin = System.currentTimeMillis(); for (int i=0; i <10000; i++) { if (i==0) { System.out.println(intersect3(sets)); } else { intersect3(sets); } } end = System.currentTimeMillis(); System.out.println("i3 use " + (end-begin) + " ms"); } private Collection<Integer> intersect3(List<SortedSet<Integer>> sets) { Collection<Integer> result = new HashSet<Integer>(); boolean first = true; for(SortedSet set : sets) { if (first) { CollectionUtils.addAll(result, set.iterator()); first = false; } else { CollectionUtils.retainAll(result, set); } } return result; } private Collection<Integer> intersect2(List<SortedSet<Integer>> sets) { Collection<Integer> result = new HashSet<Integer>(); boolean first = true; for(SortedSet set : sets) { if (first) { result.addAll(set); first = false; } else { result.retainAll(set); } } return result; } private Pair<Integer[], Integer[][]> intersectHelper(List<SortedSet<Integer>> sets) { Collections.sort(sets, new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { SortedSet s1 = (SortedSet) o1; SortedSet s2 = (SortedSet) o2; return s1.size() > s2.size() ? 1 : -1; } }); Integer[] head = new Integer[sets.get(0).size()]; sets.get(0).toArray(head); Integer[][] others = new Integer[sets.size() - 1][]; int i = 0; for (SortedSet<Integer> set : sets.subList(1, sets.size())) { others[i] = new Integer[set.size()]; int p = 0; for (Integer one : set) { others[i][p++] = one; } i++; } return new Pair<Integer[], Integer[][]>(head, others); } private Collection<Integer> intersect(Integer[] head, Integer[][] others) { Collection<Integer> result = new ArrayList<Integer>(); int i; // 其他数组的工作指针位置 int[] othersPoint = new int[others.length]; for (i = 0; i<head.length; i++) { int doc = head[i]; boolean allIn = true; boolean otherExit = false; for (int j=0; j<others.length; j++) { Integer[] other = others[j]; while (othersPoint[j] < other.length && other[othersPoint[j]] < doc) { othersPoint[j]++; } if (othersPoint[j] == other.length) { otherExit = true; allIn = false; break; } if (other[othersPoint[j]] != doc) { allIn = false; } } if (allIn) { result.add(doc); } if (otherExit) { return result; } } return result; }