【问题标题】:Better algorithm for finding a "fit" of bit pattern in the bit string在位串中找到“适合”位模式的更好算法
【发布时间】:2019-11-17 03:23:14
【问题描述】:

我面临以下问题:给定一个相对较短的位模式,我想在一个相当长的位字符串中找到它“适合”的位置。我的意思是,对于按模式设置的每一位,目标位字符串的相应位置都有一个零。

例如,101101 的模式可以适合以下字符串:

  • ...00000000.... - “微不足道” 适合
  • ...10100101.... - “完全”匹配
  • ...00000100.... - 另一个有用的搭配

等等。

“适合”的质量无关紧要,按位顺序排列的第一个好就行了。

显然,一个简单的算法会起作用 - 通过迭代目标位字符串中的每个 0,我可以检查一个模式是否适合从该位置开始(调整模式中设置的前导位)。

但是,正如我们所知,更专业的搜索算法比简单的实现实现了巨大的加速。显然,“精确位模式搜索”算法不适用于这个问题,但考虑到收益,精确模式搜索算法提供的幼稚实现,我正在寻找是否有人已经投入了一些好的工作来设计更好的算法对于所描述的模式拟合问题。

【问题讨论】:

  • 模式不是真的0x00x0,其中每个0 必须匹配字符串中的0,并且每个x 匹配任何字符。如果是这样,可以应用KMP algorithm 的概念来实现性能的微小改进。我说“次要”,因为从发现不匹配中获得的信息很少。
  • 它看起来确实像一个基本的通配符搜索(一位通配符)。但是,KMP 真的适用于这个问题吗?我见过的大多数通配符搜索算法都只使用嵌套循环/递归。
  • 我认为您的问题可能更适合cs.stackexchange.com,因为您正在寻找一种理论方法及其时间复杂度,而不仅仅是一些工作代码。
  • 我想知道您是否提出了您要解决的真正问题。具体来说,“相对短”“合理长”的准确定义是什么?输入的格式是什么——换句话说,假设算法是作为函数实现的,那么函数的参数会是什么样子?最重要的是,完整的位串是否已经存储在内存中,还是从通信接口实时接收的?
  • @user3386109 该模式通常是长度很少的机器字,100-300 位。位串完全存储在内存中,被认为是“无限的”,但大约为几兆位。

标签: algorithm bit-manipulation


【解决方案1】:

这取决于您要在编写算法时投入多少工作,但您所描述的可以使用正则表达式来解决;位模式101101 对应于正则表达式0[01]00[01]0

显然,将位字符串转换为实际字符串,然后在其上使用实际的正则表达式会很慢。但是您可以使用正则表达式背后的想法来开发一种直接作用于位的算法。关键思想是使用该模式来构建deterministic finite automaton。 DFA 是一种状态机,它使用位串的位,根据当前位从一种状态转换到另一种状态。当它达到“接受”状态时,表示在当前位结束的位置找到了匹配项。

将模式转换为状态机的最简单方法是使用Thompson's construction 创建NFA,然后使用powerset construction 将NFA 转换为DFA。这将花费相当多的精力来编写代码,但生成的状态机应该能够测试位串是否匹配,并且如果仔细实施应该会非常有效。

与简单的 O(nk) 算法相比,测试位串的复杂度为 O(n),其中 n 是位串的长度,k 是模式的长度。假设 k 小而 n 大,那么将模式转换为 DFA 所需的时间应该可以忽略不计。

【讨论】:

  • 我也想过这个问题。然而,在实际机器(缓存和所有)上,DFA 是否会比线性搜索更快并不明显,虽然实现它需要做很多工作。因此,这个问题的真正意图是找出是否有人已经完成了研究和基准测试。例如,Thierry Lecroq 研究了精确的位串匹配,并得到了一些有趣的结果(他研究了与匹配相关的大多数事情,但可惜,似乎不是我现在正在研究的那种)。
  • 您可以使用单个数组实现状态机,其中状态编号为 0、2、4、6...,每个状态占用数组中的两个索引,用于在 0 上的转换位,以及它在 1 位上的转换。循环体只是state = transitions[state | bit];,它是一个按位或一个数组访问和一个赋值。如果将状态 0 设置为“接受”状态,则循环条件为 i < n && state != 0,然后在循环之后,当且仅当 state == 0 时,匹配结束于索引 i(不包括)。对于短模式,整个数组应该很容易适合 L1 缓存。
  • 模式适应度的测试只需要很少的机器指令(xor-and)。逐字线性扫描位串对于机器来说也是非常可预测的。分配和填充一个侧阵列,然后来回跳跃 - 不是那么明显。我不反对 DFA 方法,但谨慎的方法是用简单的方法实现它,然后对应用程序进行大量基准测试,然后可能尝试 DFA(这肯定需要一些时间)。
  • 是的,您需要对其进行基准测试才能确定。特别是如果模式永远不会超过一个机器字,那么您不需要嵌套循环来实现“朴素”算法。我要补充一点,您不需要实现整个 regex-to-DFA 算法来进行基准测试;如果您可以使用笔和纸为简单的测试用例设计 DFA 并手动导出转换数组,那么您可以对其进行基准测试。
  • 它更长,更像是 4 - 5 个机器词(参见“问题”cmets)。
【解决方案2】:

假设您检查比特流的最低位是否匹配,您已将它们的副本放入lowbits

首先执行位异或操作:

lowbits = lowbits ^ pattern;

这样,应该为零的位都变成了1,我们不关心的位不变。

然后进行位与运算:

lowbits = lowbits & pattern;

不计数的位(模式为 0)被清除。

当且仅当:

lowbits == pattern

如果没有匹配,只需将比特流向右移动并继续。

如果模式适合硬件寄存器,这将变得非常有效。否则,必须将模式分解为硬件寄存器大小的块,并在每个块上重复操作,直到失败。

可能有加速比特流移位的策略(例如,对于大小为 p 的模式分为大小为 r 的寄存器的 n 块,那么您可以每n*r-p+1 步从流中获取新位,您可以在函数开始时将模式向左移动一次 n*r-p+1 并将这些 n*r-p+1 案例保留在内存中。

【讨论】:

  • “可能有加速的策略” - 这个问题是关于什么的。
  • @oakad 当然。预先计算左移模式的副本是我提出的策略。如果你足够聪明,你可以完全省略比特流的位移例如,调整块的数量 n 以保持精确的 r 左移模式,这样比特流可以前进 r 位,这只是意味着进一步推进一个 r 位字。这将过于依赖机器,也取决于指令集,所以我不会进一步描述。
  • 我的意思是你必须自己尝试和测量。
猜你喜欢
  • 1970-01-01
  • 2016-07-19
  • 2016-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-27
  • 2012-03-24
  • 1970-01-01
相关资源
最近更新 更多