简体   繁体   中英

implementing dijkstra's algorithm using a priority queue

So I'm trying to implement Dijkstra's algorithm. I understand Dijkstra's works, but I struggle to turn the concept into code. I have what I thought would be the correct code, I'm getting an out-of-memory exception on the java heap space, and I'm not sure why. I also just lost all confidence in my implementation, so any feedback would be great

    protected Path dijkstrasShortestPath(T start, T end) {
        if (start == null || end == null) {
            throw new NoSuchElementException("Vertices cannot have null data");
        }
        if (vertices.get(start) == null || vertices.get(end) == null) {
            throw new NoSuchElementException("Vertices do not exist");
        }
        PriorityQueue<Path> pq = new PriorityQueue<Path>();
        LinkedList<Vertex> visited = new LinkedList<Vertex>();
        Path startPath = new Path(vertices.get(start));
        visited.add(startPath.start);
        pq.add(startPath);
        while (!pq.isEmpty()) {
            Path front = pq.poll();
            visited.add(front.end);
            if (front.end.data.equals(end)) {
                return front;
            } else {
                for (int i = 0; i < front.end.edgesLeaving.size(); i++) {
                    if (!visited.contains(front.end.edgesLeaving.get(i).target)) {
                        pq.add(new Path(front, front.end.edgesLeaving.get(i)));
                    }
                }
            }
        }

        throw new NoSuchElementException("No such path from start to end exists");
    }

these are some other classes and fields that I use

/**
     * Vertex objects group a data field with an adjacency list of weighted
     * directed edges that lead away from them.
     */
    protected class Vertex {
        public T data; // vertex label or application specific data
        public LinkedList<Edge> edgesLeaving;

        public Vertex(T data) {
            this.data = data;
            this.edgesLeaving = new LinkedList<>();
        }
    }

    /**
     * Edge objects are stored within their source vertex, and group together
     * their target destination vertex, along with an integer weight.
     */
    protected class Edge {
        public Vertex target;
        public int weight;

        public Edge(Vertex target, int weight) {
            this.target = target;
            this.weight = weight;
        }
    }

    protected Hashtable<T, Vertex> vertices; // holds graph verticies, key=data

and this is the path class

/**
     * Path objects store a discovered path of vertices and the overall distance of cost
     * of the weighted directed edges along this path. Path objects can be copied and extended
     * to include new edges and vertices using the extend constructor. In comparison to a
     * predecessor table which is sometimes used to implement Dijkstra's algorithm, this
     * eliminates the need for tracing paths backward from the destination vertex to the
     * starting vertex at the end of the algorithm.
     */
    protected class Path implements Comparable<Path> {
        public Vertex start; // first vertex within path
        public int distance; // sumed weight of all edges in path
        public List<T> dataSequence; // ordered sequence of data from vertices in path
        public Vertex end; // last vertex within path

        /**
         * Creates a new path containing a single vertex.  Since this vertex is both
         * the start and end of the path, its initial distance is zero.
         * @param start is the first vertex on this path
         */
        public Path(Vertex start) {
            this.start = start;
            this.distance = 0;
            this.dataSequence = new LinkedList<>();
            this.dataSequence.add(start.data);
            this.end = start;
        }

        /**
         * This extension constructor makes a copy of the path passed into it as an argument
         * without affecting the original path object (copyPath). The path is then extended
         * by the Edge object extendBy.
         * @param copyPath is the path that is being copied
         * @param extendBy is the edge the copied path is extended by
         */
        public Path(Path copyPath, Edge extendBy) {
            this.start = copyPath.start;
            this.start.edgesLeaving.add(extendBy);
            this.distance = extendBy.weight + copyPath.distance;
            this.dataSequence = new LinkedList<>();
            for (int i = 0; i < copyPath.dataSequence.size(); i++) {
                this.dataSequence.add(copyPath.dataSequence.get(i));
            }
            this.end = extendBy.target;
            this.dataSequence.add(end.data);
        }

        /**
         * Allows the natural ordering of paths to be increasing with path distance.
         * When path distance is equal, the string comparison of end vertex data is used to break ties.
         * @param other is the other path that is being compared to this one
         * @return -1 when this path has a smaller distance than the other,
         *         +1 when this path has a larger distance than the other,
         *         and the comparison of end vertex data in string form when these distances are tied
         */
        public int compareTo(Path other) {
            int cmp = this.distance - other.distance;
            if(cmp != 0) return cmp; // use path distance as the natural ordering
            // when path distances are equal, break ties by comparing the string
            // representation of data in the end vertex of each path
            return this.end.data.toString().compareTo(other.end.data.toString());
        }
    }

I didn't look deeply into the code and I don't remember how dijkstra works, but it seems to me very suspicious that you do not have equals and hashCode defined for your classes. I didn't find where you may use it but maybe I've overlooked.

Also you have this line: if (front.end.data.equals(end)) {
Here we may have a problem in case when T does not have equals overload. I'm not sure, which type you use for data, so it may or may not be a problem.

I think the problem is caused by this.start.edgesLeaving.add(extendBy) in the Path(Path, Edge) constructor. In the first iteration of the while loop, a path consisting of just the start vertex is considered. In the for loop, the algorithm then iterates over all edges leaving from the end vertex of that path, where the end vertex and the start vertex are equal. For every of these leaving edges, a new path is created with the Path(Path, Edge) constructor and every time, a new element is added to the edgesLeaving list of the start vertex. This causes the value of front.end.edgesLeaving.size() to grow by one element, so the execution never leaves the for loop and on every iteration, edgesLeaving grows by one, eventually exhausting heap space.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM