6

我正在尝试解决反向Boggle问题。简单地说,给定一个单词列表,得出一个 4x4 的字母网格,其中可以在相邻字母序列中找到列表中尽可能多的单词(字母在正交和对角线上都相邻)。

我不想拿一个已知的板子来解决它。这是一个简单的 TRIE 问题,并且已经在这里为人们的 CS 项目进行了讨论/解决。

示例单词列表:

margays, jaguars, cougars, tomcats, margay, jaguar, cougar, pumas, puma, toms

解决方案:

ATJY
CTSA
OMGS
PUAR

这个问题很难(对我来说)。到目前为止我的算法:

  1. 对于输入中的每个单词,列出所有可以合法地单独出现在板上的可能方式。
  2. 尝试在这些板上放置单词#2的所有可能组合,并保留没有冲突的组合。
  3. 重复直到列表结束。
  4. ...
  5. 利润!!!(对于那些阅读 /。)

显然,有实现细节。首先从最长的单词开始。忽略作为其他单词子串的单词。

我可以在大约 0.4 秒内为 7 个字符的单词生成所有 68k 可能的板。然后当我添加一个额外的 7 字符板时,我需要比较 68k x 68k 板 x 7 比较。解决时间变得冰冷。

必须有更好的方法来做到这一点!!!!

一些代码:

BOARD_SIDE_LENGTH = 4

class Board:
    def __init__(self):
        pass

    def setup(self, word, start_position):
        self.word = word
        self.indexSequence = [start_position,]
        self.letters_left_over = word[1:]
        self.overlay = []
        # set up template for overlay.  When we compare boards, we will add to this if the board fits
        for i in range(BOARD_SIDE_LENGTH*BOARD_SIDE_LENGTH):
            self.overlay.append('')
        self.overlay[start_position] = word[0]
        self.overlay_count = 0

    @classmethod
    def copy(boardClass, board):
        newBoard = boardClass()
        newBoard.word = board.word
        newBoard.indexSequence = board.indexSequence[:]
        newBoard.letters_left_over = board.letters_left_over
        newBoard.overlay = board.overlay[:]
        newBoard.overlay_count = board.overlay_count
        return newBoard

    # need to check if otherboard will fit into existing board (allowed to use blank spaces!)
    # otherBoard will always be just a single word
    @classmethod
    def testOverlay(self, this_board, otherBoard):
        for pos in otherBoard.indexSequence:
            this_board_letter = this_board.overlay[pos]
            other_board_letter = otherBoard.overlay[pos]
            if this_board_letter == '' or other_board_letter == '':
                continue
            elif this_board_letter == other_board_letter:
                continue
            else:
                return False
        return True

    @classmethod
    def doOverlay(self, this_board, otherBoard):
        # otherBoard will always be just a single word
        for pos in otherBoard.indexSequence:
            this_board.overlay[pos] = otherBoard.overlay[pos]
        this_board.overlay_count = this_board.overlay_count + 1

    @classmethod
    def newFromBoard(boardClass, board, next_position):
        newBoard = boardClass()
        newBoard.indexSequence = board.indexSequence + [next_position]
        newBoard.word = board.word
        newBoard.overlay = board.overlay[:]
        newBoard.overlay[next_position] = board.letters_left_over[0]    
        newBoard.letters_left_over = board.letters_left_over[1:]
        newBoard.overlay_count = board.overlay_count
        return newBoard

    def getValidCoordinates(self, board, position):
        row = position / 4
        column = position % 4
        for r in range(row - 1, row + 2):
            for c in range(column - 1, column + 2):
                if r >= 0 and r < BOARD_SIDE_LENGTH and c >= 0 and c < BOARD_SIDE_LENGTH:
                    if (r*BOARD_SIDE_LENGTH+c not in board.indexSequence):
                        yield r, c

class boardgen:
    def __init__(self):
        self.boards = []

    def createAll(self, board):
        # get the next letter
        if len(board.letters_left_over) == 0:
            self.boards.append(board)
            return
        next_letter = board.letters_left_over[0]    
        last_position = board.indexSequence[-1]
        for row, column in board.getValidCoordinates(board, last_position):
            new_board = Board.newFromBoard(board, row*BOARD_SIDE_LENGTH+column)
            self.createAll(new_board)

并像这样使用它:

words = ['margays', 'jaguars', 'cougars', 'tomcats', 'margay', 'jaguar', 'cougar', 'pumas', 'puma']
words.sort(key=len)

first_word = words.pop()

# generate all boards for the first word
overlaid_boards = []
for i in range(BOARD_SIDE_LENGTH*BOARD_SIDE_LENGTH):
    test_board = Board()
    test_board.setup(first_word, i)
    generator = boardgen()
    generator.createAll(test_board)
    overlaid_boards += generator.boards
4

2 回答 2

2

这是一个有趣的问题。我不能完全想出一个完整的优化解决方案,但这里有一些你可以尝试的想法。

困难的部分是如果您无法将所有单词都放入其中,则需要找到最佳子集。这将增加很多复杂性。首先消除显然不起作用的单词组合。剪切任何大于 16 个字母的单词。计算所需的唯一字母数。一定要考虑在同一个单词中重复的字母。例如,如果列表中包含“eagle”,我认为您不允许在单词的两端使用相同的“e”。如果您需要的字母列表> 16,则必须删除一些单词。弄清楚首先要删除哪些是一个有趣的子问题......我将从包含最少使用字母的单词开始。将所有子列表按分数排序可能会有所帮助。

然后你可以做总字长<16的小例子。之后,您从完整的单词列表开始,看看是否有解决方案。如果没有,请找出要删除的单词并重试。

给定一个单词列表,核心算法是找到一个包含所有这些单词的网格(如果存在的话)。

愚蠢的蛮力方法是使用您需要的字母遍历所有可能的网格,并测试每个网格以查看您的所有单词是否适合。不过这很苛刻:中间情况是 16!= 2x10exp13 板。n 个唯一字母的精确公式是... (16!)/(16-n)!x pow(n, 16-n)。这给出了 3x10exp16 范围内的最坏情况。不太好管理。即使您可以避免旋转和翻转,也只能为您节省 1/16 的搜索空间。

一个更聪明的贪心算法是按照一些标准对单词进行排序,比如难度或长度。递归解决方案是获取列表中剩余的最上面的单词,并尝试将其放置在网格上。然后使用该网格和剩余的单词列表进行递归。如果你在用完单词之前填满了网格,那么你必须回溯并尝试另一种放置单词的方式。更贪婪的方法是首先尝试重复使用最多字母的展示位置。你可以做一些修剪。如果在任何时候网格中剩余的空格数少于所需的剩余唯一字母集,那么您可以消除这些子树。在其他一些情况下,很明显没有可以削减的解决方案,尤其是当剩余的网格空间小于最后一个单词的长度时。搜索空间取决于字长和重复使用的字母数量。我确信它比蛮力更好,但我不知道这是否足以使问题变得合理。

聪明的方法是使用某种形式的动态编程。我看不到完整的算法。一个想法是有一个字母树或图表,将每个字母连接到单词列表中的“相邻”字母。然后,您从连接最多的字母开始,并尝试将树映射到网格上。始终放置完成大部分单词列表的字母。必须有某种方法来处理网格中多个相同字母的情况。而且我不确定如何订购它,因此您不必搜索每个组合。

最好的办法是有一个动态算法,它还包括所有子单词列表。因此,如果列表有“fog”和“fox”,并且 fox 不适合但fox 适合,那么它将能够处理这个问题,而不必在列表的两个版本上运行整个事情。这增加了复杂性,因为现在您必须随时按分数对每个解决方案进行排名。但是在所有单词都不合适的情况下,它会节省很多时间。

祝你好运。

于 2014-02-07T13:37:02.220 回答
0

您可以尝试一些加快回溯搜索的一般想法:

1) 早期检查。它通常有助于尽早丢弃永远无法工作的部分解决方案,即使以更多工作为代价。考虑通过切分你试图适应的单词产生的所有两个字符序列——例如,PUMAS 贡献了 PU、UM、MA 和 AS。这些都必须出现在最终答案中。如果部分解决方案没有足够的重叠双字符空间来包含它尚不具有的所有重叠双字符序列,则它不能扩展到最终答案。

2) 对称性。如果您试图证明没有解决方案,我认为这可能是最有用的。给定一种填充板的方法,您可以旋转并反映该解决方案以找到其他填充板的方法。如果您有 68K 起点,而一个起点是另一个起点的旋转或反射,则无需同时尝试两者,因为如果您可以(或可以)从一个起点解决问题,您可以从通过旋转或反射板的另一个起点。因此,您可以将需要尝试的起点数除以某个整数。

这个问题并不是每个阶段都有大量备选方案的唯一问题。这也影响了旅行商问题。如果您可以接受无法保证您会找到绝对最佳答案,那么您可以尝试不跟进这些 68k 选择中最没有希望的选择。您需要某种分数来决定保留哪个分数 - 您可能希望保留那些使用尽可能多的字母的分数。一些针对旅行商问题的程序很早就丢弃了节点之间没有希望的链接。一种丢弃部分解决方案而不是进行全深度优先搜索或分支定界的更通用方法是有限差异搜索 - 例如参见http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34。 2426 .

当然,一些 TSP 丢弃树搜索的方法完全有利于某种爬山方法。你可以从一个填充的方块开始,反复尝试在其中找到你的单词,修改一些字符以强制它们进入,尝试找到连续增加可以在方块中找到的单词数量的步骤。最简单的爬山形式是从多个随机起点重复简单的爬山。另一种方法是通过到目前为止仅随机化解决方案的一部分来重新开始爬山 - 因为您不知道要随机化的部分的最佳大小,您可能会决定随机选择要随机化的部分的大小,这样在至少有一部分时间是您随机化正确大小的区域以生成一个新的正方形开始。遗传算法和模拟退火在这里非常流行。一篇关于新想法 Late Acceptance Hill-Climbing 的论文也描述了它的一些竞争对手——http://www.cs.nott.ac.uk/~yxb/LAHC/LAHC-TR.pdf

于 2014-02-06T06:41:50.713 回答