Алгоритмы: интересный дифференцирующий алгоритм - PullRequest
8 голосов
/ 07 мая 2009

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

Математическая формулировка

Предположим, у вас есть два списка, L и R, каждый из которых содержит элементы из некоторого основного алфавита S. Более того, эти списки обладают тем свойством, что общие элементы появляются в следующем порядке: то есть, если L[i] = R[i*] и L[j] = R[j*], а i <<code>j, то i* <<code>j* , Списки вообще не должны иметь общих элементов, и один или оба могут быть пустыми. [ Уточнение: вы не можете предполагать, что элементы не повторяются. ]

Проблема заключается в создании своего рода «diff» списков, которые можно рассматривать как новый список упорядоченных пар (x,y), где x от L и y от R, со следующими свойствами:

  1. Если в обоих списках появляется x, то в результате появляется (x,x).
  2. Если x появляется в L, но не в R, то в результате появляется (x,NULL).
  3. Если y появляется в R, но не в L, то в результате появляется (NULL,y).

и наконец

  • Список результатов имеет «тот же» порядок, что и каждый из входных списков: он имеет, грубо говоря, то же свойство порядка, что и выше, с каждым из списков в отдельности (см. Пример).

Примеры

L = (d)
R = (a,b,c)
Result = ((NULL,d), (a,NULL), (b,NULL), (c,NULL))

L = (a,b,c,d,e)  
R = (b,q,c,d,g,e)
Result = ((a,NULL), (b,b), (NULL,q), (c,c), (d,d), (NULL,g), (e,e))

У кого-нибудь есть хорошие алгоритмы для решения этой проблемы? В чем сложность?

Ответы [ 8 ]

3 голосов
/ 08 мая 2009

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

Создать хэш-карту списка R, где ключом является элемент, а значением - исходный индекс в массиве; в C ++ вы можете использовать unordered_map из tr1 или boost.

Сохранить индекс для необработанной части списка R, инициализированной для первого элемента.

Для каждого элемента в списке L, проверьте хеш-карту на совпадение в списке R. Если вы не нашли его, выведите (значение L, NULL). Если есть совпадение, получите соответствующий индекс из хэш-карты. Для каждого необработанного элемента в списке R вплоть до соответствующего индекса выведите (NULL, значение R). Для совпадения выведите (значение, значение).

Когда вы дойдете до конца списка L, просмотрите оставшиеся элементы списка R и выведите (NULL, значение R).

Редактировать: Вот решение в Python. Для тех, кто говорит, что это решение зависит от наличия хорошей функции хеширования - конечно, это так. Оригинальный постер может добавить дополнительные ограничения к вопросу, если это проблема, но я буду занимать оптимистическую позицию до тех пор.

def FindMatches(listL, listR):
    result=[]
    lookupR={}
    for i in range(0, len(listR)):
        lookupR[listR[i]] = i
    unprocessedR = 0
    for left in listL:
        if left in lookupR:
            for right in listR[unprocessedR:lookupR[left]]:
                result.append((None,right))
            result.append((left,left))
            unprocessedR = lookupR[left] + 1
        else:
            result.append((left,None))
    for right in listR[unprocessedR:]:
        result.append((None,right))
    return result

>>> FindMatches(('d'),('a','b','c'))
[('d', None), (None, 'a'), (None, 'b'), (None, 'c')]
>>> FindMatches(('a','b','c','d','e'),('b','q','c','d','g','e'))
[('a', None), ('b', 'b'), (None, 'q'), ('c', 'c'), ('d', 'd'), (None, 'g'), ('e','e')]
2 голосов
/ 07 мая 2009

Наихудший случай, как определено и использует только равенство, должен быть O (n * m). Рассмотрим следующие два списка:

A [] = {a, b, c, d, e, f, g}

B [] = {h, ​​i, j, k, l, m, n}

Предположим, что существует ровно одно совпадение между этими двумя "упорядоченными" списками. Это займет O (n * m) сравнений, так как не существует сравнения, которое устраняет необходимость других сравнений позже.

Итак, любой алгоритм, который вы придумаете, будет O (n * m) или хуже.

1 голос
/ 09 мая 2009

Это в точности как выравнивание последовательностей, вы можете использовать алгоритм Needleman-Wunsch для его решения. Ссылка включает в себя код на Python. Просто убедитесь, что вы установили оценку так, чтобы несоответствие было отрицательным, совпадение было положительным, а выравнивание с пробелом равнялось 0 при максимизации. Алгоритм работает за O (n * m) времени и пространства, но пространственная сложность этого может быть улучшена.

Функция подсчета очков

int score(char x, char y){
    if ((x == ' ') || (y == ' ')){
        return 0;
    }
    else if (x != y){
        return -1;
    }
    else if (x == y){
        return 1;
    }
    else{
        puts("Error!");
        exit(2);
    }
}

код

#include <stdio.h>
#include <stdbool.h>

int max(int a, int b, int c){
    bool ab, ac, bc;
    ab = (a > b);
    ac = (a > c);
    bc = (b > c);
    if (ab && ac){
        return a;
    }
    if (!ab && bc){
        return b;
    }
    if (!ac && !bc){
        return c;
    }
}

int score(char x, char y){
    if ((x == ' ') || (y == ' ')){
        return 0;
    }
    else if (x != y){
        return -1;
    }
    else if (x == y){
        return 1;
    }
    else{
        puts("Error!");
        exit(2);
    }
}


void print_table(int **table, char str1[], char str2[]){
    unsigned int i, j, len1, len2;
    len1 = strlen(str1) + 1;
    len2 = strlen(str2) + 1;
    for (j = 0; j < len2; j++){
        if (j != 0){
            printf("%3c", str2[j - 1]);
        }
        else{
            printf("%3c%3c", ' ', ' ');
        }
    }
    putchar('\n');
    for (i = 0; i < len1; i++){
        if (i != 0){
            printf("%3c", str1[i - 1]);
        }
        else{
            printf("%3c", ' ');
        }
        for (j = 0; j < len2; j++){
            printf("%3d", table[i][j]);
        }
        putchar('\n');
    }
}

int **optimal_global_alignment_table(char str1[], char str2[]){
    unsigned int len1, len2, i, j;
    int **table;
    len1 = strlen(str1) + 1;
    len2 = strlen(str2) + 1;
    table = malloc(sizeof(int*) * len1);
    for (i = 0; i < len1; i++){
        table[i] = calloc(len2, sizeof(int));
    }
    for (i = 0; i < len1; i++){
        table[i][0] += i * score(str1[i], ' ');
    }
    for (j = 0; j < len1; j++){
        table[0][j] += j * score(str1[j], ' ');
    }
    for (i = 1; i < len1; i++){
        for (j = 1; j < len2; j++){
            table[i][j] = max(
                table[i - 1][j - 1] + score(str1[i - 1], str2[j - 1]),
                table[i - 1][j] + score(str1[i - 1], ' '),
                table[i][j - 1] + score(' ', str2[j - 1])
            );
        }
    }
    return table;
}

void prefix_char(char ch, char str[]){
    int i;
    for (i = strlen(str); i >= 0; i--){
        str[i+1] = str[i];
    }   
    str[0] = ch;
}

void optimal_global_alignment(int **table, char str1[], char str2[]){
    unsigned int i, j;
    char *align1, *align2;
    i = strlen(str1);
    j = strlen(str2);
    align1 = malloc(sizeof(char) * (i * j));
    align2 = malloc(sizeof(char) * (i * j));
    align1[0] = align2[0] = '\0';
    while((i > 0) && (j > 0)){
        if (table[i][j] == (table[i - 1][j - 1] + score(str1[i - 1], str2[j - 1]))){
            prefix_char(str1[i - 1], align1);
            prefix_char(str2[j - 1], align2);
            i--;
            j--;
        }
        else if (table[i][j] == (table[i - 1][j] + score(str1[i-1], ' '))){
            prefix_char(str1[i - 1], align1);
            prefix_char('_', align2);
            i--;
        }
        else if (table[i][j] == (table[i][j - 1] + score(' ', str2[j - 1]))){
            prefix_char('_', align1);
            prefix_char(str2[j - 1], align2);
            j--;
        }
    }
    while (i > 0){
        prefix_char(str1[i - 1], align1);
        prefix_char('_', align2);
        i--;
    }
    while(j > 0){
        prefix_char('_', align1);
        prefix_char(str2[j - 1], align2);
        j--;
    }
    puts(align1);
    puts(align2);
}

int main(int argc, char * argv[]){
    int **table;
    if (argc == 3){
        table = optimal_global_alignment_table(argv[1], argv[2]);
        print_table(table, argv[1], argv[2]);
        optimal_global_alignment(table, argv[1], argv[2]);
    }
    else{
        puts("Reqires to string arguments!");
    }
    return 0;
}

Образец ввода-вывода

$ cc dynamic_programming.c && ./a.out aab bba
__aab
bb_a_
$ cc dynamic_programming.c && ./a.out d abc
___d
abc_
$ cc dynamic_programming.c && ./a.out abcde bqcdge
ab_cd_e
_bqcdge
1 голос
/ 07 мая 2009

Различать упорядоченные списки можно за линейное время, обходя оба списка и сопоставляя их по ходу. Я постараюсь опубликовать некоторый psuedo Java-код в обновлении.

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

0 голосов
/ 09 мая 2009

ВЫБЕРИТЕ различный элемент l.element
ОТ LeftList l
НАРУЖНОЕ СОЕДИНЕНИЕ RightList r
ON l.element = r.element
ЗАКАЗАТЬ по l.id, r.id

Предполагается, что идентификатором каждого элемента является его порядок. И, конечно же, ваши списки содержатся в реляционной базе данных:)

0 голосов
/ 07 мая 2009

Не думаю, что у вас достаточно информации. Все, что вы утверждали, это то, что элементы, которые соответствуют, совпадают в том же порядке, но поиск первой подходящей пары является операцией O (nm), если у вас нет другого порядка, который вы можете определить.

0 голосов
/ 07 мая 2009

Нет реального осязаемого ответа, только смутная интуиция. Поскольку вы не знаете алгоритм упорядочения, а только то, что данные упорядочены в каждом списке, он звучит неопределенно, как алгоритмы, используемые для «сравнения» файлов (например, в Beyond Compare) и сопоставления последовательностей строк вместе. Или также смутно похож на алгоритмы регулярных выражений.

Также может быть несколько решений. (не берите в голову, если нет повторяющихся элементов, которые строго упорядочены. Я слишком много думал о сравнении файлов)

0 голосов
/ 07 мая 2009

Это довольно простая проблема, так как у вас уже есть упорядоченный список.

//this is very rough pseudocode
stack aList;
stack bList;
List resultList;
char aVal;
char bVal;

while(aList.Count > 0 || bList.Count > 0)
{
  aVal = aList.Peek; //grab the top item in A
  bVal = bList.Peek; //grab the top item in B

  if(aVal < bVal || bVal == null)
  {
     resultList.Add(new Tuple(aList.Pop(), null)));
  }
  if(bVal < aVal || aVal == null)
  {
     resultList.Add(new Tuple(null, bList.Pop()));
  }
  else //equal
  {
     resultList.Add(new Tuple(aList.Pop(), bList.Pop()));
  }
}

Примечание ... этот код НЕ СОБИРАЕТСЯ. Это просто руководство.

РЕДАКТИРОВАТЬ На основании комментариев ОП

Если алгоритм упорядочения не раскрыт, то списки должны считаться неупорядоченными. Если списки неупорядочены, то алгоритм имеет временную сложность O (n ^ 2), в частности, O (nm), где n и m - количество элементов в каждом списке.

EDIT Алгоритм для решения этой проблемы

Ь (а, б, в, г, д) Р (б, д, в, г, д, е)

//pseudo code... will not compile
//Note, this modifies aList and bList, so make copies.
List aList;
List bList;
List resultList;
var aVal;
var bVal;

while(aList.Count > 0)
{
   aVal = aList.Pop();
   for(int bIndex = 0; bIndex < bList.Count; bIndex++)
   {
      bVal = bList.Peek();
      if(aVal.RelevantlyEquivalentTo(bVal)
      {
         //The bList items that come BEFORE the match, are definetly not in aList
         for(int tempIndex = 0; tempIndex < bIndex; tempIndex++)
         {
             resultList.Add(new Tuple(null, bList.Pop()));
         }
         //This 'popped' item is the same as bVal right now
         resultList.Add(new Tuple(aVal, bList.Pop()));

         //Set aVal to null so it doesn't get added to resultList again
         aVal = null;

         //Break because it's guaranteed not to be in the rest of the list
         break;
      }
   }
   //No Matches
   if(aVal != null)
   {
      resultList.Add(new Tuple(aVal, null));
   }
}
//aList is now empty, and all the items left in bList need to be added to result set
while(bList.Count > 0)
{
   resultList.Add(new Tuple(null, bList.Pop()));
}

Набор результатов будет

* * 1 022 л (а, б, в, г, д) Р (б, д, в, г, д, е)

Результат ((a, ноль), (b, b), (ноль, q), (c, c), (d, d), (ноль, g), (e, e))

...