任意多个有序结合求交集

朴素的想法是两两求交集,然后得到结果,又因为是有序的,两个有序结合求交集的方法,之前想的是遍历其中小的集合,在另一个集合里做二分查找,这样时间复杂度是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;
}

 

转载于:https://www.cnblogs.com/23lalala/p/4910649.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值