问题描述:
需要在N个村庄之间修建道路,给的每个村庄的坐标位置,求出最小修路费用
例:给定(0,0),(0,1),(1,0),(1,0),(0.5,0.5) 输出2.83
拙见:将输入的点保存到集合points中,然后将所有可行边保存到edges中,然后确定一个起点(这里将输入的第一个点作为起点),将该点保存到待选点集合tarPoints中,随后遍历该集合将该点能与该点有关的点全都保存到待选集合edge中。从集合中选出权值最小的一条边保存到解集target中,循环选出n-1条边后得到解。判断:当一条边已经访问则标记visited=true,当点连通后将点也标记visited=true,当一条边没被访问且权值最低但是所连接得两点已经连通则放弃该条边。
明显的缺点就是时间复杂度,希望得到大神的指点提拨以优化,谢谢!
设置一个Point类表示村庄点
class Point implements Comparable{ private double x, y; public boolean visited; public Point(double x, double y) { this.x = x; this.y = y; visited = false; } public double getX() { return x; } public double getY() { return y; } @Override public int compareTo(Object o) { Point point = (Point) o; if (x == point.getX() && y == point.getY()) return 1; else return -1; } @Override public String toString() { return "(" + x + " , " + y + ")"; } }
然后设置一个可以表示边的Edge类(无向图中的表示是边这里也引用无向图中的表示)
class Edge implements Comparable{ private Point start; private Point end; private double weight; public boolean visited; public Edge(Point start, Point end) { this.start = start; this.end = end; double x = Math.abs(start.getX() - end.getX()); double y = Math.abs(start.getY() - end.getY()); weight = Math.sqrt(((x * x) + (y * y))/1.0); visited = false; } public double getWeight() { return weight; } public Point getStart() { return start; } public Point getEnd() { return end; } @Override public int compareTo(Object o) { Edge edge = (Edge) o; if (weight == edge.weight) return 0; else if (weight > edge.weight) return 1; else return -1; } @Override public String toString() { return "("+start.getX() + " , " + start.getY() + ")" + "-->(" + end.getX() + " , " + end.getY() + ")"; } }
使用Prim算法的思想:
public class MinimumSpanningTree { public List<Point> points = new ArrayList<>();//保存所有村庄的坐标 public List<Point> tarPoints = new ArrayList<>();//保存所有已经连接的点 public List<Edge> edges = new ArrayList<>(); //生成一个村庄两两相连的图 public List<Edge> target = new ArrayList<>(); //最小生成树 public void init(){ Scanner scanner = new Scanner(System.in); System.out.println("请输入村庄的坐标点,输入 # 结束输入"); while (true) { String into = scanner.nextLine(); if (into .equals("#") ) break; String[] xy = into.split(","); double x = Double.parseDouble(xy[0]); double y = Double.parseDouble(xy[1]); Point point = new Point(x, y); points.add(point); } for (int i=0;i<points.size()-1;i++) { for (int j = i+ 1; j <points.size();j++) { Edge edge = new Edge(points.get(i), points.get(j)); edges.add(edge); } } } /* *普利姆算法实现: * 将集合得第一个点看作起点放入待选集合, * 列出所有能与该定点相连的边,并放入待选集合 * 遍历待选集合找到未被使用的边中权值最小的放入结果集, * 将使用过的边和点标记为已经访问,继续判断知道选出n-1条边(n为定点数) * */ public void prim() { points.get(0).visited = true; //将第一个点设置为起点 tarPoints.add(points.get(0)); //添加到解集 List<Edge> edge = new ArrayList<>(); for (int i=0;i<points.size()-1;i++) { //确定待选边 for (Point point : tarPoints) { for (Edge edge1 : edges) { if (!edge1.visited && (edge1.getStart() == point || edge1.getEnd() == point) && !edge.contains(edge1)) { edge.add(edge1); } } } Edge ret = null; double weight = Double.MAX_VALUE; //遍历待选边,确定权值最小边 for (Edge e : edge) { if (!e.visited && e.getWeight() < weight && (!e.getStart().visited || !e.getEnd().visited)) { weight = e.getWeight(); ret = e; } } ret.getEnd().visited = true;//将最小权重边连接的点标记 ret.getStart().visited = true; if (tarPoints.contains(ret.getEnd())) tarPoints.add(ret.getStart()); else tarPoints.add(ret.getEnd()); target.add(ret); //将边加到结果中 ret.visited = true; } } public void print() { double weight = 0; for (Edge edge : target) { System.out.println("("+edge.getStart().getX() + "," + edge.getStart().getY() + ")" + "-->(" + edge.getEnd().getX() + "," + edge.getEnd().getY() + ")"); weight += edge.getWeight(); } System.out.println("最小权重为:" + weight); } public static void main(String[] args) { MinimumSpanningTree mst = new MinimumSpanningTree(); mst.init(); mst.prim(); mst.print(); } }