zoukankan      html  css  js  c++  java
  • 任意多个有序结合求交集

    朴素的想法是两两求交集,然后得到结果,又因为是有序的,两个有序结合求交集的方法,之前想的是遍历其中小的集合,在另一个集合里做二分查找,这样时间复杂度是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;
    }
  • 相关阅读:
    UVA1401 Remember the word DP+Trie
    LG5202 「USACO2019JAN」Redistricting 动态规划+堆/单调队列优化
    模拟赛总结合集
    LG5201 「USACO2019JAN」Shortcut 最短路树
    LG5200 「USACO2019JAN」Sleepy Cow Sorting 树状数组
    LG5196 「USACO2019JAN」Cow Poetry 背包+乘法原理
    20190922 「HZOJ NOIP2019 Round #7」20190922模拟
    LG2530 「SHOI2001」化工厂装箱员 高维DP+记忆化搜索
    LG2893/POJ3666 「USACO2008FEB」Making the Grade 线性DP+决策集优化
    关于对QQ 输入法的评价
  • 原文地址:https://www.cnblogs.com/23lalala/p/4910649.html
Copyright © 2011-2022 走看看