Эффективность обнаружения палиндрома

Меня заинтересовал ошибка интервью Джона Лимджапа, и я начал искать эффективные способы обнаружения палиндромов. . Я проверил ответы палиндром-гольф, и мне кажется, что в ответах есть только два алгоритма, меняющие строку и проверка с хвоста и головы.

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]

Я думаю, что ни один из этих методов не используется для обнаружения точных палиндромов в огромных последовательностях ДНК. Я немного осмотрелся и не нашел ни одной бесплатной статьи о том, каким может быть сверхэффективный способ для этого.

Хорошим способом может быть распараллеливание первой версии по принципу «разделяй и властвуй», назначая пару массивов символов 1..n и length-1-n..length-1 каждому потоку или процессору.

Что было бы лучше?

Вы знаете какие-нибудь?


person Vinko Vrsalovic    schedule 29.10.2008    source источник


Ответы (9)


Учитывая только один палиндром, вам придется сделать это за O (N), да. Вы можете повысить эффективность работы с несколькими процессорами, разделив строку, как вы сказали.

Теперь предположим, что вы хотите провести точное сопоставление ДНК. Эти строки состоят из тысяч символов и очень повторяются. Это дает нам возможность оптимизировать.

Скажем, вы разделили строку длиной 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), хотя...

person Claudiu    schedule 29.10.2008
comment
Вы забываете, что поиск хеша является линейным по длине ключа, и, поскольку вычисление хэша использует некоторую арифметику, на самом деле он менее эффективен, чем сравнение символов за символом. Кроме того, разбиение на фрагменты не поможет, даже если вы разделите его на части, поскольку на каждый промах у вас будет огромное количество потраченной впустую работы, а промахов гораздо больше, чем попаданий. Сравнение с центром намного эффективнее, так как вы можете выйти из игры раньше. - person ZXX; 03.08.2010

Другой вариант вашей второй функции. Нам не нужно проверять равенство правых частей нормальных и обратных строк.

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

Очевидно, что вы не сможете получить асимптотическую эффективность лучше, чем O(n), поскольку каждый символ должен быть проверен хотя бы один раз. Однако вы можете получить лучшие мультипликативные константы.

Для одного потока вы можете получить ускорение, используя сборку. Вы также можете добиться большего успеха, просматривая данные блоками размером более байта за раз, но это может быть сложно из-за соображений выравнивания. Еще лучше будет использовать SIMD, если вы сможете просматривать фрагменты размером до 16 байт за раз.

Если вы хотите распараллелить его, вы можете разделить строку на N частей, и пусть процессор i сравнит сегмент [i*n/2, (i+1)*N/2) с сегментом [L-(i+1)*N/2, L-i*N/2).

person Adam Rosenfield    schedule 29.10.2008
comment
Вместо того, чтобы сравнивать куски по 16 байт, вероятно, быстрее делать 4 палиндрома за раз. Это убережет вас от прокручивания данных и, вероятно, не потребует столько горизонтальных операций. - person Jasper Bekkers; 29.10.2008
comment
Другие идеи: Храните как можно больше ключей в одном машинном слове. Сравните это с каждым байтом буфера памяти, содержащего тестовый элемент. Не прибегайте к строковым операциям, пока это не произойдет. Не используйте символы длиннее 8-битных, так как ограничивающим фактором будет доступ к памяти. - person Loren Pechtel; 20.07.2010

Нет, если только вы не сделаете нечеткое совпадение. Это то, что они, вероятно, делают в ДНК (я провел поиск EST в ДНК со Смитом-Уотерманом, но это, очевидно, намного сложнее, чем сопоставление палиндрома или обратного дополнения в последовательности).

person nlucaroni    schedule 29.10.2008

Они оба находятся в O (N), поэтому я не думаю, что есть какая-то особая проблема эффективности с любым из этих решений. Может быть, я недостаточно креативен, но я не понимаю, как можно сравнивать N элементов менее чем за N шагов, поэтому что-то вроде O (log N) определенно невозможно ИМХО.

Парареллизм может помочь, но он все равно не изменит большой ранг алгоритма, поскольку он эквивалентен запуску его на более быстрой машине.

person Tamas Czinege    schedule 29.10.2008

Сравнение с центром всегда намного эффективнее, так как вы можете быстро выйти из строя при промахе, но это также позволяет вам выполнять более быстрый поиск максимального палиндрома, независимо от того, ищете ли вы максимальный радиус или все непересекающиеся палиндромы.

Единственная реальная параллелизация — это если у вас есть несколько независимых строк для обработки. Разделение на куски приведет к потере большого количества работы для каждого промаха, а промахов всегда гораздо больше, чем попаданий.

person ZXX    schedule 20.07.2010
comment
Можете ли вы объяснить, как сравнение с центром позволяет выручить раньше? Есть ли причина, по которой несовпадения вблизи центра более вероятны, чем несовпадения вблизи краев? - person Michał Wróbel; 13.12.2018
comment
Откуда вы знаете, что промахов всегда больше, чем попаданий? Говорил ли ОП что-нибудь о характеристиках входных данных? - person Michał Wróbel; 13.12.2018

С Python короткий код может быть быстрее, поскольку он загружает более быстрые внутренние части виртуальной машины (и есть весь кеш и другие подобные вещи).

def ispalin(x):
   return all(x[a]==x[-a-1] for a in xrange(len(x)>>1))
person Demur Rumed    schedule 28.01.2009
comment
Ницца. питонический. Компактный. К сожалению, вопреки предложению, этот код не кажется более быстрым, чем предложение Винко. - person Michał Wróbel; 13.12.2018

Вы можете использовать хеш-таблицу для размещения символа и иметь переменную счетчика, значение которой увеличивается каждый раз, когда вы находите элемент, которого нет в таблице/карте. Если вы проверите и найдете элемент, который уже находится в таблице, уменьшите количество.

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)");
        }
person Community    schedule 30.07.2017
comment
Алгоритм, представленный в этом ответе, совершенно неверен. - person Michał Wróbel; 13.12.2018

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), это можно сделать и с использованием потоков, но вычисления становятся беспорядочными, лучше избегать этого. это не проверяет специальные символы и чувствительно к регистру. У меня есть код, который это делает, но этот код можно легко изменить, чтобы справиться с этим.

person Tangang Atanga    schedule 29.11.2018
comment
Постарайтесь яснее объяснить, почему это ответ на вопрос. - person Jeroen Heier; 29.11.2018
comment
Этот сайт предназначен для ответов на вопросы людей. Код может помочь ответить на некоторые вопросы, но более важно описать идеи, лежащие в основе кода. - person Sneftel; 29.11.2018