如何对点的集合进行排序,以便它们一个接一个地设置?

2022-09-04 22:20:44

我有一个包含点坐标的 ArrayList:

class Point
{
   int x, y;
}
ArrayList<Point> myPoints;

这样的图像,例如:

enter image description here

问题是这些点在ArrayList中是混乱的,我想对它们进行排序,以便图像上彼此相邻的2个点在ArrayList中也是一个接一个的。我无法想出一些好主意或算法来解决这样的排序......有没有一些已知的方法来解决这些问题?

编辑:形状不能交叉自身,让我们假设只有看起来像这样的形状才能发生。


答案 1

我的想法是,你首先需要一个数学定义你的排序。我建议(注意,这个定义在原始问题中并不明确,为了完整起见,留在这里):

从放置序列中的任何点开始,然后永久追加到序列中最接近当前点且尚未追加到序列的点,直到所有点都追加到序列中。

因此,通过这个排序的定义,你可以为此派生一个简单的算法。

ArrayList<point> orderedList = new ArrayList<point>();

orderedList.add(myList.remove(0)); //Arbitrary starting point

while (myList.size() > 0) {
   //Find the index of the closest point (using another method)
   int nearestIndex=findNearestIndex(orderedList.get(orderedList.size()-1), myList);

   //Remove from the unorderedList and add to the ordered one
   orderedList.add(myList.remove(nearestIndex));
}

以上是相当通用的(无论查找下一个点的算法如何)。那么“findNearestIndex”方法可以定义为:

//Note this is intentially a simple algorithm, many faster options are out there
int findNearestIndex (point thisPoint, ArrayList listToSearch) {
    double nearestDistSquared=Double.POSITIVE_INFINITY;
    int nearestIndex;
    for (int i=0; i< listToSearch.size(); i++) {
        point point2=listToSearch.get(i);
        distsq = (thisPoint.x - point2.x)*(thisPoint.x - point2.x) 
               + (thisPoint.y - point2.y)*(thisPoint.y - point2.y);
        if(distsq < nearestDistSquared) {
            nearestDistSquared = distsq;
            nearestIndex=i;
        }
    }
    return nearestIndex;
}

更新:由于这个问题被修改为在很大程度上采用了我使用的定义,我删除了一些警告。


答案 2

这里有一个可能的解决方案:我们的目标是构建一个路径,在列表循环之前,该路径恰好访问列表中的每个点一次。我们可以递归地构造路径:我们可以从原始列表中选择任何点作为起点,并创建一个仅由单个节点组成的简单路径。然后,我们可以通过附加一个我们尚未访问的点来扩展已构造的路径。

然后,我们假设通过选择长度最小的路径来确保原始点列表可以找到一个好的顺序。在这里,按长度计算,我不是指路径中的点数,而是指路径上每对相邻点之间的欧几里得距离的总和。

唯一的问题是:给定这样的路径,我们接下来应该附加哪一点?从理论上讲,我们必须尝试所有可能性,看看哪一个能带来最好的整体路径。

下面的代码采用的主要技巧是它使用以下启发式方法:在我们必须将新点附加到到目前为止构建的路径的每个步骤中,选择最小化两个相邻点之间平均距离的点。

应该注意的是,将路径上最后一点与第一点之间的“循环距离”包括在内是一个坏主意:随着我们不断添加点,我们越来越远离第一个路径点。如果我们包括两个端点之间的距离,这将严重影响所有相邻对之间的平均距离,从而损害我们的启发式。

下面是一个简单的辅助类,用于实现上面概述的路径构造:

/**
 * 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;
}

如果对问题的样本点运行此代码,然后连接返回列表中的每个相邻点对,则会得到下图:

enter image description here