【问题标题】:binary search in an array in PerlPerl中数组的二分查找
【发布时间】:2011-06-08 08:53:22
【问题描述】:

我有一个十六进制数字数组,我需要检查其他数字并检查它们是否出现在该数组中。现在我正在使用一个foreach 循环,每次都会遍历整个数组。有没有办法通过先对数组进行排序,然后对其进行二分查找来加快速度。

此刻的代码:

sub is_bad_str{
  my ($str, @keys) = @_;
  my $flag = 0;
  my ($key, $hex_num);
        if ($str =~ m/14'h([0-9a-f][0-9a-f][0-9a-f][0-9a-f])/;){ #'# fixes bad highlighting
  $hex_num = $1;
      }
  if (defined $hex_num){
    foreach $key (@keys){
        if ($hex_num =~ /\Q$key\E/i){
            $flag = 1;
            last;
        }
    }
  }
  if (($flag == 0) && (defined $hex_num)){
    return 1;#Bad str
  }else{
    return 0;#Good str
      }
}

【问题讨论】:

  • 你有一个非常微妙的错误。匹配变量$1not 重置的,因此一旦定义,它将保持定义,无论是否存在正则表达式匹配。您应该检查是否定义了x=~y,以确定是否存在匹配
  • 这是作业吗?如果是这样,那是一回事......如果不是,您应该使用 CPAN 模块来执行此操作。
  • 这不是家庭作业。具体是什么型号?我查看了模型列表,那里似乎没有二进制搜索模型。
  • 我做了 Dancrumb 建议的编辑,虽然它正在等待批准。在这篇文章中,我详细说明了为什么它是必要的(我在我需要的一些代码中找到了它)stackoverflow.com/q/4045467/468327

标签: perl binary-search


【解决方案1】:

如果您只进行一次搜索,那么排序将比执行单次线性扫描花费更长的时间,因此您最好坚持循环遍历数组。对于一个小数组,或者如果您可以有多个匹配项,您可能还想查看grep 函数;它使用起来更容易一些,但它总是会检查整个候选匹配列表,而不是在找到匹配时停止。

如果您要进行多次搜索,将数组的值放入哈希中并进行哈希查找将比搜索数组更快,即使您对其进行排序并进行二进制搜索(假设您负担得起内存成本,但几乎可以肯定)。

【讨论】:

    【解决方案2】:

    有四种策略可以在 Perl 中对一组数据进行高效的批量搜索。

    下面概述了完整的分析,但总而言之,具有大量搜索的平均随机数据集的最佳性能当然是哈希查找提供的,其次是更糟糕的 BST。


    1. 数组的二进制(半间隔)搜索。

      这显然是一种标准的算法方法。

      性能成本:

      • O(N * log N) 用于初始排序。
      • O(N) 平均用于在排序后在列表中插入/删除数据。 Perl 数组不是链表,所以它不是 O(log N)
      • O(log N) 用于每次搜索。

      实现the algorithm 非常简单,DIY 也很简单。像往常一样,CPAN 模块存在并且可能应该被用来代替 DIY:Search::Binary


    2. Binary Search Trees(BST)

      性能成本:

      • O(N * log N) 用于初始排序。
      • O(log N) 平均用于在排序后在列表中插入/删除数据
      • O(log N) 用于每次搜索。


      实现:CPAN 上有几种风格:Tree::Binary::SearchTree::TreapTree::RedBlack。后两个have better average performance and smaller performance fluctuations, algorithmically

      比较:如果数据会发生变化,您必须使用 BST 以避免重新排序成本。如果您的数据是随机的并且一旦排序就永远不会改变,您可以在 BST 上使用简单的二分搜索,但如果每一盎司的性能都很重要,则可以更好地调整 BST(如果您知道查找,可以优化 BST 以实现比列表二分搜索更快的平均搜索基于数据分布的成本 - 请参阅 Wiki's "Optimal binary search trees" section 或者如果您的数据分布有利于一种特殊的树,例如 Treap 或 Red/Black)。


    3. 缩写(短路)扫描查找。

      这些是对未排序列表的线性扫描搜索,一旦找到该项目就会停止搜索。

      性能O(N) 每次搜索随机数据,但比 @987654329 之类的完整列表搜索更快 O(N)(例如,N/2) @。无需额外费用。

      实现:在 Perl 中有 3 种方法可以实现:

      • Smart match 操作员 (~~)。问题是它仅在 Perl 5.10 及更高版本中可用。
      • 您自己的循环,一旦找到就会执行next;
      • List::MoreUtils 模块的first() 子例程。

      比较

      • 首先,在上面的 3 个实现中,List::MoreUtils::first 比 DIY 循环更快,因为它是在 XS 中实现的;所以它应该在 5.10 之前的 Perl 版本中使用。智能匹配可能同样快,尽管我会在您在 Perl 5.10+ 中选择其中一个之前对两者进行基准测试。

      • 其次,将短路搜索与其他方法进行比较,应该使用它的只有 3 个边缘情况:

        A. 内存限制。 排序列表搜索、BST 和哈希查找的内存占用至少为2*N。如果您面临严重的内存限制(给定您的列表大小)以至于N2*N 内存成为不可协商的成本障碍,那么您使用短路搜索并及时支付性能损失。 当您批量/逐行处理大型数据集时尤其如此,以避免首先将整个数据存储在内存中。

        B.如果您的数据以这样一种方式分布和预排序,那么绝大多数搜索会在列表的最开头找到他们的猎物。如果是这种情况,它可能会胜过像 BST 之类的更好的方法,尽管它们明显更快的 O(log N) 平均搜索。仍然很难超越哈希查找,但稍后会详细介绍。

        C.如果执行的搜索数量与列表大小相比相当小,则短路搜索优于 BST 或排序列表搜索,在这种情况下,前两种方法 (O(N log N)) 的初始排序成本将超过搜索节省。由于 BST 与线性搜索相比节省了O(M * N),其中 M 是搜索次数,因此搜索次数 M 必须小于 O(log N) 才能实现平均节省,但第二次可能更多由于数据分布,平均扫描成本低于O(N) 的边缘情况。


    4. 哈希查找

      性能成本:

      • O(N) + epsilon 用于初始哈希创建(严格来说,对于随机大数据集而言,由于可能的密钥冲突,它不是 O(N)。我对 Perl 的哈希实现知之甚少,除了说明它可以来任何哈希映射的问题。
      • O(1) 平均用于在排序后在列表中插入/删除数据(+ 由于键冲突,与初始哈希创建相同的 epsilon)。
      • O(1) 用于每次搜索(加上相同的 epsilon)。

      实施

      my %lookup = map { $_ => 1 } @list; 
      my @lookup2{ @list } = (); # Alternative method of creating a hash
      sub find { return exists $lookup{$_[0]; }
      

      比较

      • 首先,同样的逻辑适用于比较短路搜索与散列查找与 BST 与短路搜索。例如,您应该几乎总是在线性搜索上使用哈希图,除了相同的两个边缘情况(数据集使得平均列表扫描变为 O(1) 而不是 O(N) 以及搜索数与数据集的比率size 使得搜索的总成本低于创建哈希所需的O(N))。

      • 第二,平均散列图显然比 BST 或二进制列表搜索快得多。这里唯一可能的边缘情况是,您不知何故偶然发现了一个数据集,该数据集设法使存储桶超载,并将额外的“epsilon”成本转化为足够大的权重,以至于它开始表现不佳O(log N)

        我强烈怀疑它是否有可能实现,但同样,我对 Perl 的 hashmap 实现不够了解,无法证明即使是最糟糕的数据集也不会发生这种情况。

    【讨论】:

    • 对有抱负的编辑类徽章有抱负的人的注意 - 随意添加指向 CPAN 模块的链接。
    • 发疯了:上述链接由我的编辑添加,目前正在等待同行评审。感谢您提供如此详细且经过充分研究的答案。
    猜你喜欢
    • 2010-09-19
    • 2023-03-12
    • 1970-01-01
    • 2011-12-25
    相关资源
    最近更新 更多