如何在字符网格中找到有效的单词?


12

我正在创建一个类似于Tetris的游戏,但有两个主要区别:屏幕已经开始填充图块(例如在Nintendo DS和PC的Puzzle Quest中),每个图块中都有一个字母。玩家的目标是通过与它们形成有效的单词来消除它们。单词是通过将字母以除斜线以外的任何方向彼此相邻而形成的。

玩家可以将整行瓷砖向左或向右移动,也可以将整列瓷砖向上或向下移动,以获得所需的空间(如果行/列的移动超出了棋盘的限制,则超过限制的字母将“循环”,出现在行/列的另一端)。玩家采取行动后,游戏应检查整个棋盘以寻找有效的单词,并从棋盘中删除构成这些单词的字母。被删除的字母上方的字母将落在被删除的字母的位置,并且新字母将从屏幕顶部掉落,直到板子再次装满为止。

我已经写了一个线性算法,给定一个字符序列,可以确定它是否是一个有效的英语单词。我遇到的问题是:如何检查板上的有效单词?暴力是唯一的方法吗?测试电路板上所有可能的组合以查看它们是否有效,即使对于小型(5x5)的电路板也非常慢。任何帮助将不胜感激,谢谢!


不幸的是,你是对的。由于数字(3、4、5,... 25个字母的所有组合),它非常慢。也许将其限制为“单词必须水平或垂直排列”以提高性能(而不是使玩家看不到的随机单词)?
ashes999 2014年

我认为您需要再次查看将字符序列与单词匹配的算法。根据我的计算,一个5x5的网格将有2700个可能的单词,您的算法应该会穿透这些单词,例如,请参见乔什的答案。
塔米尔(Taemyr),2014年

我以下列方式得出2700个单词;从第一行的左到右单词开始。1个位置是5个字母词,2个4个字母词,3个3个字母词,4个2个字母词和5个1个字母词。我们可以将单词中的一个字母换成另一列的字母。我们可以不失一般性地假设没有字母被交换为1个字母的单词,并且第一个字母没有被交换为2个字母的单词。这给; 5 * 5 * 1 + 4 * 5 * 2 + 3 * 5 * 3 + 1 * 5 * 4 + 1 = 135。乘以行数和方向;135 * 5 * 4 = 2700
Taemyr 2014年

我想我没有说清楚,但是可以在任何方向上形成单词,除了对角线以外,甚至可以形成角(例如,第一行的第一个图块,然后第一行右边的第二个图块,然后是从第二行的下方开始)。
塔维奥2014年

@Tavio一些想法:检查应该首先走更长的单词(如果我不愿意,我不希望“ as”。此外,最好不要忽略单个字母的单词,否则您将永远无法使用任何一个。完成后,我想知道您给这个游戏的名字,以便我检查出来
David Starkey 2014年

Answers:


22

暴力不是唯一的方法。

解决游戏棋盘与解决Boggle棋盘相似,但更为简单。您想检查板上的每个磁贴,以查看是否有可以沿适当方向写的字。

您仍然想进一步完善搜索空间,以免您知道无法说一个字就不必沿着一个方向搜索。例如,如果q连续发现两个,则应中止。为此,您需要某种数据结构,该结构可让您判断给定的字符集是否为有效单词的前缀。为此,您可以使用trie或前缀树;解决此类问题时有用的数据结构。

前缀树是基于分层节点的结构,其中每个节点代表其子级的某个前缀,叶节点(通常)代表最终值。例如,如果您的有效词词典包含“ cat”,“ car”和“ cell”,则trie可能类似于:

    0        The root of the trie is the empty string here, although you 
    |        can implement the structure differently if you want.
    c
   / \ 
  a   e
 / \   \
t  r    l
         \
          l

因此,首先用游戏中的每个有效单词填充前缀树。

在任何给定时间在板上查找有效单词的实际过程将涉及从板上的每个图块开始递归搜索。由于从某个给定图块开始的整个木板空间搜索都是独立的,因此可以根据需要并行进行搜索。搜索时,您将根据字母的值在搜索方向上“跟随”前缀树。

您最终将到达一个周围的字母都不是当前前缀树节点的子代的位置。当达到该点时,如果也确实当前节点是叶,则您已经找到了有效的单词。否则,您将找不到有效的单词,并且可能会中止搜索。

可以在此同伴的博客上找到示例代码和有关此技术的讨论(以及其他讨论,例如动态编程解决方案,可以通过在时尚之后“反转”搜索空间来加快速度)。他讨论了解决Boggle的问题,但要使解决方案适应您的游戏或多或少是改变允许搜索的方向的问题。


暴力并不是您自己解释的唯一方式。:)有很多前缀,表明没有意义继续寻找。(大多数[随机]字符串不是单词。+1
AturSams,2014年

好答案。“单词”是游戏字典句号中的所有内容。
亚当·埃伯巴赫,2014年

OP声明他有一种算法可以将单词与字符串匹配。因此,我认为这不能回答问题。
塔米尔(Taemyr),2014年

OTOH我认为OP将需要比他目前拥有的更高效的字符串匹配算法。
塔米尔(Taemyr),2014年

1
@Taemyr使用简单的特里,是的。但是可以使用Aho-Corasick算法,该算法利用稍微修改的特里更有效(线性)。使用Aho-Corasick算法,可以在O(n ^ 2)的时间内在nxn矩阵中找到所有有效单词。
el.pescado 2014年

3

您可能已经尝试过了,已经实施了,也许最好再加上另一个答案,等等。但是我还没有看到他们被提及,所以这里是:

通过跟踪发生了什么变化和没有发生什么变化,您可以丢弃很多支票。例如:

On a 5x5 field, A vertical word is found on base of the third column,
All the rows change. However, the first, second, fourth, and fifth,
columns do not change, so you dont need to worry about them (the third did change.)

On a 5x5 field, A 3 letter word is found horizontally on row 2, column 3, to column 5.
So you need to check row 1 and 2 (row 1 because the words on that one
fell down and where replaced), as-well as columns 3, 4, and 5.

或者,以伪代码

// update the board

// and check
if (vertical_word)
{
    check(updated_column)

    for (i in range 0 to updated_row_base)
        check(i)
}
else // horizontal word
{
    for (i in range 0 to updated_row)
        check(i)

    for (i in range 0 to updated_column_start)
        check(i)

    for (i in range updated_column_end+1 to final_column)
        check(i)
}

还有一些琐碎的问题:

您是否设置了编译器速度优化?(如果您使用的是)

可以完全优化您的单词搜索算法吗?以任何方式?


除了允许玩家旋转行外,因此在第三列中查找单词将影响其他列。
塔米尔(Taemyr),2014年

@Taemyr IF(rowMoved){ checkColumns(); checkMovedRow(); } IF(columnMoved){ checkRows() checkMovedColumn();} 如果用户一次只能移动一个,则在该移动结束时,没有平行字母移动过,因此无需重新检查这些字母。
大卫·斯塔基

2

请记住,每个字符都是一个值。因此,利用它来发挥您的优势。对子字符串进行迭代时,可以快速计算出一些哈希函数。例如,假设我们给每个字母一个5位代码(就像c - 'a' + 1在C中一样):

space = 00000;
a = 00001;
c = 00011;
e = 00101;
t = 01100;

比起您可以快速遍历一定大小的所有子字符串:

a b [c a t] e t h e = 00011 00001 01100;
//Now we just remove the 5 msb and shfit the rest 5 bits left and add the new character.
a b  c [a t e] t h e = (([c a t] & 0xffff) << 5) | e; // == a t e

在当今大多数常见的体系结构上,我们可以通过这种方式检查最多12个字母的子字符串。

如果您的词典中存在哈希码,则可以从那里快速提取单词,因为这样的哈希码是唯一的。当最多达到12个字母时,您可能希望为以这12个字母开头的单词添加其他数据结构。如果您发现以特定的12个字母开头的单词,则可以简单地为以该前缀开头的每个单词的后缀创建一个列表或另一个小哈希表。

存储所有现有单词代码的字典所占用的内存不应超过几兆字节。


0

形成单词时,您只限于经典的俄罗斯方块形状吗,还是任何形式都可以?单词可以无限期弯曲还是只能弯曲一次?单词可以随便吗?如果您可以根据需要进行任意多的弯曲操作,那么有效的最长单词最长为25个字符就变得非常复杂。我假设您有一个可接受的单词列表。基于这个假设,我建议您尝试这样的操作:

At the start of the game:
  Iterate tiles:
    Use tile as starting letter
      Store previous tile
      Check the four adjacent tiles
      If a tile can continue a word started by the previous tile, carry on
      Store the next tile
      Move check to next tile

这将在每个图块上创建一个地图,其中包含有关该图块如何连接到网格中周围单词的信息。当移动列或行时,请检查移动之前或附近的所有图块,然后重新计算信息。当您找到一个单词时,就不能再为该单词添加图块了;去掉它。我不确定这是否会更快,实际上归结为一半创建了多少个单词。这样做的好处是,用户最有可能尝试从板上的半个完整单词中创建一个单词。通过保留所有这些单词,可以很容易地检查单词是否已完成。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.