【问题标题】:Tokenize valid words from a long string对长字符串中的有效单词进行标记
【发布时间】:2011-04-03 00:13:14
【问题描述】:

假设您有一本包含有效单词的字典。

给定一个删除所有空格的输入字符串,确定该字符串是否由有效单词组成。

您可以假设字典是提供 O(1) 查找的哈希表。

Some examples:

helloworld-> hello world (valid)
isitniceinhere-> is it nice in here (valid)
zxyy-> invalid

如果一个字符串有多种可能的解析方式,只要返回true就足够了。

字符串可以很长。因此,请考虑一种既节省空间又节省时间的算法。

【问题讨论】:

  • 这可以通过动态规划在二次时间内完成,参见here
  • 这对于给定的字典数据结构是最佳的,因为我们可以有子字符串 [0..i] 用于 i=1..N/2-1 匹配,以及子字符串 [i..N -1] 对于 i=N/2..N-2 匹配。
  • 啊,有趣。我认为可能有一种方法可以通过以某种方式避免重新计算(考虑到 CYK 和所有的存在)来获得比我直接递归方法更好的最坏情况界限,但无法完全弄清楚如何做到这一点。

标签: algorithm string parsing


【解决方案1】:

我会选择一种带有隐式回溯的递归算法。函数签名:f: input -> resultinput 是字符串,resulttruefalse,这取决于整个字符串是否可以正确标记。

像这样工作:

  1. 如果input是空字符串,返回true
  2. 查看input 的长度为一的前缀(即第一个字符)。如果它在字典中,则在input 的后缀上运行f。如果返回 true,则也返回 true
  3. 如果上一步的长度为一的前缀不在字典中,或者上一步中f的调用返回false,则将前缀加一并在步骤2重复。如果前缀不能再做(已经在字符串的末尾),返回false
  4. 冲洗并重复。

对于具有低到中等数量的歧义前缀的字典,这应该在实践中获取相当不错的运行时间(我会说平均情况下为 O(n)),尽管在理论上,可能可以构建复杂度为 O(2^n) 的病理案例。但是,我怀疑我们是否可以做得更好,因为无论如何我们都需要回溯,因此使用传统的预先计算的词法分析器的“本能” O(n) 方法是不可能的。 ...我想。

编辑:平均案例复杂度的估计可能不正确,请参阅我的评论。

空间复杂度只是堆栈空间,所以即使在最坏的情况下也是 O(n)。

【讨论】:

  • 你能澄清一下“平均情况 O(n)”的说法吗?
  • 嗯,仔细想想,平均 O(n) 可能是我的误判。例如,如果算法在字典中看到前缀“a”(假设一分钟的英文单词字典),它会首先尝试标记剩余的输入......可能会添加 entire在决定不能对其进行标记并恢复为扩展“a”之前,逐个字符地添加后缀。看起来更像是多项式。考虑到 Cocke-Younger-Kasami 也有类似 O(n^3) 的东西......那么好问题。
  • 在最坏的情况下,算法将尝试每个可能的文本跨度来证明它是否是一个有效的单词。有 n^2 个可能的文本跨度,因此最坏的情况是 O(n^2)。
  • 最坏的情况是 O(2^N),因为我们有 T(0) = O(1), T(N) = sum(T[i], 0..N-1 )。
  • @Nabb:仔细的实施不会呈指数级增长。该算法不断询问第二项:剩余的子字符串是否满足该属性?如果之前我们已经尝试过这个子字符串,请不要再次计算它。总共有 n^2 个子字符串,因此在最坏的情况下 O(n^2)。
【解决方案2】:

我认为作为有效单词(从有限字典中提取的单词)的串联出现的所有字符串的集合形成了字符字母表上的常规语言。然后,您可以构建一个有限自动机,它完全接受您想要的字符串;计算时间为 O(n)。

例如,让字典由单词 {bat, bag} 组成。然后我们构造以下自动机:状态用 0、1、2 表示。边:(0,1,b), (1,2,a), (2,0,t), (2,0,g) ;其中三元组 (x,y,z) 表示输入 z 上从 x 到 y 的边。唯一接受的状态是 0。在每一步中,在读取下一个输入符号时,您必须计算在该输入上可到达的状态集。鉴于自动机中的状态数是恒定的,这具有 O(n) 的复杂度。至于空间复杂度,我认为你可以用 O(number of words) 来做上面的构造提示。

再举一个例子,带有 {bag, bat, bun, but} 的自动机看起来像这样:

假设已经构建了自动机(执行此操作的时间与单词的长度和数量有关 :-) 我们现在认为决定自动机是否接受字符串的时间是 O( n) 其中 n 是输入字符串的长度。 更正式地说,我们的算法如下:

  1. 令 S 为一组状态,最初包含起始状态。
  2. 读取下一个输入字符,我们用a来表示。
  3. 对于 S 中的每个元素 s,确定我们在读取 a 时从 s 移动到的状态;也就是说,状态 r 使得上面的符号 (s,r,a) 是一条边。让我们用 R 来表示这些状态的集合。也就是说,R = {r | S中的s,(s,r,a)是一条边}。
  4. (如果 R 为空,则不接受字符串,算法停止。)
  5. 如果没有更多的输入符号,检查是否有任何接受状态在 R。(在我们的例子中,只有一个接受状态,即起始状态。)如果是,则接受字符串,如果不是,该字符串不被接受。
  6. 否则,取 S := R 并转到 2。

现在,这个循环的执行次数与输入符号的次数一样多。我们唯一需要检查的是步骤 3 和 5 需要恒定的时间。假设 S 和 R 的大小不大于自动机中的状态数,它是恒定的,并且我们可以以查找时间恒定的方式存储边,因此如下。 (请注意,我们当然会丢失多个“解析”,但这也不是必需的。) 我认为这实际上称为常规语言的成员资格问题,但我找不到合适的在线参考。

【讨论】:

  • 如果我们有 n 个有效单词。通过链接单词(不同或相同)可以有多少可能的自动机?我猜是 O(n!)。空间复杂度无法接受。
  • 没有必要“串起来”,我会尝试编辑我的答案:-)
  • 字符链。好的。你能证明 O(n) 的时间成本是合理的吗?在 O(n) 中找到图中的完整路径。
  • @SiLent SoNG:我在回答中添加了一些解释。
  • 这个答案是非常错误的,并且具有误导性。相当于贪心算法,不能解决问题。例如,如果您的字典中有单词“the”、“there”和“is”,那么您必须同时接受字符串“theis”和“thereis”,但您的解决方案无法做到。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-23
  • 2011-06-01
  • 1970-01-01
  • 2021-05-23
  • 2012-09-05
  • 1970-01-01
相关资源
最近更新 更多