这里有一个可能的解决方案:我们的目标是构建一个路径,在列表循环之前,该路径恰好访问列表中的每个点一次。我们可以递归地构造路径:我们可以从原始列表中选择任何点作为起点,并创建一个仅由单个节点组成的简单路径。然后,我们可以通过附加一个我们尚未访问的点来扩展已构造的路径。
然后,我们假设通过选择长度最小的路径来确保原始点列表可以找到一个好的顺序。在这里,按长度计算,我不是指路径中的点数,而是指路径上每对相邻点之间的欧几里得距离的总和。
唯一的问题是:给定这样的路径,我们接下来应该附加哪一点?从理论上讲,我们必须尝试所有可能性,看看哪一个能带来最好的整体路径。
下面的代码采用的主要技巧是它使用以下启发式方法:在我们必须将新点附加到到目前为止构建的路径的每个步骤中,选择最小化两个相邻点之间平均距离的点。
应该注意的是,将路径上最后一点与第一点之间的“循环距离”包括在内是一个坏主意:随着我们不断添加点,我们越来越远离第一个路径点。如果我们包括两个端点之间的距离,这将严重影响所有相邻对之间的平均距离,从而损害我们的启发式。
下面是一个简单的辅助类,用于实现上面概述的路径构造:
/**
* Simple recursive path definition: a path consists
* of a (possibly empty) prefix and a head point.
*/
class Path {
private Path prefix;
private Point head;
private int size;
private double length;
public Path(Path prefix, Point head) {
this.prefix = prefix;
this.head = head;
if (prefix == null) {
size = 1;
length = 0.0;
} else {
size = prefix.size + 1;
// compute distance from head of prefix to this new head
int distx = head.x - prefix.head.x;
int disty = head.y - prefix.head.y;
double headLength = Math.sqrt(distx * distx + disty * disty);
length = prefix.length + headLength;
}
}
}
这是实际的启发式搜索算法。
/**
* Implements a search heuristic to determine a sort
* order for the given <code>points</code>.
*/
public List<Point> sort(List<Point> points) {
int len = points.size();
// compares the average edge length of two paths
Comparator<Path> pathComparator = new Comparator<Path>() {
public int compare(Path p1, Path p2) {
return Double.compare(p1.length / p1.size, p2.length / p2.size);
}
};
// we use a priority queue to implement the heuristic
// of preferring the path with the smallest average
// distance between its member points
PriorityQueue<Path> pq = new PriorityQueue<Path>(len, pathComparator);
pq.offer(new Path(null, points.get(0)));
List<Point> ret = new ArrayList<Point>(len);
while (!pq.isEmpty()) {
Path path = pq.poll();
if (path.size == len) {
// result found, turn path into list
while (path != null) {
ret.add(0, path.head);
path = path.prefix;
}
break;
}
loop:
for (Point newHead : points) {
// only consider points as new heads that
// haven't been processed yet
for (Path check = path; check != null; check = check.prefix) {
if (newHead == check.head) {
continue loop;
}
}
// create new candidate path
pq.offer(new Path(path, newHead));
}
}
return ret;
}
如果对问题的样本点运行此代码,然后连接返回列表中的每个相邻点对,则会得到下图: