2015-06-26 4 views
3

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

Нам задана сетка 5 * 5 с номером 1-25 и набор из 5 пар точек, которые являются начальными и конечными точками пути на сетке.

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

Например, мы дали пару точек как:

P={1,22},{4,17},{5,18},{9,13},{20,23} 

Тогда соответствующие дорожки будут

  1. 1-6-11-16-21-22
  2. 4-3-2-7-12-17
  3. 5-10-15-14-19-18
  4. 9-8-13
  5. 20-25-24-23

То, что я думал до сих пор: Может быть, я могу вычислить все пути от источника к месту назначения для всех пар точек, а затем проверить, если нет общей точки в пути. Однако это, по-видимому, имеет более высокую временную сложность.

Может ли кто-нибудь предложить лучший алгоритм? Я был бы рад, если бы можно было объяснить с помощью псевдокода. Спасибо

+0

Посмотрите на алгоритмы шахматных движений для перемещения короля – SteveFerg

+0

Да, я слышал об этом, но это только для того, чтобы держать Короля в безопасности. Это, однако, другое ИМО. – user3907480

ответ

2

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

Одним из ключевых вариантов оптимизации является то, что он обозначает начальную и конечную точки сетки (10 из 25 баллов).

Другая оптимизация заключается в том, что он генерирует все ходы из каждой точки перед началом «ходьбы» по сетке. Например, из пункта 1 перемещения относятся к точкам 2 & 6; от точки 7, хода к пунктам 2, 6, 8 & 12.

points = [(1,22), (4,17), (5,18), (9,13), (20,23)] 
paths = [] 

# find all moves from each position 0-25 
moves = [None] # set position 0 with None 
for i in range(1,26): 
    m = [] 
    if i % 5 != 0: # move right 
     m.append(i+1) 
    if i % 5 != 1: # move left 
     m.append(i-1) 
    if i > 5:   # move up 
     m.append(i-5) 
    if i < 21:  # move down 
     m.append(i+5) 
    moves.append(m) 

# Recursive function to walk path 'p' from 'start' to 'end' 
def walk(p, start, end): 
    for m in moves[start]: # try all moves from this point 
     paths[p].append(m) # keep track of our path 
     if m == end:   # reached the end point for this path? 
      if p+1 == len(points): # no more paths? 
       if None not in grid[1:]: # full coverage? 
        print 
        for i,path in enumerate(paths): 
         print "%d." % (i+1), '-'.join(map(str, path)) 
      else: 
       _start, _end = points[p+1] # now try to walk the next path 
       walk(p+1, _start, _end) 

     elif grid[m] is None: # can we walk onto the next grid spot? 
      grid[m] = p   # mark this spot as taken 
      walk(p, m, end) 
      grid[m] = None  # unmark this spot 
     paths[p].pop()  # backtrack on this path 

grid = [None for i in range(26)] # initialize the grid as empty points 
for p in range(len(points)): 
    start, end = points[p] 
    paths.append([start])   # initialize path with its starting point 
    grid[start] = grid[end] = p # optimization: pre-set the known points 

start, end = points[0] 
walk(0, start, end) 
+0

Привет. Благодарю. это большая помощь. мне очень жаль, но я забыл добавить точку, о которой мне сказали сейчас, что пути, когда они объединяются, должны охватывать все точки в сетке. Ваш код делает это в этом примере и выглядит так, как во всех случаях. Имеет ли это? – user3907480

+0

Я проверил его для ввода образца. Это не гарантирует, что вся сетка будет закрыта. http://ideone.com/RPGUOm – user3907480

+0

Я добавил чек для полного покрытия, попробуйте еще раз. –

2

Эта проблема по существу связана с проблемой Hamiltonian path/cycle problem (поскольку вы можете подключить конец одного пути к началу другого и рассмотреть все пять путей как часть одного большого цикла). Для этого нет известных эффективных алгоритмов, так как проблема NP-полная, поэтому вам действительно нужно попробовать все возможные пути с обратным трассированием (есть более интересные алгоритмы, но они не намного быстрее).

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


Edit: ниже является решением исходной задачи, размещенного О.П., которые не включают «все клетки должны быть покрыты» ограничения. Я оставляю это для тех, кто может столкнуться с оригинальной проблемой.

Это можно решить с помощью алгоритма максимального потока, такого как Edmonds-Karp.

Хитрость заключается в моделировании сетки в виде графика, где есть два узла на ячейку сетки; один «исходящий» узел и один «входящий» узел. Для каждой соседней пары ячеек есть ребра от «исходящего» узла в любой ячейке до «входящего» узла в другой ячейке. Внутри каждой ячейки есть также край от «входящего» к «исходящему» узлу. У каждого ребра есть емкость 1. Создайте один глобальный узел источника, который имеет ребро для всех начальных узлов и один глобальный узел стока, к которому все конечные узлы имеют ребро.

Затем запустите алгоритм потока; результирующий поток показывает непересекающиеся пути.

Это работает, потому что весь поток, поступающий в ячейку, должен проходить через «внутренний» край от «входящего» к узлу «ougoing», и, таким образом, поток через каждую ячейку ограничен 1 - никакие пути не пересекаются. Кроме того, Edmonds-Karp (и все алгоритмы потока на основе Floyd-Warshall) будут генерировать целые потоки, если все емкости являются целыми числами.

+0

Я знаю, как найти максимальный поток в графе. Но как найти все пути потока? Можете ли вы, пожалуйста, помочь мне с рабочим кодом для этого? – user3907480

+0

@ user3907480 (Исправлено.) Поиск глубины в орграфе, соответствующем потоку. Делайте по одной паре клемм за раз, уменьшая поток по выбранному пути. –

+0

Я сделал редактирование в заявлении в BOLD. Я думаю, что теперь ваш алгоритм не работает – user3907480

0

Ну, я начал думать о алгоритме грубой силы, и я ушел, что ниже, но оказывается, что это на самом деле проще для поиска всех ответов, а не генерации всех конфигураций и проверки правильных ответов. Вот код поиска, который в итоге выглядел так же, как @Brent Washburne's. Он работает через 53 миллисекунды на моем ноутбуке.

import java.util.Arrays; 

class Puzzle { 

    final int path[][]; 
    final int grid[] = new int[25]; 

    Puzzle(int[][] path) { 
    // Make the path endpoints 0-based for Java arrays. 
    this.path = Arrays.asList(path).stream().map(pair -> { 
     return new int[] { pair[0] - 1, pair[1] - 1 }; 
    }).toArray(int[][]::new); 
    } 

    void print() { 
    System.out.println(); 
    for (int i = 0; i < grid.length; i += 5) 
     System.out.println(
     Arrays.toString(Arrays.copyOfRange(grid, i, i + 5))); 
    } 

    void findPaths(int ip, int i) { 
    if (grid[i] != -1) return; // backtrack 
    grid[i] = ip; // mark visited 
    if(i == path[ip][1])  // path complete 
     if (ip < path.length - 1) findPaths(ip + 1, path[ip + 1][0]); // find next path 
     else print(); // solution complete 
    else { // continue with current path 
     if (i < 20) findPaths(ip, i + 5); 
     if (i > 4) findPaths(ip, i - 5); 
     if (i % 5 < 4) findPaths(ip, i + 1); 
     if (i % 5 > 0) findPaths(ip, i - 1); 
    } 
    grid[i] = -1; // unmark 
    } 

    void solve() { 
    Arrays.fill(grid, -1); 
    findPaths(0, path[0][0]); 
    } 

    public static void main(String[] args) { 
    new Puzzle(new int[][]{{1, 22}, {4, 17}, {5, 18}, {9, 13}, {20, 23}}).solve(); 
    } 
} 

Старый, плохой ответ

Эта проблема является выполнимым с помощью грубой силы, если вы думаете о том, что «назад:» назначить все сетки квадратов к дорожкам и тест, чтобы увидеть, если присвоение является действительным. Есть 25 квадратов сетки, и вам нужно построить 5 путей, каждая с двумя конечными точками. Значит, вы знаете пути, на которые эти 10 пунктов лежат. Все, что осталось, - обозначить оставшиеся 15 квадратов путями, на которых они лежат. Для каждого из них есть 5 возможностей, поэтому всего 5^15. Это около 30 миллиардов. Осталось только создать эффективную проверку, которая говорит, является ли заданное задание 5 действительными путями. Это легко сделать с помощью линейного поиска по времени. Приведенный ниже код находит свое решение в течение примерно 2 минут и занимает немного до 11 минут, чтобы проверить исчерпывающе на моем MacBook:

import java.util.Arrays; 

public class Hacking { 

    static class Puzzle { 

    final int path[][]; 
    final int grid[] = new int[25]; 

    Puzzle(int[][] path) { this.path = path; } 

    void print() { 
     System.out.println(); 
     for (int i = 0; i < grid.length; i += 5) 
     System.out.println(
      Arrays.toString(Arrays.copyOfRange(grid, i, i + 5))); 
    } 

    boolean trace(int p, int i, int goal) { 
     if (grid[i] != p) return false; 
     grid[i] = -1; // mark visited 
     boolean rtn = 
      i == goal ? !Arrays.asList(grid).contains(p) : nsew(p, i, goal); 
     grid[i] = p; // unmark 
     return rtn; 
    } 

    boolean nsew(int p, int i, int goal) { 
     if (i < 20 && trace(p, i + 5, goal)) return true; 
     if (i > 4 && trace(p, i - 5, goal)) return true; 
     if (i % 5 < 4 && trace(p, i + 1, goal)) return true; 
     if (i % 5 > 0 && trace(p, i - 1, goal)) return true; 
     return false; 
    } 

    void test() { 
     for (int ip = 0; ip < path.length; ip++) 
     if (!trace(ip, path[ip][0] - 1, path[ip][1] - 1)) return; 
     print(); 
    } 

    void enumerate(int i) { 
     if (i == grid.length) test(); 
     else if (grid[i] != -1) enumerate(i + 1); // already known 
     else { 
     for (int ip = 0; ip < 5; ip++) { 
      grid[i] = ip; 
      enumerate(i + 1); 
     } 
     grid[i] = -1; 
     } 
    } 

    void solve() { 
     Arrays.fill(grid, -1); 
     for (int ip = 0; ip < path.length; ip++) 
     grid[path[ip][0] - 1] = grid[path[ip][1] - 1] = ip; 
     enumerate(0); 
    } 
    } 

    public static void main(String[] args) { 
    new Puzzle(new int[][]{{1, 22}, {4, 17}, {5, 18}, {9, 13}, {20, 23}}).solve(); 
    } 
} 

начиная массива:

[ 0, -1, -1, 1, 2] 
[-1, -1, -1, 3, -1] 
[-1, -1, 3, -1, -1] 
[-1, 1, 2, -1, 4] 
[-1, 0, 4, -1, -1] 

Результат:

[ 0, 1, 1, 1, 2] 
[ 0, 1, 3, 3, 2] 
[ 0, 1, 3, 2, 2] 
[ 0, 1, 2, 2, 4] 
[ 0, 0, 4, 4, 4]