【问题标题】:Palindrome detection efficiency回文检测效率
【发布时间】:2010-09-19 21:00:29
【问题描述】:

我对@9​​87654321@ 产生了好奇,并开始寻找有效的方法来进行回文检测。我检查了palindrome golf 的答案,在我看来,答案中只有两种算法,反转字符串并从尾部和头部检查。

def palindrome_short(s):
    length = len(s)
    for i in xrange(0,length/2):
        if s[i] != s[(length-1)-i]: return False
    return True

def palindrome_reverse(s):
    return s == s[::-1]

我认为这两种方法都不能用于检测巨大 DNA 序列中的精确回文。我环顾四周,没有找到任何免费的文章来说明这可能是一种超高效的方法。

一个好的方法可能是以分而治之的方法并行化第一个版本,将一对 char 数组 1..n 和 length-1-n..length-1 分配给每个线程或处理器。

有什么更好的方法?

你知道吗?

【问题讨论】:

    标签: algorithm performance palindrome


    【解决方案1】:

    只给一个回文,你必须在 O(N) 内完成,是的。您可以通过按您所说的拆分字符串来提高多处理器的效率。

    现在假设您想要进行精确的 DNA 匹配。这些字符串有数千个字符长,而且非常重复。这给了我们优化的机会。

    假设您将一个 1000 字符长的字符串分成 5 对 100,100。代码将如下所示:

    isPal(w[0:100],w[-100:]) and isPal(w[101:200], w[-200:-100]) ...
    

    等等...第一次进行这些匹配时,您必须处理它们。但是,您可以将您所做的所有结果添加到一个哈希表映射对到布尔值:

    isPal = {("ATTAGC", "CGATTA"): True, ("ATTGCA", "CAGTAA"): False}
    

    等等...不过,这会占用太多内存。对于 100,100 对,哈希映射将有 2*4^100 个元素。假设您只存储两个 32 位的字符串哈希作为键,您将需要 10^55 兆字节,这很荒谬。

    如果您使用较小的字符串,问题可能会很容易解决。然后你会有一个巨大的哈希图,但至少回文对于假设 10x10 对将花费 O(1),因此检查 1000 字符串是否是回文将需要 100 次查找而不是 500 次比较。不过仍然是 O(N)...

    【讨论】:

    • 您忘记了哈希查找在键的长度上是线性的,并且由于哈希计算使用了一些算术,它实际上比逐字符比较效率低。即使你partalelize,分块也无济于事,因为每次错过都会浪费大量的工作,而且错过的次数比命中的多得多。从中心进行比较效率更高,因为您可以提前退出。
    【解决方案2】:

    第二个函数的另一个变体。我们不需要检查正常字符串和反向字符串的正确部分是否相等。

    def palindrome_reverse(s):
      l = len(s) / 2
      return s[:l] == s[l::-1]
    

    【讨论】:

      【解决方案3】:

      很明显,你不会比 O(n) 渐近效率更好,因为每个字符必须至少检查一次。不过,您可以获得更好的乘法常数。

      对于单线程,您可以使用汇编获得加速。您还可以通过一次检查大于一个字节的块中的数据来做得更好,但是由于对齐方面的考虑,这可能会很棘手。如果您可以一次检查最大 16 字节的块,那么使用 SIMD 会做得更好。

      如果你想并行化它,你可以将字符串分成 N 部分,并让处理器 i 将段 [i*n/2, (i+1)*N/2) 与段 [L-(i+1)*N/2, L-i*N/2) 进行比较。

      【讨论】:

      • 不是比较 16 个字节的块,一次做 4 个回文可能更快。它会为您节省大量数据,并且可能不需要太多的水平操作。
      • 其他想法:将尽可能多的密钥存储在一个机器字中。将此与包含测试项目的内存缓冲区的每个字节进行比较。在此之前不要诉诸字符串操作。不要使用任何超过 8 位字符的字符,因为限制因素将是内存访问。
      【解决方案4】:

      没有,除非您进行模糊匹配。这可能是他们在 DNA 中所做的(我已经用 smith-waterman 在 DNA 中进行了 EST 搜索,但这显然比匹配回文或序列中的反向互补要困难得多)。

      【讨论】:

        【解决方案5】:

        它们都在 O(N) 中,所以我认为这些解决方案中的任何一个都没有任何特定的效率问题。也许我不够有创意,但我看不出如何在少于 N 步的时间内比较 N 个元素,所以像 O(log N) 这样的事情绝对不可能恕我直言。

        Pararellism 可能会有所帮助,但它仍然不会改变算法的 big-Oh 等级,因为它相当于在更快的机器上运行它。

        【讨论】:

          【解决方案6】:

          从中心进行比较总是更有效,因为您可以在未命中的情况下及早退出,但它允许您进行更快的最大回文搜索,无论您是在寻找最大半径还是所有不重叠的回文。

          唯一真正的并行化是如果您有多个独立的字符串要处理。拆分成块会为每次未命中浪费大量工作,而且未命中总是比命中多得多。

          【讨论】:

          • 你能解释一下如何从中心进行比较可以提前退出吗?为什么中心附近的不匹配比边缘附近的不匹配更有可能?
          • 你怎么知道“失败总是比命中多得多”? OP 是否说明了输入数据的特征?
          【解决方案7】:

          使用 Python,短代码可以更快,因为它将负载放入更快的 VM 内部(还有整个缓存和其他类似的东西)

          def ispalin(x):
             return all(x[a]==x[-a-1] for a in xrange(len(x)>>1))
          

          【讨论】:

          • 不错。蟒蛇。袖珍的。不幸的是,与建议相反,这段代码似乎并不比 Vinko 的建议快。
          【解决方案8】:

          您可以使用哈希表来放置字符,并有一个计数器变量,每次您找到不在 table/map 中的元素时,其值都会增加。如果您检查并找到表中已经存在的元素,请减少计数。

          For odd lettered string the counter should be back to 1 and for even it should hit 0.I hope this approach is right.
          
          See below the snippet.
          s->refers to string
          eg: String s="abbcaddc";
          Hashtable<Character,Integer> textMap= new Hashtable<Character,Integer>();
                  char charA[]= s.toCharArray();
                  for(int i=0;i<charA.length;i++)
                  {
          
                      if(!textMap.containsKey(charA[i]))
                      {   
                          textMap.put(charA[i], ++count);
          
                      }
                      else
                          {
                          textMap.put(charA[i],--count);
          
          
                  }
                  if(length%2 !=0)
                  {
                      if(count == 1)
                      System.out.println("(odd case:PALINDROME)");
                      else
                          System.out.println("(odd case:not palindrome)");
                  }
                  else if(length%2==0)    
                  {
                      if(count ==0)
                          System.out.println("(even case:palindrome)");
                      else
                          System.out.println("(even case :not palindrome)");
                  }
          

          【讨论】:

          • 这个答案中提出的算法是完全不正确的。
          【解决方案9】:
          public class Palindrome{
              private static boolean isPalindrome(String s){
                  if(s == null)
                      return false;   //unitialized String ? return false
                  if(s.isEmpty())     //Empty Strings is a Palindrome 
                      return true;
                  //we want check characters on opposite sides of the string 
                  //and stop in the middle <divide and conquer>
                  int left = 0;  //left-most char    
                  int right = s.length() - 1; //right-most char
          
                  while(left < right){  //this elegantly handles odd characters string 
                      if(s.charAt(left) != s.charAt(right)) //left char must equal 
                          return false; //right else its not a palindrome
                      left++; //converge left to right 
                      right--;//converge right to left 
                  }
                  return true; // return true if the while doesn't exit 
              }
          }
          

          虽然我们正在进行 n/2 次计算,但它仍然是 O(n) 这也可以使用线程来完成,但是计算会变得混乱,最好避免它。这不会测试特殊字符并且区分大小写。我有代码可以做到这一点,但是可以修改此代码以轻松处理。

          【讨论】:

          • 尝试更清楚地解释为什么这是问题的答案。
          • 这个网站是为了回答人们的问题。代码可以帮助回答一些问题,但更重要的是描述代码背后的想法。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-22
          • 1970-01-01
          • 2011-11-09
          • 1970-01-01
          • 2018-03-12
          • 1970-01-01
          相关资源
          最近更新 更多