【问题标题】:Best algorithm to find the minimum absolute difference between two numbers in an array查找数组中两个数字之间的最小绝对差的最佳算法
【发布时间】:2012-09-02 06:36:05
【问题描述】:

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

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

第二:我使用快速排序对数组进行排序。 (范围太大,所以我认为基数排序不会那么有效)。然后我对其进行迭代,如果两个连续数字相等,则返回 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";

【问题讨论】:

  • 会经常发生吗?否则,1000 个元素真的没什么大不了的——而且很可能不值得你花时间优化它
  • 它最多可以包含大约 5000 个元素。
  • 仍然很小。除非它经常发生,否则任何优化都不值得您花时间
  • 要了解它有多小:sandy-bridge 处理器有 32KB L1-Cache。假设 4 字节整数,它最多可以支持 8192 个整数。如果您真的想优化解决方案 - 专注于尽可能缓存友好的解决方案,并尽可能避免创建更多数据。就地快速排序可能会胜过任何其他解决方案
  • 请使用pragma use strict;,对你有帮助!

标签: algorithm perl sorting binary-search


【解决方案1】:

您最多有 5k 个元素。

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

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

因此,对数组中的元素进行就地快速排序而不是迭代可能会比任何其他解决方案更好,既能提高缓存效率,又能保持@的渐近复杂性987654324@.

注意 - 尽管此解决方案可能会更有效(至少在理论上),但规模仍然非常小 - 除非您要多次执行此操作 - 它只是没有不值得你花时间过度优化它。


一般提示:在讨论小规模问题(最多 5000 个元素符合此标准)时,大 O 表示法通常是不够的。缓存性能通常是这些问题的主要因素。

【讨论】:

  • 当你知道问题出在 Perl 时,假设一个整数有 4 个字节有点愚蠢,不是吗?作为参考,SVIV 在 32 位系统上为 16 字节,在 64 位系统上为 24 字节;并且 AvARRAY 开销是 32 位每个 4 个字节和 64 位每个 8 个字节,对于 32 位和 32*n 个字节(加上更改)的总存储要求64 位。
  • @hobbs:感谢您的评论。我主要关注此类问题的理论方面(更多地关注算法标签而不是珍珠标签)。尽管如此,即使珍珠的确切数字不正确,仍然需要考虑。如果由于珍珠的开销而导致数据不适合 L1 - 您应该尽一切可能使其适合 L2 缓存。
  • 这个处理器的东西及其对算法的影响对我来说是全新的。您能否进一步详细说明,或者更好地指出一篇这样做的文章?
  • 所以从缓存读取比从 RAM 读取更好,就像从 RAM 读取比从存储读取更好一样?那么这是一个通用的改进,它与我们的问题有什么关系呢?
  • 假设 4 个字节的整数在问题规范说在 1 到 10^10 之间时也显得有些愚蠢。
【解决方案2】:

这是一维的closest pair problem。请注意,解决这个问题至少和解决element uniqueness problem 一样难,因为如果有任何重复的元素,那么答案就是 0。

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

然而,正如 wiki 文章中所述,有些算法的最坏情况运行时间更差,但预期运行时间呈线性。一种这样的方法被描述为in this article,看起来相当复杂!

【讨论】:

  • 使用哈希表可以获得平均 O(n)元素唯一性时间
  • 对于元素唯一性,二分查找算法会不会提供更好的性能(O(log N))?
  • @Cupidvogel 是的,搜索是O(lg n),但是,将元素插入到数组中的任意位置是O(n),因为您必须移动以下所有元素。如果您切换到同时具有O(lg n) search O(lg n) 插入的其他一些数据结构(我不知道其中一个),那么整个过程仍然是@987654332 @,与sort-and-iterate方法相同。
  • “元素唯一性问题需要O(n lg n) 时间来解决”——小心这样的陈述。 OP 说输入是一个有界整数列表,并且众所周知,在这种情况下,您可以比O(n log n)(参见Wikipedia)更快地对它们进行排序,这也使得此类数据的元素唯一性问题更快。
  • @liori 这个问题没有说整数,而是说数字,但是阅读 cmets 似乎 OP 确实意味着整数,所以很公平。然而,对于这个问题,非比较排序不会给您带来任何好处,即使对于非常大的n。如果整数列表以(0, 2^k) 为界,则该列表可以在O(kn) 中排序。但是请注意,如果n &lt; 2^klg n &lt; k,那么实际上非比较排序只有在n &gt; 2^k(给予或接受)时才会更快。不过,这里的关键是n &gt; 2^k 立即给了我们答案0,因为肯定有重复(根据 pidgin-hole 原则)。
【解决方案3】:

第二个会更快,原因很简单,第一个解决方案您使用的是您自己在 Perl 空间中编写的排序,而第二个解决方案您有机会使用 Perl 内置sort 这是一个 C 函数,速度非常快。由于投入如此之少,第一个获胜几乎是不可能的,即使它有可能做更少的工作。

【讨论】:

  • 为什么 Perl 内置在 sort 函数中会更快,因为它是用 C 编写的?是不是说如果我写一个 Perl 的sort 函数和原生的sort 完全一样,会比较慢?
  • @Cupidvogel 绝对 - 试试吧!
  • 为什么会这样?这是因为 C 需要强类型,因为在编译时消除了查找元素类型?
  • @Cupidvogel 这只是一小部分原因。在很多方面,C 排序的开销都比 Perl 版本少。由于原生类型,它将使用更少的存储空间并且对缓存更友好;由于本机类型 semantics (没有检查数组边界,没有将整数提升为浮点数,没有引用计数,而且正如你所说,不需要在运行时检查 a变量是),并且由于是本机编译代码而不是 perl 操作码,它将具有更少的控制流开销。
  • 你和@Amit 究竟是什么意思缓存?这里缓存了什么?我认为缓存在数据库数据查找中很重要,缓存可以从内存而不是存储中获取数据,从而减少昂贵的 I/O 操作。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    A simple randomized sieve algorithm for the closest-pair problem 描述了一种针对最近对问题的 O(n) 随机算法,并且它还参考了另一篇论文,如果您可以访问,该论文给出了一个针对一维最近对问题的 O(n log log n) 确定性算法到floor 函数。

    【讨论】:

      【解决方案6】:

      因为我们在谈论 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) 你应该在

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

      【讨论】:

      • 为什么会这样?这是因为 C 需要强类型,因为在编译时消除了查找元素类型?
      • @Cupidvogel 你对“this发生[ing]”到底是什么意思?Perl以性能为代价给你灵活性——Perl甚至不编译成机器码,所以每个操作都有很高的开销。用 Perl 处理数字有点像用笔刀砍树。
      【解决方案7】:
      1. 将数组的数字复制到黑红树中
      2. 这将允许您测试 log(n) 的特定间隔中是否有数字
      3. 设置 d = inf
      4. 使用变量 i 遍历数组
      5. 如果区间(i-d, i+d)中有一个数,则设置d等于|i-thatNumber|

      它会带你 ~ n*ln 找到 d

      【讨论】:

      • 我毫不怀疑将元素插入到非缓存效率的树中会比快速排序和单次迭代更好。两者都是O(nlogn),但第二个(快速排序+迭代)很可能有很多更好的常量
      • 这似乎过于复杂。对数组进行排序然后迭代一次也可以在O(n lg n)时间找到答案。
      • 第一个可以立即找到拖车号是否相同并退出。无论如何排序都会对它进行排序,如果两个相同的数字非常大以至于它们接近数组的末尾,那么迭代将花费很多时间。
      • @Cupidvogel 与排序成本相比,迭代数组实际上是免费的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-08
      • 1970-01-01
      • 2015-08-28
      • 1970-01-01
      • 2021-03-22
      • 1970-01-01
      相关资源
      最近更新 更多