我最近遇到过这个问题,并认为我可以在这里分享,因为我无法得到它。
我们得到一个从1-25编号的5 * 5网格,以及一组5对点,它们是网格上路径的起点和终点。
现在我们需要为5对点找到5条相应的路径,这样就不会有两条路径重叠。另请注意,只允许垂直和水平移动。 此外,组合5路径应覆盖整个网格。
例如,我们给出了一对点:
P={1,22},{4,17},{5,18},{9,13},{20,23}
然后相应的路径将是
1-6-11-16-21-22
4-3-2-7-12-17
5-10-15-14-19-18
9-8-13
20-25-24-23
到目前为止我的想法: 也许我可以为所有点对计算从源到目的地的所有路径,然后检查路径中是否没有公共点。然而,这似乎是更高的时间复杂性。
有人能提出更好的算法吗?如果可以通过伪代码解释我会很高兴。谢谢
答案 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]