C++ - 如何有效地找出向量中的任何字符串是不是可以由一组字母组合而成

Posted

技术标签:

【中文标题】C++ - 如何有效地找出向量中的任何字符串是不是可以由一组字母组合而成【英文标题】:C++ - How to efficiently find out if any string in a vector can be assembled from a set of lettersC++ - 如何有效地找出向量中的任何字符串是否可以由一组字母组合而成 【发布时间】:2010-05-14 16:39:26 【问题描述】:

我正在为一个大学项目实施基于文本的 Scrabble 版本。

我有一个包含大约 400K 字符串的向量(我的字典),而且,在每一个转折点的某个时刻,我都必须检查 字典中是否还有一个可以用玩家手中的棋子。我正在检查玩家是否有任何动作......如果没有,那么有问题的玩家游戏结束......

对此我唯一的解决方案是逐个遍历字符串,并使用子例程检查是否可以从玩家的棋子中形成有问题的字符串。如果用户有任何元音,我将实施快速故障检查,但它仍然会非常低效。

包含字典的文本文件已经按字母顺序排序,因此向量已排序。

有什么建议吗?


在下面的 cmets 中出现了一个问题:关于如何考虑已在板上的字母有什么建议吗?

【问题讨论】:

所以问题不是真正的“如何有效地迭代向量”,而是“如何有效地找出集合中的任何单词是否可以从一组字母中组装出来”? 您在问题描述中似乎没有考虑到单词可以根据棋盘和玩家的手组成。 哦。我没有考虑到这一点。伟大的,但更多的复杂性添加到已经(就我的知识水平)复杂的问题 【参考方案1】:

在不给您任何具体代码的情况下(因为这毕竟是家庭作业),一种通用的考虑方法是将单词中的排序字母映射到实际的合法单词。

也就是说,如果您的字典文件中只有单词apegummug,那么您的数据结构将如下所示:

aep -> ape
gmu -> gum, mug

然后您可以简单地检查玩家字母的排列,并快速确定该键是否存在于地图中。

您需要在启动时花费一点处理时间来设置字典,但随后您只需执行一些快速查找,而不是每次都遍历整个列表。

【讨论】:

这正是我正在打字的过程。 这就是 Jon Bentley 在“Programming Pearls”中描述他的字谜检测/创建算法的方式。这也是错误的:它只会识别可以用 all 玩家的字母产生的单词。 @jemfinch:你说得对,这不允许单次查找来确定来自玩家字母的所有子集的所有字谜,但我确实在我的回答中指定你需要执行多个查找。 “那么你可以简单地通过玩家字母的排列” - 对不起,我是初学者,我将如何生成这些排列? @Mark 我想念你会做多次查找。那么问题是您仍然需要进行 127 次查找;效率不高。【参考方案2】:

听起来像是子集和问题的变体:http://en.wikipedia.org/wiki/Subset_sum_problem

也许一些描述的算法会对你有所帮助。

【讨论】:

【参考方案3】:

这个网站上有很多关于拼字游戏的论文和问题。

还有很多可用的策略。

您的字典的表示不充分,有很多聪明的方法可用。例如,查看 wikipedia 上的 Trie 是什么。

使用它,您可以实现回溯算法来快速确定您可以组成哪些单词。

'as', 'ape', 'gum'

Trie:

void -a-> (n) -p-> (n) -e-> (y)
              -s-> (y)
     -g-> (n) -u-> (n) -m-> (y)

其中 'n' 表示它不构成一个单词,而 y 表示它构成。

现在,您只需在 Trie 中走一走,记住可用的字母。

假设你有 'a', 'p', 'g', 'm', 'u':

 1. I have a 'a' (but 'a' is not a word)
 2. I have a 'p' (but 'ap' is not a word)
 3. I don't have any 'e' so I can't go further, let's backtrack
 4. I don't have any 's' so...
 5. I have a 'g', but it's not a word
 6. I have a 'u', but 'gu' is not a word
 7. I have a 'm' and 'gum' is a word, I store it somewhere, I can't go further

关键是要维护一组可用字母,当您采用 -a-> 分支时,您会从该集合中删除 'a',然后当您采用 -a-> 反向(回溯时)时添加它回到了集合中。

这种结构更节省空间,它实际上模拟了一个有限自动机,可以识别字典的语言,而不是盲目地保存所有单词 运行时也应该更快,因为您永远不会深入树结构(您只有 7 个字母可用) 这当然不是我会做的,因为它没有考虑到董事会:p

' ' 字母表示你可以选择任何可用的分支。如果您有所需的字母,则无需使用空格。

【讨论】:

感谢您的详尽回答。在我的项目开始时,我想到了一个 Trie,但我想避免实现如此复杂的数据结构。我在网上找到了一个很好的基数树实现,并从我的导师那里得到了一个“完全清楚”的使用它。你认为那会削减它吗? 基数树是一种“节省空间”的树,原理是一样的,所以它肯定会起作用。但是,您的逻辑的主要问题是:仅尝试用您拥有的字母组成单词是不够的;)如果您想要更多线索,请尝试在 SO 上搜索拼字游戏。【参考方案4】:

您还可以将具有按 ASCII 顺序排序的字符的字符串存储到 std::set 中,然后将玩家的字母排序为相同的顺序,并在地图中搜索玩家字母的每个子字符串。

【讨论】:

【参考方案5】:

如何保留对字典中的单词,由相同字母组成但按升序排列的字符串(排序)

然后根据第二个字符串对这些对的向量进行排序,并使用二进制搜索与由玩家手中的排序字母组成的字符串进行比较。

【讨论】:

【参考方案6】:

这里已经有一些很好的答案,我认为尝试尝试可能是正确的方法,但这是一个有趣的问题,所以我将投入两分钱的价值......

天真的方法是生成可用字母和所有不同子集的所有排列,然后在字典中搜索每个潜在单词。问题是,虽然这样做并不难,但潜在的单词数量惊人地多,而且大部分都是无效的。

从积极的方面来说,可以通过二分搜索或类似的方法来加快检查字典的速度。不利的一面是,您会这样做很多次,以至于程序会因为长长的字母列表而停止运行。

我们肯定需要对字典进行预处理以使其更有用,而我们真正需要的是有一种方法可以快速排除大部分潜在匹配,即使该方法偶尔会出现误报。

一种方法是在位图中表示一个单词使用的字母。换句话说,为字典中的每个单词预先计算一个 32 位数字,如果在单词中至少使用一次相应的字母表,则设置每个位。这将允许您通过对字典进行线性扫描并仅保留仅使用可用字母的单词来找到所有潜在单词。我怀疑,通过一些聪明和索引,你可以做得比线性更好。

在您找到的候选人中,有些人需要比您可用的更多的信件实例,因此这些将是误报。这意味着您需要对您生成的所有候选人进行最终检查,以消除几乎命中。有很多方法可以做到这一点,但最简单的一种方法是浏览您的字母列表,并用破折号替换潜在单词中该字母的第一次出现。完成后,如果潜在单词除了破折号之外还有其他内容,那就是失败了。一个更优雅的解决方案,虽然不一定更快,是生成一个字母频率数组并比较它们。

再次,我认为尝试可能是要走的路,但我希望这些想法对你有用。

编辑

让我举一个例子来说明如何在初始搜索上比完全线性搜索做得更好:使用基数。保留一个简单的索引,让您可以查找以给定字母开头的第一个单词。然后,在进行搜索时,跳过所有以您没有的字母开头的单词。这不是一个巨大的加速,但它是一个改进。

【讨论】:

我不打算进一步编辑,但我觉得有必要提到 Bloom 过滤器将是一种根据字典检查任何潜在单词列表的好方法,因为它们速度快并且可以不允许漏报。

以上是关于C++ - 如何有效地找出向量中的任何字符串是不是可以由一组字母组合而成的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中,是不是可以针对这些对象的任何属性轻松地对对象类型指针的向量进行排序?

如何有效地为现代 C++ 中指向虚拟基类的指针向量分配空间

C++ 将向量传递给重载类 - 有些调用语句有效,有些则无效。

如何仅针对键的子集有效地比较 C++ 中的两个字符串映射

C ++:检查向量中的元素是不是大于另一个具有相同索引的元素的有效方法?

C++ 将单个元素移动到向量中的新位置的最简单最有效的方法