public static <N, E> double dijkstra(Graph<N, E> g, N startNode, N targetNode) {

        // get list of all nodes
        Collection<N> nodes = g.getNodes();

        // preconditions
        // make sure nodes exist
        assertTrue(nodes.contains(startNode));
        assertTrue(nodes.contains(targetNode));

        // initialize distances from start to nodes
        // d prefix indicates distance, not double
        Map<N, Double> dStartToNodeMap = new HashMap<>();
        for (N node : nodes) { dStartToNodeMap.put(node, Double.POSITIVE_INFINITY); }
        dStartToNodeMap.put(startNode, 0.0); // start to start is 0

        // init list of unvisited nodes
        List<N> unvisited = new ArrayList<>(nodes); // make copy

        // init start node
        double dStartToTarget = Double.POSITIVE_INFINITY;
        double dStartToCurrent = 0.0;
        N currentNode = startNode;

        // search graph until target found or all nodes visited
        while (true) {

            // update neighbor distances based on distance to current node
            dijkstraUpdateNeighbors(g, currentNode, unvisited, dStartToCurrent, dStartToNodeMap);

            // mark current node as visited
            unvisited.remove(currentNode);

            // if target has been visited, return its distance value
            if (! unvisited.contains(targetNode)) {
                dStartToTarget = dStartToNodeMap.get(targetNode);
                break;
            }

            // find unvisited node with lowest distance from start
            N nodeLowest = null;
            double dLowest = Double.POSITIVE_INFINITY;
            for (N node : nodes) {
                if (unvisited.contains(node)) {
                    double dStartToNode = dStartToNodeMap.get(node);
                    if (dStartToNode < dLowest) { dLowest = dStartToNode; nodeLowest = node; }
                }
            }

            // if there are no more unvisited nodes or all are at infinity, target is unreachable, so return infinity
            if (nodeLowest == null) { dStartToTarget = Double.POSITIVE_INFINITY; break; }

            // go to best unvisited node
            currentNode = nodeLowest;
            dStartToCurrent = dStartToNodeMap.get(currentNode);

        } // while

        return dStartToTarget;
    }