【问题标题】:Comparing two sorted int arrays比较两个排序的 int 数组
【发布时间】:2011-01-21 11:13:44
【问题描述】:

我有数百万个固定大小 (100) 的 int 数组。每个数组都经过排序并具有唯一的元素。对于每个数组,我想找到所有具有 70% 共同元素的数组。现在我每秒进行大约 100 万次比较(使用 Arrays.binarySearch()),这对我们来说太慢了。

谁能推荐一个更好的搜索算法?

【问题讨论】:

  • 你可能想看看布隆过滤器。
  • 那么您的结果是包含至少 70 个常见元素的数组对列表?
  • 另外,您期望有多少匹配项?整数是否大致均匀分布?我问这个是因为如果没有很多匹配项,那么排除启发式可能会有所帮助。
  • 使用Arrays.binarySearch() 对我来说毫无意义。为了比较两个数组,您只需要并行遍历它们并即时计算公共元素。我的意思与归并排序非常相似。
  • 只有我一个人认为这正是可以用汇编语言编写的那种东西吗?

标签: java arrays comparison int


【解决方案1】:

这样的事情应该可以完成(前提是数组已排序并包含唯一元素):

public static boolean atLeastNMatchingElements(final int n,
    final int[] arr1,
    final int[] arr2){

    /* check assumptions */
    assert (arr1.length == arr2.length);

    final int arrLength = arr2.length;

    { /* optimization */
        final int maxOffset = Math.max(arrLength - n, 0);
        if(arr1[maxOffset] < arr2[0] || arr2[maxOffset] < arr1[0]){
            return false;
        }
    }

    int arr2Offset = 0;
    int matches = 0;

    /* declare variables only once, outside loop */
    int compResult; int candidate;

    for(int i = 0; i < arrLength; i++){
        candidate = arr1[i];
        while(arr2Offset < arrLength - 1){
            compResult = arr2[arr2Offset] - candidate;
            if(compResult > 0){
                break;
            } else{
                arr2Offset++;
                if(compResult == 0){
                    matches++;
                    break;
                }
            }
        }
        if(matches == n){
            return true;
        }
        /* optimization */
        else if(matches < n - (arrLength - arr2Offset)){
            return false;
        }
    }
    return false;
}

示例用法:

public static void main(final String[] args){
    final int[] arr1 = new int[100];
    final int[] arr2 = new int[100];
    int x = 0, y = 0;
    for(int i = 0; i < 100; i++){
        if(i % 10 == 0){
            x++;
        }
        if(i % 12 == 0){
            y++;
        }
        arr1[i] = x;
        arr2[i] = y;
        x++;
        y++;
    }
    System.out.println(atLeastNMatchingElements(70, arr1, arr2));
    System.out.println(atLeastNMatchingElements(95, arr1, arr2));
}

输出:

是的
假的

过早优化™

我现在尝试优化上面的代码。请检查代码块是否标记为

/* optimization */

显着不同。优化后,我会重构代码以将其简化为一两个返回语句。

【讨论】:

  • 很好。它可以概括为一次处理多个数组,并且可能比仅成对工作更快。但是,我认为,仍然需要一些更好的想法。
  • @ashish 我添加了一些小的优化,请参阅我的更新@maaartinus 我同意需要一些更好的想法
  • 似乎没有太大区别。无论如何,谢谢。
  • @ashish 虽然如此。 int[] 数组非常快,因此每个“优化”实际上可能会减慢速度
  • @sean 这对我来说似乎是最好的答案。谢谢肖恩。
【解决方案2】:

您可以进行两个快速优化。

如果数组 A 的起始元素大于 B 的结束元素,则它们通常不能有公共元素。

他们另一个是类似三角不等式的东西:

f(B,C) <= 100 - |f(A,B)-f(A,C)|

原因是(假设f(A,B) &gt; f(A,C))至少有f(A,B) - f(A,C) 元素同时在A 和B 中,但不在C 中。这意味着它们不能是B 和C 的共同元素。

【讨论】:

    【解决方案3】:

    您可以尝试忽略重复的合并排序。这是排序数组的 O(n) 操作。如果两个数组有 70% 的共同元素,则生成的集合将具有 130 个或更少的唯一整数。在您的情况下,您不需要结果,因此您只需计算唯一条目的数量并在达到 131 或两个数组的末尾时立即停止。

    编辑 (2) 以下代码可以使用 4 个内核在大约 47 秒内进行约 1760 亿次比较。使用 4 门将代码进行多线程处理的速度仅提高了 70%。

    仅当 int 值的范围非常小时,使用 BitSet 才有效。否则你必须比较 int[] (如果你需要的话,我已经把代码留在里面了)

    在 47.712 秒内进行了 176,467,034,428 次比较,找到了 444,888 个匹配项

    public static void main(String... args) throws InterruptedException {
        int length = 100;
        int[][] ints = generateArrays(50000, length);
        final BitSet[] bitSets = new BitSet[ints.length];
        for(int i=0;i<ints.length;i++) {
            int[] ia = ints[i];
            BitSet bs = new BitSet(ia[ia.length-1]);
            for (int i1 : ia)
                bs.set(i1);
            bitSets[i] = bs;
        }
    
        final AtomicInteger matches = new AtomicInteger();
        final AtomicLong comparisons = new AtomicLong();
        int nThreads = Runtime.getRuntime().availableProcessors();
        ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
    
        long start = System.nanoTime();
        for (int i = 0; i < bitSets.length - 1; i++) {
            final int finalI = i;
            executorService.submit(new Runnable() {
                public void run() {
                    for (int j = finalI + 1; j < bitSets.length; j++) {
                        int compare = compare(bitSets[finalI], bitSets[j]);
                        if (compare <= 130)
                            matches.incrementAndGet();
                        comparisons.addAndGet(compare);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.HOURS);
        long time = System.nanoTime() - start;
        System.out.printf("Peformed %,d comparisons in %.3f seconds and found %,d matches %n",comparisons.longValue(),time/1e9, matches.intValue());
    }
    
    private static int[][] generateArrays(int count, int length) {
        List<Integer> rawValues = new ArrayList<Integer>(170);
        for (int i = 0; i < 170; i++)
            rawValues.add(i);
    
        int[][] ints = new int[count][length];
        Random rand = new Random(1);
        for (int[] ia : ints) {
            Collections.shuffle(rawValues, rand);
            for (int i = 0; i < ia.length; i++)
                ia[i] = (int) (int) rawValues.get(i);
            Arrays.sort(ia);
        }
        return ints;
    }
    
    private static int compare(int[] ia, int[] ja) {
        int count = 0;
        int i=0,j=0;
        while(i<ia.length && j<ja.length) {
            int iv = ia[i];
            int jv = ja[j];
            if (iv < jv) {
                i++;
            } else if (iv > jv) {
                j++;
            } else {
                count++; // duplicate
                i++;
                j++;
            }
        }
        return ia.length + ja.length - count;
    }
    private static int compare(BitSet ia, BitSet ja) {
        BitSet both = new BitSet(Math.max(ia.length(), ja.length()));
        both.or(ia);
        both.or(ja);
        return both.cardinality();
    }
    

    【讨论】:

    • 这可能会加快比较(O(1) 因为数组大小相同),但我认为,性能杀手是大量比较本身(O(n^2) 如果我明白了n &gt;&gt; 1,000,000)
    • 是的,n^2 比较是这里的问题。这将分布在不止一台机器上。现在,我正试图找出最好的比较方法。我很确定用一台机器不可能实现我想要的。
    • @ashish,只要比较便宜,您可以在一秒钟内完成 100 万次比较。你要比较多少个数组?
    • 一个 3 GHz 处理器的内核每秒可以进行 10 亿次 int 比较。 ;) esp 顺序读取数组,以便有效地将数据加载到缓存中。
    • @Peter Lawrey 你是对的。在 mycase 中,BitSet 在这里没有用,因为 int 的范围无处不在。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-02
    • 2012-03-19
    • 1970-01-01
    • 2015-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多