Неэкспоненциальное решение проблемы лабиринта? - PullRequest
14 голосов
/ 11 февраля 2009

Учитывая многоголовый ациклический граф * размера n, в котором каждый узел имеет не более трех дочерних элементов и трех родителей, существует ли неэкспоненциальный алгоритм, позволяющий определить, существует ли путь n-длины, если два узла не имеют одинаковое значение , а каждое значение множества учитывается?

По сути, у меня есть лабиринт n * n, в котором каждое пространство имеет случайное значение (1..n). Мне нужно найти путь (сверху вниз) из n узлов, который включает каждое значение.

Сейчас я использую поиск в глубину, но это T(n) = 3T(n-1) + O(1), то есть O(3^n), неидеальное решение.

Буду очень признателен за подтверждение моих страхов или за направление меня в правильном направлении.

Редактировать: чтобы сделать это немного более конкретным, вот лабиринт с решениями (решается с использованием решения в глубину).

 1 2 5 5 4
 1 5 1 3 5
 4 1 2 3 2
 5 5 4 4 3
 4 2 1 2 4
S3, 5, 1, 3, 4, 2, F4
S3, 5, 1, 3, 4, 2, F2
S3, 5, 1, 3, 4, 2, F4
S3, 5, 3, 2, 4, 1, F3
S3, 5, 3, 2, 4, 1, F3
S3, 5, 3, 2, 4, 1, F3
S4, 5, 3, 2, 4, 1, F3
S4, 5, 3, 2, 4, 1, F3
S4, 5, 3, 2, 4, 1, F3
S4, 5, 1, 3, 4, 2, F4
S4, 5, 1, 3, 4, 2, F2
S4, 5, 1, 3, 4, 2, F4
S5, 4, 3, 2, 5, 1, F3
13 total paths`

Ответы [ 5 ]

11 голосов
/ 17 февраля 2009

Эта проблема является NP-полной, поэтому неизвестно, существует ли решение за полиномиальное время. (Стандартные условия возможной простоты на практике, и т. Д. , применяются все.) Одно возможное сокращение - с 3SAT.

Предположим, у нас есть экземпляр 3SAT, такой как (a ∨ b ∨ c) ∧ (¬a ∨ ¬b ∨ ¬c). Я покажу, как использовать «гаджеты» для создания экземпляра вашей проблемы. Прежде чем мы начнем, переписать задачу 3SAT как (a1 ∨ b1 ∨ c1) ∧ (¬a2 ∨ ¬b2 ∨ ¬c2) вместе с a1 = a2, b1 = b2 и c1 = c2; то есть сделайте каждое вхождение переменной уникальным, но затем добавьте условие, что разные вхождения одной и той же переменной должны быть равны.

Во-первых, мы должны убедиться, что вы должны выбрать число 0 в первом ряду, чтобы мы могли использовать его позже как «страж», которого вы должны избегать.

 0   0   0 

Теперь мы создаем гаджет, который применяет правило a1 = a2: (подчеркивание _ - это новый уникальный номер при каждом использовании этого гаджета)

 0   _   0 
 a1  0  ¬a1
 a2  0  ¬a2

Из-за первой строки вы должны избегать нулей. Это означает, что вы либо выбираете путь "a1, a2" или путь "¬a1, ¬a2"; первый будет соответствовать (слегка сбивающему с толку) значению a, а второй будет соответствовать значению a. Это связано с тем, что наш гаджет предложения действительно прост, потому что мы просто записываем предложение, например (снова _ здесь каждый раз новая переменная):

 0   _   0 
 a1  b1  b2

или

 0   _   0 
¬a1 ¬b1 ¬b2

Наконец, поскольку вы использовали только один из a1 и ¬a1 и т. Д., Мы позволяем вам взять те, которые вы не использовали свободно:

 0   _   0 
 a1 ¬a1  0 

Теперь, это не совсем работает, потому что один из a1 и ¬a1, возможно, использовался в гаджете выбора переменной, в то время как другой мог быть использован в предложении. Итак, мы включаем новую переменную @i для каждого предложения, которое вы можете использовать вместо одной из переменных. Таким образом, если переменная a1 появляется в пункте 1, мы имеем

 0   _   0 
 a1 ¬a1  @1 

Вот полный вывод перевода исходного предложения 3SAT (выделение пути, соответствующего установке a и b в true, c в false и выбор a из первого предложения), с цифрами слева и глянцем на право. Гаджеты переупорядочены (гаджеты первого предложения, затем для каждой переменной, гаджета равенства и затем неиспользуемого гаджета), но это не имеет значения, так как они все равно независимы.

0       0  <    0               .       .  <    .       
0       8  <    0               .       _  <    .       
2  <    4       6               a1 <    b1      c1      
0       16 <    0               .       _       .       
11      13      15 <            -a2     -b2     -c2<    
0       17 <    0               .       _  <    .       
2       0       3  <            a1      .       -a1<    
10      0       11 <            a2      .       -a2<    
0       18 <    0               .       _  <    .       
2       3       1  <            a1      -a1     @1 <    
0       19 <    0               .       _       .       
10 <    11      9               a2 <    -a2     @2      
0       20 <    0               .       _  <    .       
4       0       5  <            b1      .       -b1<    
12      0       13 <            b2      .       -b2<    
0       21 <    0               .       _  <    .       
4  <    5       1               b1 <    -b1     @1      
0       22 <    0               .       _  <    .       
12 <    13      9               b2 <    -b2     @2      
0       23 <    0               .       _  <    .       
6  <    0       7               c1 <    .       -c1     
14 <    0       15              c2 <    .       -c2     
0       24 <    0               .       _  <    .       
6       7  <    1               c1      -c1<    @1      
0       25 <    0               .       _  <    .       
14      15      9  <            c2      -c2     @2 <    

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

В конце моего поста написана наспех написанная на Perl программа, которая генерирует одну из ваших проблем из ввода вида

a b c
-a -b -c

Число переменных в результирующем экземпляре вашей проблемы 11C + V + 1. Дайте программе переключатель -r для получения глянца вместо цифр.

# Set useful output defaults
$, = "\t"; $\ = "\n";

# Process readability option and force sentinel
my $r = "0";
if( $ARGV[0] =~ /-r/ ) { shift; $r = "."; }
print $r, $r, $r;

# Clause gadgets
my( %v, %c, $m, $c );
$m = 1;
while( <> ) {
    my( @v, @m );
    $c = $m++;
    chomp; @v = split;
    for my $v ( @v ) {
        push @{$v{strip($v)}}, -1; # hack, argh!
        push @m, ($r ? $v.@{$v{strip($v)}} : $m + neg($v));
        $c{($r ? (strip($v).@{$v{strip($v)}}) : $m)} = $c;
        $v{strip($v)}->[-1] = ($r ? (strip($v).@{$v{strip($v)}}) : $m);
        $m += 2 unless $r;
    }
    print $r, newv(), $r;
    print @m;
}

# Variable gadget
for my $v ( sort keys %v ) {
    # Force equal
    print $r, newv(), $r;
    for my $n ( @{$v{$v}} ) {
        print $n, $r, ($r ? "-".$n : $n+1);
    }

    # Unused
    for my $n ( @{$v{$v}} ) {
        print $r, newv(), $r;
        print $n, ($r ? "-".$n : $n+1), ($r ? "\@".$c{$n} : $c{$n});
    }
}

# Strip leading -
sub strip {
    my( $v ) = @_;
    return substr $v, neg($v);
}

# Is this variable negative?
sub neg {
    my( $v ) = @_;
    return "-" eq substr( $v, 0, 1 );
}

# New, unused variable
sub newv {
    return "_" if $r;
    return $m++;
}
5 голосов
/ 11 февраля 2009

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

РЕДАКТИРОВАТЬ: Если это все еще не достаточно быстро, вы можете улучшить его, применив оптимизация Арлекина .

Например:

1 2 3
3 2 1
1 2 1

Состояние 0: R = 0 // Строка P = {} // Путь указан

// {{Path so far}, Column}

P' = {
    {{1}, 0}
    {{2}, 1}
    {{3}, 2}
}

P = P'

Состояние 1: R = 1 // ROW P = { {{1}, 0} {{2}, 1} {{3}, 2} }

P' = {
    {{1 3}, 0}
    {{1 2}, 1}
    {{2 3}, 0}
    {{2 1}, 2}
    {{3 2}, 1}
    {{3 1}, 2}
}

Состояние 2: R = 2 P = { {{1 3}, 0} {{1 2}, 1} {{2 3}, 0} {{2 1}, 2} {{3 2}, 1} {{3 1}, 2} } * * Тысяча двадцать-один

P' = {
    {{1 3 2}, 1}
    {{2 3 1}, 0}
    {{3 2 1}, 0}
    {{3 2 1}, 2}
    {{3 1 2}, 1}
}

Результат:
Количество путей: 5
S1 1 3 2 F2
S2 2 3 1 F1
S3 3 2 1 F1
S3 3 2 1 F3
S3 3 1 2 F2

3 голосов
/ 11 февраля 2009

Вы можете попробовать оптимизацию колонии муравьев . Он быстро дает очень хорошие результаты, которые очень близки к идеальному решению.

1 голос
/ 11 февраля 2009

Одна оптимизация для Решение Кевина Лони может заключаться в объединении частичных путей, содержащих одинаковые элементы в одном столбце. Вам нужно будет отметить количество слияний с путем, если вы хотите узнать количество решений в конце.

Пример. В вашем примере 5x5, когда вы попадаете в третью строку, третий столбец ведет к трем путям, которые содержат (1 2 5) в некотором порядке. Вы не должны следовать им отдельно с этого момента, но можете объединить их. Если вы хотите узнать количество решений в конце, вам просто нужно скорректировать структуру данных вашего пути, например, три (1 (1 2 5)) слились бы с (3 (1 2 5)).

0 голосов
/ 11 февраля 2009

Поиск A * поиск. Это твой друг.

...