找到数组中两个数字之间的最小绝对差值的最佳算法

时间:2012-09-02 06:36:05

标签: algorithm perl sorting binary-search

有一个数组可以包含最多1000个元素。它可以产生的数字范围是1 to 10^10。现在我必须找到数组中两个数字之间的minimal absolute difference。我想到了两种算法:

对于第一个,我定义了一个binarysearch函数,它在已排序的数组中查找待插入数字的位置。现在,我只使用给定数组的第一个数字启动排序数组,然后从第二个元素开始迭代给定数组。对于每个数字,我在排序数组中找到它的位置。如果该位置的数字是这个数字,那么差值为0,它是最低的数字,所以我退出循环。否则,我在该点插入已排序数组中的数字,然后检查该数字与该数组中前一个和下一个数字之间的差异。然后我存储此结果的最小值和先前的结果,并以这种方式继续。

第二:我使用quicksort 对数组进行排序。 (范围太大,所以我认为基数排序不会那么高效)。然后我迭代它,如果两个连续的数字相等则以0的答案突破,否则存储该数字与前一个数字和先前结果之间的差异的最小值。

哪一个更有效率?

有没有更好的算法?

Stackoverflow在这方面有很多帖子,但它们没有多大帮助。这是我在Perl中的代码:

sub position {
    my @list   = @{$_[0]};
    my $target = $_[1];

    my ($low,$high) = (0, (scalar @list)-1);

    while ($low <= $high) {
        $mid = int(($high + $low)/2);

        if ( $list[$mid] == $target ) {

            return $mid;
        }
        elsif ( $target < $list[$mid] ) {

            $high = $mid - 1; 
        }
        else {

            $low = $mid + 1;
        }
    }
    $low;
}
sub max { $_[0] > $_[1] ? $_[0] : $_[1]; }
sub min { $_[0] > $_[1] ? $_[1] : $_[0]; }

$ans        = 10_000_000_000;
@numbers    = (234, 56, 1, 34...123); #given array
($max,$min) = @num[0, 0];
@sorted     = ($numbers[0]);

for ( @num[1 .. $#num] ) {
    $pos = position(\@sorted, $_);

    if ( $sorted[$pos] == $_ ) { 

        $ans = 0;
        last;
    }
    splice @sorted, $pos, 0, $_;

    if ( $#sorted == $pos ) { 

        $ans = min($_-$sorted[-2], $ans);
    }
    elsif ( 0 == $pos ) {

        $ans = min($sorted[1]-$_, $ans);
    }
    else { 

        $ans = min(min(abs($sorted[$pos-1]-$_), abs($sorted[$pos+1]-$_)), $ans);
    }
    $max = max($_, $max);
    $min = min($_, $min);
}
print "$ans\n";

7 个答案:

答案 0 :(得分:16)

你有多达5k个元素。

请注意,sandy bridge处理器具有32KB L1-Cache,假设整数为4个字节 - 这意味着它可以包含8192个整数。

尽量避免创建额外的数据(除了计数器等),并使用相同的数组执行所有操作。这将使cache-misses的数量非常小,并且可能会超过任何算法。

因此,对于数组中的元素,就地快速排序和迭代可能会比任何其他解决方案更好,既可以提高缓存效率,又可以保持{的渐近复杂度{ {1}}。

注意 - 虽然这个解决方案可能可能更有效率(至少在理论上),但规模仍然非常小 - 除非你要做很多次这样的操作 - 它只是没有不值得你的时间过度优化它。


一般提示:当谈到小规模问题(并且最多5000个元素符合此标准)时,大O符号通常是不够的。缓存性能通常是这些问题的主导因素。

答案 1 :(得分:11)

这是一维中的closest pair problem。请注意,解决此问题至少与解决element uniqueness problem一样困难,因为如果有任何重复元素,则答案为0.

元素唯一性问题需要O(n lg n)时间来解决,所以这个问题也必须至少那么难。由于您提出的迭代排序解决方案是O(n lg n),因此没有更好的渐近最坏情况算法。

然而,如维基文章中所述,有些算法的最坏情况运行时间更差,但线性预期运行时间更短。一种这样的方法被描述为in this article,它似乎相当复杂!

答案 2 :(得分:5)

第二个会更快,原因很简单,第一个解决方案是你在Perl-space中使用你自己编写的那种,而第二个解决方案你有机会使用内置的Perl sort这是一个C函数并且非常快。有了这么小的投入,第一个人几乎不可能获胜,即使它有可能减少工作量。

答案 3 :(得分:2)

第二种算法可能更好。在第一个算法中,您使用的是插入排序,它的效率低于其他一些排序算法。

答案 4 :(得分:1)

因为我们正在谈论Perl,所以我们不应该想太多关于最有效的排序算法 - 在Perl中实现自己的东西必然比使用内置函数慢。为了好玩,我运行了这个小脚本(为了清晰起见):

time perl -e'
    @array = map {rand} 1..100000;
    $lastdiff=10**11;
    for(sort {$a <=> $b} @array){
        unless(defined $last){
            $last=$_;
            next
        }
        $difference = abs($last - $_);
        $last = $_;
        $lastdiff = $lastdiff < $difference ? $lastdiff : $difference;
        last if $lastdiff == 0;
    }
    print $lastdiff, "\n"
'

我设置了一个包含100,000个随机数的数组。这个脚本在0.42秒内终止(在我的慢速笔记本电脑上)。考虑到我使用~0.12秒进行启动和数组初始化,主算法使用大约0.3秒。假设 O(n)你应该完成&lt; 0.02秒......哦,等等,这并不多......(5000元)

如果您需要更快,请使用Inline :: C编写算法。

答案 5 :(得分:1)

A simple randomized sieve algorithm for the closest-pair problem描述了一个针对最近对的问题的O(n)随机算法,它还引用了另一篇论文,该论文给出了一维最近对问题的O(n log log n)确定性算法,如果你可以访问floor功能。

答案 6 :(得分:0)

  1. 将数组的数字复制到黑红树
  2. 这将允许您测试log(n)
  3. 在特定时间间隔内是否存在数字
  4. 设置d = inf
  5. 使用变量i
  6. 循环数组
  7. 如果在区间(i-d,i + d)中有一个数字,则设置d等于| i-thatNumber |
  8. 它需要你~n * ln才能找到d