网格中

时间:2015-06-26 19:43:22

标签: algorithm graph-algorithm approximation

我最近遇到过这个问题,并认为我可以在这里分享,因为我无法得到它。

我们得到一个从1-25编号的5 * 5网格,以及一组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
  6. 到目前为止我的想法: 也许我可以为所有点对计算从源到目的地的所有路径,然后检查路径中是否没有公共点。然而,这似乎是更高的时间复杂性。

    有人能提出更好的算法吗?如果可以通过伪代码解释我会很高兴。谢谢

3 个答案:

答案 0 :(得分:3)

此问题基本上是Hamiltonian path/cycle problem问题(因为您可以将一条路径的末尾连接到另一条路径的开头,并将所有五条路径视为一个大循环的一部分)。没有已知的有效算法,因为问题是NP完全的,所以你基本上需要尝试所有可能的回溯路径(有更好的算法,但它们并不快得多)。

您的标题要求使用近似算法,但这不是优化问题 - 某些解决方案并不比其他解决方案更好;所有正确的解决方案都同样好,如果不正确,那就完全错了 - 所以不可能进行近似。

编辑:以下是OP发布的原始问题的解决方案,其中不包括“必须覆盖所有单元格”约束。我将其留给可能面临原始问题的人。

这可以通过最大流算法解决,例如Edmonds-Karp

诀窍是将网格建模为每个网格单元有两个节点的图形;一个“传出”节点和一个“传入”节点。对于每个相邻的单元对,从任一单元中的“输出”节点到另一单元中的“输入”节点存在边缘。在每个单元内,还有从“传入”节点到“传出”节点的边缘。每个边具有容量1.创建一个全局源节点,其具有到所有起始节点的边缘,以及一个全局宿节点,所有端节点都具有边缘。

然后,运行流算法;结果流显示非交叉路径。

这是有效的,因为进入单元的所有流必须通过“内部”边缘从“传入”到“外部”节点,因此,通过每个单元的流量限制为1 - 因此,没有路径将相交。此外,只要所有容量都是整数,Edmonds-Karp(以及所有基于Floyd-Warshall的流算法)都将产生整数流。

答案 1 :(得分:2)

这是一个用Python编写的程序,可以遍历所有潜在的路径。它使用递归和回溯来查找路径,并标记一个网格以查看哪些位置已被使用。

一个关键的优化是它标记了网格上的起点和终点(25个点中的10个)。

另一个优化是它在开始跨越网格“行走”之前从每个点生成所有移动。例如,从第1点开始,移动到第2点和第2点。 6;从第7点开始,移动到第2,6,8和2点。 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)

答案 2 :(得分: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();
  }
}

陈旧,错误答案

如果你想一想这个问题就可以解决这个问题&#34;落后:&#34;将所有网格方块分配给路径并测试以查看分配是否有效。有25个网格方块,您需要构建5个路径,每个路径有2个端点。所以你知道这10点的路径。剩下的就是用剩下的15个方格标记它们所在的路径。每种都有5种可能性,总共5 ^ 15种。这大约有300亿。剩下的就是构建一个有效的检查器,说明给定的赋值是否是一组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]