数组 – 数组中的差异总和

有没有更有效的方法来实现这一目标:

给定大小为n的阵列A和两个正整数a和b,找到在所有对(i,j)上取得的总和(abs(A [i] -A [j])* a / b),其中0 <1. = i< j< ñ.

int A[n];
int a, b; // assigned some positive integer values
...
int total = 0;
for (int i = 0; i < n; i++) {
    for (int j = i+1; j < n; j++) {
        total += abs(A[i]-A[j])*a/b; // want integer division here
    }
}

为了优化这一点,我对数组进行了排序(O(nlogn)),然后没有使用abs函数.另外,我在内部for循环之前缓存了值a [i],所以我可以从A顺序读取东西.我正在考虑预先计算a / b并将其存储在一个浮点数中,但额外的转换只会让它变慢(特别是因为我想取决于结果).

我无法想出一个比O(n ^ 2)好的解决方案.

最佳答案 是的,有一个更有效的算法.它可以在O(n * log n)中完成.我不希望有一种渐近更快的方式,但我对任何证据都没有任何想法.

算法

首先在O(n * log n)时间内对数组进行排序.

现在,让我们来看看这些术语

floor((A[j]-A[i])*a/b) = floor ((A[j]*a - A[i]*a)/b)

对于0< = i< j< ñ.对于每个0 <= k <1. n,写入A [k] * a = q [k] * b r [k],其中0 <= r [k]

floor((A[j]*a - A[i]*a)/b) = floor(q[j] - q[i] + (r[j] - r[i])/b)
                           = q[j] - q[i] + floor((r[j] - r[i])/b)

每个q [k]出现k次正符号(对于i = 0,1,…,k-1)和n-1-k次带负号(对于j = k 1,k 2,…, n-1),所以它对总和的贡献是

(k - (n-1-k))*q[k] = (2*k+1-n)*q[k]

剩下的人仍然需要考虑.现在,因为0< = r [k]< b,我们有

-b < r[j] - r[i] < b

并且当r [j]> = r [i]时,(r [j] -r [i])/ b)为0,当r [j] <1时,其为-1. [R [i]中.所以

                            n-1
 ∑ floor((A[j]-A[i])*a/b) =  ∑ (2*k+1-n)*q[k] - inversions(r)
i<j                         k=0

其中,反转是一对(i,j)的索引,其中0 <= i <1. j< n和r [j]< [R [i]中. 计算q [k]和r [k]并将(2 * k 1-n)* q [k]求和在O(n)时间内完成. 仍然有效地计算r [k]阵列的反转. 对于每个索引0< = k< n,令c(k)为i的数量< k使得r [k] < r [i],即k表示较大指数的反转次数. 那么反转的数量显然是Σc(k). 另一方面,c(k)是以稳定的种类在r [k]后面移动的元素的数量(这里稳定性很重要). 计算这些移动,因此在合并排序时很容易进行数组的反转. 因此,反转也可以用O(n * log n)计算,给出O(n * log n)的总体复杂度. 码 一个简单的不科学基准的示例实现(但是天真的二次算法和上面的差异之间的差异是如此之大,以至于一个不科学的基准是足够的结论).

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

long long mergesort(int *arr, unsigned elems);
long long merge(int *arr, unsigned elems, int *scratch);
long long nosort(int *arr, unsigned elems, long long a, long long b);
long long withsort(int *arr, unsigned elems, long long a, long long b);

int main(int argc, char *argv[]) {
    unsigned count = (argc > 1) ? strtoul(argv[1],NULL,0) : 1000;
    srand(time(NULL)+count);
    long long a, b;
    b = 1000 + 9000.0*rand()/(RAND_MAX+1.0);
    a = b/3 + (b-b/3)*1.0*rand()/(RAND_MAX + 1.0);
    int *arr1, *arr2;
    arr1 = malloc(count*sizeof *arr1);
    arr2 = malloc(count*sizeof *arr2);
    if (!arr1 || !arr2) {
        fprintf(stderr,"Allocation failed\n");
        exit(EXIT_FAILURE);
    }
    unsigned i;
    for(i = 0; i < count; ++i) {
        arr1[i] = 20000.0*rand()/(RAND_MAX + 1.0) - 2000;
    }
    for(i = 0; i < count; ++i) {
        arr2[i] = arr1[i];
    }
    long long res1, res2;
    double start = clock();
    res1 = nosort(arr1,count,a,b);
    double stop = clock();
    printf("Naive:   %lld in %.3fs\n",res1,(stop-start)/CLOCKS_PER_SEC);
    start = clock();
    res2 = withsort(arr2,count,a,b);
    stop = clock();
    printf("Sorting: %lld in %.3fs\n",res2,(stop-start)/CLOCKS_PER_SEC);
    return EXIT_SUCCESS;
}

long long nosort(int *arr, unsigned elems, long long a, long long b) {
    long long total = 0;
    unsigned i, j;
    long long m;
    for(i = 0; i < elems-1; ++i) {
        m = arr[i];
        for(j = i+1; j < elems; ++j) {
            long long d = (arr[j] < m) ? (m-arr[j]) : (arr[j]-m);
            total += (d*a)/b;
        }
    }
    return total;
}

long long withsort(int *arr, unsigned elems, long long a, long long b) {
    long long total = 0;
    unsigned i;
    mergesort(arr,elems);
    for(i = 0; i < elems; ++i) {
        long long q, r;
        q = (arr[i]*a)/b;
        r = (arr[i]*a)%b;
        if (r < 0) {
            r += b;
            q -= 1;
        }
        total += (2*i+1LL-elems)*q;
        arr[i] = (int)r;
    }
    total -= mergesort(arr,elems);
    return total;
}

long long mergesort(int *arr, unsigned elems) {
    if (elems < 2) return 0;
    int *scratch = malloc((elems + 1)/2*sizeof *scratch);
    if (!scratch) {
        fprintf(stderr,"Alloc failure\n");
        exit(EXIT_FAILURE);
    }
    return merge(arr, elems, scratch);
}

long long merge(int *arr, unsigned elems, int *scratch) {
    if (elems < 2) return 0;
    unsigned left = (elems + 1)/2, right = elems-left, i, j, k;
    long long inversions = 0;
    inversions += merge(arr, left, scratch);
    inversions += merge(arr+left,right,scratch);
    if (arr[left] < arr[left-1]) {
        for(i = 0; i < left; ++i) {
            scratch[i] = arr[i];
        }
        i = 0; j = 0; k = 0;
        int *lptr = scratch, *rptr = arr+left;
        while(i < left && j < right) {
            if (rptr[j] < lptr[i]) {
                arr[k++] = rptr[j++];
                inversions += (left-i);
            } else {
                arr[k++] = lptr[i++];
            }
        }
        while(i < left) arr[k++] = lptr[i++];
    }
    return inversions;
}
点赞