我怎样才能加速这个Anagram算法

时间:2011-07-02 04:02:34

标签: algorithm performance logic anagram

我正在制作移动应用以查找字谜和部分匹配。移动很重要,因为没有很多计算能力,效率是关键。

该算法采用任意数量的字母,包括重复,并找到由其字母组成的最长单词,每个字母仅使用一次。我也很想快速找到最好的结果,只要N满足,我就不会真正关注底部(较短的)。例如:

STACK => stack, tacks, acts, cask, cast, cats…

我已经做了一些谷歌搜索,并找到了一些算法,我想出了一个我认为有效的算法,但效率不如我想的那么高。

我预先制作了一个查找词典,它将排序的键映射到生成该键的真实单词。

"aelpp" => ["apple", "appel", "pepla"]

我根据密钥的长度进一步将每个字典拆分成不同的字典。所以5个字母长的键在一个字典中,6个键在另一个字典中。这些字典中的每一个都在一个数组中,其中索引是在字典中找到的键的长度。

anagramArray[5] => dictionary5
dictionary5["aelpp"] => ["apple", "appel", "pepla"]

我的算法首先输入一个输入词“lappe”,然后对其进行排序:

"lappe" => "aelpp"

现在,对于每个包含最多5个字母内容的字典,我会进行比较以将其拉出来。这是伪代码:

word = input.sort
for (i = word.length; i > 0; i--)
    dictionaryN = array[i]
    for (key in dictionaryN)
        if word matches key
            add to returnArray
        end
    end
    if returnArray count > N
      break
    end
end

returnArray.sort by longest word, alphabetize

字典中只有大约170,000个单词,但12个字母输入的搜索最多需要20秒。我的match方法从密钥中生成正则表达式:

"ackst" => /a.*c.*k.*s.*t.*/

这样,例如,acst(行为)等4个字母的键将匹配ackst(堆栈),因为:

"ackst" matches /a.*c.*s.*t.*/

我看到其他应用程序在更短的时间内完成同样的事情,我想知道我的方法是垃圾还是需要一些调整。

如何从单词生成前N个字谜中获得最大计算效率,按最大长度排序?

5 个答案:

答案 0 :(得分:5)

如果您认为(甚至可能代表)字典作为字母树,您可以避免查看大量节点。如果“堆栈”在字典中,那么将存在从根到标记为a-c-k-s-t的叶子的路径。如果输入的单词是“攻击”,那么将其排序以获得aackstt。您可以编写一个递归例程来跟踪从根目录下来的链接,随时使用来自aackstt的字母。当你到达ack时,你的字符串中会留下stt​​,所以你可以按照s来达到ackst,但你可以排除跟随你到达acku及其后代,v到达ackv及其后代,等等。 / p>

事实上,通过这种方案,你可以只使用一棵树来保存任意数量字母的单词,这样可以节省你多次搜索,每个目标长度一次。

答案 1 :(得分:0)

生成正则表达式有点贵,因此您可能不希望在循环中执行此操作。

一个选项(不一定非常有效,但在这种特殊情况下似乎很有用),我想到的是,不要搜索字典中的所有单词,而是尝试删除各种组合中的字母并检查结果字符串在你的字典中。这将最多在2 ^ n次迭代(其中n是你的单词中的字母数),这对于n <1优于170k。 18.请注意,这种特殊的方法并不适用于长输入,但它应该非常快。

答案 2 :(得分:0)

按如下方式构建字典:

 For each word W in the English language (or whatever word set you have)

     Sort the characters in W by alphabetical order (e.g. "apple" -> "aelpp") into a new string called W'

     Compute Hash H into W' using any fast hash algorithm (e.g CRC32.  You could likely invent anything yourself that has a low number of collisions)

     Store W and H as an element in the dictionary array
     That is:
        Word.original = W;
        Word.hash = Hash(W');
        Dictionary.append(Word);

  Sort the dictionary by hash values.

现在找到所有的Anagrams或搜索单词S

  Sort the characters in S by alphabetical order (e.g. "apple" -> "aelpp") into a new string called S'

  Compute Hash H of S' using the same fast hash algorithm above

  Now do a binary search on the dictionary for H.  The binary search should return an index F into Dictionary

  If the binary search fails to return an index into the Dictionary array, exit and return nothing

  I = F

  // Scan forward in the dictionary array looking for matches
  // a matching hash value is not a guarantee of an anagram match
  while (I < Dictionary.size) && (Dictionary[I].hash == H)
       if (IsAnagram(Dictonary[I], S)
           ResultSet.append(Dictionary[I].original)

  // Scan backwards in the dictionary array looking for matches
  I = F-1;
  while (I >= 0) && (Dictionary[I].hash == H)
       if (IsAnagram(Dictonary[I], S)
           ResultSet.append(Dictionary[I].original)


  return ResultSet     

现在我没有介绍如何处理“子串”搜索(搜索长度小于搜索词的字谜词。如果这是一个要求,我有点困惑。你的指示意味着那个结果集anagrams应该具有与搜索词完全相同的字符集。但是您可以枚举搜索字符串的所有子字符串并通过上述搜索算法运行每个子字符串。

答案 3 :(得分:0)

这只是一个想法,但也许这就是你要找的东西。您只有一个迭代的结构,所有大小的单词都在其中。在每个迭代步骤中,您将再引入一个字母,并且只将搜索范围缩小到没有比已经引入的字母“更大”的字母。例如,如果你引入M,你就不能再在N-Z范围内引入任何东西了。

结构可以是二叉树,其中引入一个字母可以进一步引导您多个树级别。每个节点都有一个字母,分支到其余较小的字母,分支到其余较大的字母,分支到下一个缩小的搜索的根,以及指向完全用字母构建的单词列表的指针到目前为止介绍。如果在该搜索子空间中没有可能的单词,则分支可以为null,但是对于3个分支,您不能同时为null,而对于指向单词列表的指针,则为null。 (嗯,你可以,作为一种优化,现在是无关紧要的)。您可以使用一个标志来表示存在具有给定字母的单词,而不是指向单词列表的指针,但这些单词可以存储在其他字典中。

所以我们假设我们有ACKST字母。从结构的根开始,您可以在循环中搜索所有这些字母,但在K之后,您可能只会继续使用A和C进行搜索(因为S和T高于K)。因为我们对最大的单词最感兴趣,所以我们应该从最大的字母开始搜索(在这种情况下为T),并继续使用下一个最大的字母。对于单词CAT,我们只能按特定顺序搜索字母T,C,A。一旦我们到达那个A,就会有一个指向以下单词列表的指针:ACT,CAT。

答案 4 :(得分:-1)

O(N)时间和O(1)解决方案,检查2个字符串是否为字谜

bool Anagram( const  char *s1, const char *s2)
{
    unsigned int sum=0;

    if ( s1 == NULL || s2 == NULL)
        return false;

    while ( *s1 != '\0' && s2 != '\0')
    {
                   sum ^= *s1;
                   sum ^= *s2;
                   s1++;
                   s2++;
    }

    if ( s1 != '\0' || s2 != '\0')
        return false;

    if (sum) return false;

    return true;
}

如果你xor两个相等的数字..你的结果是0.(因此算法)