如何对一组点进行排序,使它们一个接一个地设置?

时间:2014-08-13 13:43:14

标签: java algorithm sorting arraylist

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

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

这样的图像例如:

enter image description here

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

编辑: 形状不能越过自身,让我们假设只有像这样的形状才会出现。

7 个答案:

答案 0 :(得分:7)

我的想法是你首先需要对你的排序进行数学定义。我建议(注意,这个定义在原始问题中并不清楚,为了完整性而留在这里):

从序列中的任何点开始,然后永久性地将序列附加到最接近当前点并且尚未附加到序列的点,直到所有点都附加到序列中。

因此,通过这种排序定义,您可以为此

派生一个简单的算法
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));
}

以上是非常普遍的(无论找到下一个点的算法如何)。那么&#34; findNearestIndex&#34;方法可以定义为:

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

更新: 由于问题被修改为主要采用我使用的定义,我提出了一些警告。

答案 1 :(得分: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

答案 2 :(得分:1)

这不是Sort算法 - 它更像是重新排列,以最小化度量(连续点之间的距离)。

我会尝试某种启发式算法 - 例如:

  1. 选择连续三个点a,b,c。
  2. 如果距离(a,c)<距离(a,b)然后交换(a,b)。
  3. 从1开始重复。
  4. 应该可以计算出你需要循环多少次以实现最小排列,或者你可以通过在运行期间找不到交换来检测最小排列。

    您可能需要改变扫描的方向,而不是像bubble-sort的经典优化那样。

    <强>加

    实验表明这个算法不起作用,但我找到了一个。基本上,对于列表中的每个条目,找到最近的其他点并将其移动到下一个位置。

    private static class Point {
    
        final int x;
        final int y;
    
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        public String toString() {
            return "(" + x + "," + y + ")";
        }
    
        public double distance(Point b) {
            int dx = x - b.x;
            int dy = y - b.y;
            // Simple cartesian distance.
            return Math.sqrt(dx * dx + dy * dy);
        }
    }
    
    // Sample test data - forms a square.
    Point[] points = new Point[]{
        new Point(0, 0),
        new Point(0, 1),
        new Point(0, 2),
        new Point(0, 3),
        new Point(0, 4),
        new Point(0, 5),
        new Point(0, 6),
        new Point(0, 7),
        new Point(0, 8),
        new Point(0, 9),
        new Point(1, 9),
        new Point(2, 9),
        new Point(3, 9),
        new Point(4, 9),
        new Point(5, 9),
        new Point(6, 9),
        new Point(7, 9),
        new Point(8, 9),
        new Point(9, 9),
        new Point(9, 8),
        new Point(9, 7),
        new Point(9, 6),
        new Point(9, 5),
        new Point(9, 4),
        new Point(9, 3),
        new Point(9, 2),
        new Point(9, 1),
        new Point(9, 0),
        new Point(8, 0),
        new Point(7, 0),
        new Point(6, 0),
        new Point(5, 0),
        new Point(4, 0),
        new Point(3, 0),
        new Point(2, 0),
        new Point(1, 0),};
    
    public void test() {
        System.out.println("Hello");
        List<Point> test = Arrays.asList(Arrays.copyOf(points, points.length));
        System.out.println("Before: " + test);
        Collections.shuffle(test);
        System.out.println("Shuffled: " + test);
        List<Point> rebuild = new ArrayList<>(test);
        rebuild.add(0, new Point(0, 0));
        rebuild(rebuild);
        rebuild.remove(0);
        System.out.println("Rebuilt: " + rebuild);
    }
    
    private void rebuild(List<Point> l) {
        for (int i = 0; i < l.size() - 1; i++) {
            Point a = l.get(i);
            // Find the closest.
            int closest = i;
            double howClose = Double.MAX_VALUE;
            for (int j = i + 1; j < l.size(); j++) {
                double howFar = a.distance(l.get(j));
                if (howFar < howClose) {
                    closest = j;
                    howClose = howFar;
                }
            }
            if (closest != i + 1) {
                // Swap it in.
                Collections.swap(l, i + 1, closest);
            }
        }
    }
    

    打印:

    Before: [(0,0), (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8), (0,9), (1,9), (2,9), (3,9), (4,9), (5,9), (6,9), (7,9), (8,9), (9,9), (9,8), (9,7), (9,6), (9,5), (9,4), (9,3), (9,2), (9,1), (9,0), (8,0), (7,0), (6,0), (5,0), (4,0), (3,0), (2,0), (1,0)]
    Shuffled: [(9,6), (0,9), (0,8), (3,9), (0,5), (9,4), (0,7), (1,0), (5,0), (9,3), (0,1), (3,0), (1,9), (8,9), (9,8), (2,0), (2,9), (9,5), (5,9), (9,7), (6,0), (0,3), (0,2), (9,1), (9,2), (4,0), (4,9), (7,9), (7,0), (8,0), (6,9), (0,6), (0,4), (9,0), (0,0), (9,9)]
    Rebuilt: [(0,0), (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8), (0,9), (1,9), (2,9), (3,9), (4,9), (5,9), (6,9), (7,9), (8,9), (9,9), (9,8), (9,7), (9,6), (9,5), (9,4), (9,3), (9,2), (9,1), (9,0), (8,0), (7,0), (6,0), (5,0), (4,0), (3,0), (2,0), (1,0)]
    

    看起来像你在找什么。

    算法的效率不高 - 大约在O(n log n)附近 - 我希望你不需要这样做数百万次。

    如果您希望点以可预测的顺序出现(比如开始时最左边的一个),您可以在重建之前在列表的开头添加一个假点并在之后将其删除。该算法将始终保留第一个点。

答案 3 :(得分:1)

  

我在问题发生后不久就开始了这个问题,但由于问题被搁置,它已被推迟。这是一个简单的方法,同时也在评论和其他答案中提到过,但无论如何我都会在这里发布:

这是MCVE,显示了最简单,最直接的方法。该方法仅包括选择任意点,然后始终选择最接近前一点的点。当然,这有局限性:

  • 当有尖角或尖角时,它可能会选择错误的点
  • 效率不高,因为它反复搜索最近的点

加速它的一种方法可能是根据x坐标对点进行排序,然后利用这种部分排序,以便在寻找下一个邻居时跳过大部分点。但是,只要你不想在时间关键的背景下将其应用到成千上万的点,这应该不是问题。

反过来,可能的歧义可能可能是一个问题,但考虑到这一点,不得不说无论如何都没有说明问题。在某些情况下,甚至人类都不能决定哪个点是合适的“下一个”点 - 至少,当问题没有延伸到检测形状的“内部/外部”时(这有点类似于{{的问题) 3}}你只是不知道想要的路径是什么。)

请注意,大多数代码对于您的实际问题并不重要,但是......您没有提供这样的“存根”实现。相关部分为=== marked ===

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class SortShapePoints
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Shape shape = createExampleShape();
        List<Point2D> points = computePoints(shape, 6);
        Collections.shuffle(points);

        List<Point2D> sortedPoints = sortPoints(points);
        Path2D path = createPath(sortedPoints, true);
        f.getContentPane().add(new ShapePanel(points, path));

        f.setSize(800, 800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    //=== Relevant part starts here =========================================

    private static List<Point2D> sortPoints(List<Point2D> points)
    {
        points = new ArrayList<Point2D>(points);
        List<Point2D> sortedPoints = new ArrayList<Point2D>();
        Point2D p = points.remove(0);
        sortedPoints.add(p);
        while (points.size() > 0)
        {
            int index = indexOfClosest(p, points);
            p = points.remove(index);
            sortedPoints.add(p);
        }

        return sortedPoints;
    }

    private static int indexOfClosest(Point2D p, List<Point2D> list)
    {
        double minDistanceSquared = Double.POSITIVE_INFINITY;
        int minDistanceIndex = -1;
        for (int i = 0; i < list.size(); i++)
        {
            Point2D other = list.get(i);
            double distanceSquared = p.distanceSq(other);
            if (distanceSquared < minDistanceSquared)
            {
                minDistanceSquared = distanceSquared;
                minDistanceIndex = i;
            }
        }
        return minDistanceIndex;
    }

    //=== Relevant part ends here ===========================================


    private static Shape createExampleShape()
    {
        Area a = new Area();
        a.add(new Area(new Ellipse2D.Double(200, 200, 200, 100)));
        a.add(new Area(new Ellipse2D.Double(260, 160, 100, 500)));
        a.add(new Area(new Ellipse2D.Double(220, 380, 180, 60)));
        a.add(new Area(new Rectangle2D.Double(180, 520, 260, 40)));
        return a;
    }

    private static List<Point2D> computePoints(Shape shape, double deviation)
    {
        List<Point2D> result = new ArrayList<Point2D>();
        PathIterator pi = shape.getPathIterator(null, deviation);
        double[] coords = new double[6];
        Point2D newPoint = null;
        Point2D previousMove = null;
        Point2D previousPoint = null;
        while (!pi.isDone())
        {
            int segment = pi.currentSegment(coords);
            switch (segment)
            {
            case PathIterator.SEG_MOVETO:
                previousPoint = new Point2D.Double(coords[0], coords[1]);
                previousMove = new Point2D.Double(coords[0], coords[1]);
                break;

            case PathIterator.SEG_CLOSE:
                createPoints(previousPoint, previousMove, result, deviation);
                break;

            case PathIterator.SEG_LINETO:
                newPoint = new Point2D.Double(coords[0], coords[1]);
                createPoints(previousPoint, newPoint, result, deviation);
                previousPoint = new Point2D.Double(coords[0], coords[1]);
                break;

            case PathIterator.SEG_QUADTO:
            case PathIterator.SEG_CUBICTO:
            default:
                // Should never occur
                throw new AssertionError("Invalid segment in flattened path!");
            }
            pi.next();
        }
        return result;
    }

    private static void createPoints(Point2D p0, Point2D p1,
        List<Point2D> result, double deviation)
    {
        double dx = p1.getX() - p0.getX();
        double dy = p1.getY() - p0.getY();
        double d = Math.hypot(dx, dy);
        int steps = (int) Math.ceil(d / deviation);
        for (int i = 0; i < steps; i++)
        {
            double alpha = (double) i / steps;
            double x = p0.getX() + alpha * dx;
            double y = p0.getY() + alpha * dy;
            result.add(new Point2D.Double(x, y));
        }
    }

    public static Path2D createPath(Iterable<? extends Point2D> points,
        boolean close)
    {
        Path2D path = new Path2D.Double();
        Iterator<? extends Point2D> iterator = points.iterator();
        boolean hasPoints = false;
        if (iterator.hasNext())
        {
            Point2D point = iterator.next();
            path.moveTo(point.getX(), point.getY());
            hasPoints = true;
        }
        while (iterator.hasNext())
        {
            Point2D point = iterator.next();
            path.lineTo(point.getX(), point.getY());
        }
        if (close && hasPoints)
        {
            path.closePath();
        }
        return path;
    }

}

class ShapePanel extends JPanel
{
    private final List<Point2D> points;
    private final Shape shape;

    public ShapePanel(List<Point2D> points, Shape shape)
    {
        this.points = points;
        this.shape = shape;
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D) gr;
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g.setColor(Color.RED);
        g.draw(shape);
        g.setColor(Color.BLACK);
        for (Point2D p : points)
        {
            g.fill(new Ellipse2D.Double(p.getX() - 1, p.getY() - 1, 2, 2));
        }
    }
}

答案 4 :(得分:0)

这是一个相当开放的问题,但是如果你想以某种方式存储它们,你需要定义超过&#34;这样它们在数组中彼此相邻&#34;你需要有一个函数,你可以拿两点说,点A小于点B,反之亦然,或者它们是相等的。

如果你有,那么你需要对它们进行排序的算法已经实现,你可以通过实现比较器来使用它,如SANN3所说。

作为旁注,您可能不希望将形状存储为一组点。我想你可能想把它们存成一条线?您可以使用三次样条曲线来获得几乎您想要的任何形状,然后您可以节省存储...

答案 5 :(得分:0)

我有一个任务是对点进行排序以代表一条线。我决定保留路径的全部权重,并根据标准Collection操作对其进行更新。该解决方案也应适用于您的情况。只需采用此LinkedList ps的元素并连接其头部和尾部即可。另外,您可以添加更多操作,例如PointXY get(int index)等,并更多地转发到此组合中的基础LinkedList。最后,在必要时,您应使用过多的防御副本来保护集合。为了简洁起见,我尝试将其简化。

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;

public class ContinuousLineSet implements Collection<PointXY> {
  LinkedList<PointXY> ps = new LinkedList<>(); // Exposed for simplicity
  private int fullPath = 0; // Wighted sum of all edges in ps

  @Override
  public int size() {
    return ps.size();
  }

  @Override
  public boolean isEmpty() {
    return ps.isEmpty();
  }

  @Override
  public boolean contains(Object o) {
    return ps.contains(o);
  }

  @Override
  public Iterator<PointXY> iterator() {
    return ps.iterator();
  }

  @Override
  public Object[] toArray() {
    return ps.toArray();
  }

  @Override
  public <T> T[] toArray(T[] a) {
    return ps.toArray(a);
  }

  private int dist(PointXY a, PointXY b) {
    return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
  }

  @Override
  public boolean add(PointXY e) {
    if (isEmpty())
      return ps.add(e);

    if (ps.getFirst().equals(e))
      return false;

    Iterator<PointXY> it = ps.iterator();
    PointXY previous = it.next();
    int asFirst = fullPath + dist(e, previous);
    int minPath = asFirst;
    int iMin = 0;
    int i = 0;
    while (it.hasNext()) {
      i++;
      PointXY next = it.next();
      if (next.equals(e))
        return false;

      int asBetween = fullPath - dist(previous, next) + dist(previous, e) + dist(e, next);
      if (asBetween < minPath) {
        iMin = i;
        minPath = asBetween;
      }

      previous = next;
    }

    int asLast = fullPath + dist(e, previous);
    if (asLast < minPath) {
      minPath = asLast;
      iMin = size();
    }

    fullPath = minPath;
    ps.add(iMin, e);
    return true;
  }

  public void reverse() {
    Collections.reverse(ps);
  }

  @Override
  public boolean remove(Object o) {
    PointXY last = null;
    for (Iterator<PointXY> it = iterator(); it.hasNext();) {
      PointXY p = it.next();
      if (o.equals(p)) {
        int part1 = last != null ? dist(last, p) : 0;
        int part2 = it.hasNext() ? dist(p, it.next()) : 0;
        fullPath -= part1 + part2;
        break;
      }

      last = p;
    }

    return ps.remove(o);
  }

  @Override
  public boolean containsAll(Collection<?> c) {
    return ps.containsAll(c);
  }

  @Override
  public boolean addAll(Collection<? extends PointXY> c) {
    boolean wasAdded = false;
    for (PointXY p : c) {
      wasAdded |= add(p);
    }

    return wasAdded;
  }

  @Override
  public boolean removeAll(Collection<?> c) {
    boolean wasRemoved = false;
    for (Object o : c) {
      if (o instanceof PointXY) {
        PointXY p = (PointXY) o;
        wasRemoved |= remove(p);
      }
    }

    return wasRemoved;
  }

  @Override
  public boolean retainAll(Collection<?> c) {
    ContinuousLineSet cls = new ContinuousLineSet();

    for (Object o : c) {
      if (o instanceof PointXY && ps.contains(o)) {
        PointXY p = (PointXY) o;
        cls.add(p);
      }
    }

    int oldSize = ps.size();
    ps = cls.ps;
    fullPath = cls.fullPath;

    return size() != oldSize;
  }

  @Override
  public void clear() {
    ps.clear();
    fullPath = 0;
  }   
}

class PointXY { 
  public static PointXY of(int x, int y) {
    return new PointXY(x, y);
  }

  public final int x, y;
  private int hash;
  private boolean wasHashInit = false;

  private PointXY(int x, int y) {
    this.x = x;
    this.y = y;
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof PointXY))
      return false;
    PointXY p = (PointXY) obj;
    return x == p.x && y == p.y;
  }

  @Override
  public int hashCode() {
    if (!wasHashInit) {
      hash = 17;
      hash = 31 * hash + y;
      hash = 31 * hash + x;
      wasHashInit = true;
    }

    return hash;
  }

  @Override
  public String toString() {
    return String.format("(%d, %d)", x, y);
  }
}

答案 6 :(得分:-1)

public class Point实现Comparable

{ ...

... @override

public int compareTo(Pointarg0) {

    ....

}

... }

...