File: DijkstraDistance.java

package info (click to toggle)
geogebra 4.0.34.0%2Bdfsg1-3
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd, stretch
  • size: 23,640 kB
  • ctags: 31,326
  • sloc: java: 221,026; xml: 786; sh: 116; makefile: 31
file content (524 lines) | stat: -rw-r--r-- 20,395 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
/*
 * Created on Jul 9, 2005
 *
 * Copyright (c) 2005, the JUNG Project and the Regents of the University 
 * of California
 * All rights reserved.
 *
 * This software is open-source under the BSD license; see either
 * "license.txt" or
 * http://jung.sourceforge.net/license.txt for a description.
 */
package edu.uci.ics.jung.algorithms.shortestpath;

import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;

import edu.uci.ics.jung.algorithms.util.BasicMapEntry;
import edu.uci.ics.jung.algorithms.util.MapBinaryHeap;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.Hypergraph;

/**
 * <p>Calculates distances in a specified graph, using  
 * Dijkstra's single-source-shortest-path algorithm.  All edge weights
 * in the graph must be nonnegative; if any edge with negative weight is 
 * found in the course of calculating distances, an 
 * <code>IllegalArgumentException</code> will be thrown.
 * (Note: this exception will only be thrown when such an edge would be
 * used to update a given tentative distance;
 * the algorithm does not check for negative-weight edges "up front".)
 * 
 * <p>Distances and partial results are optionally cached (by this instance)
 * for later reference.  Thus, if the 10 closest vertices to a specified source 
 * vertex are known, calculating the 20 closest vertices does not require 
 * starting Dijkstra's algorithm over from scratch.</p>
 * 
 * <p>Distances are stored as double-precision values.  
 * If a vertex is not reachable from the specified source vertex, no 
 * distance is stored.  <b>This is new behavior with version 1.4</b>;
 * the previous behavior was to store a value of 
 * <code>Double.POSITIVE_INFINITY</code>.  This change gives the algorithm
 * an approximate complexity of O(kD log k), where k is either the number of
 * requested targets or the number of reachable vertices (whichever is smaller),
 * and D is the average degree of a vertex.</p>
 * 
 * <p> The elements in the maps returned by <code>getDistanceMap</code> 
 * are ordered (that is, returned 
 * by the iterator) by nondecreasing distance from <code>source</code>.</p>
 * 
 * <p>Users are cautioned that distances calculated should be assumed to
 * be invalidated by changes to the graph, and should invoke <code>reset()</code>
 * when appropriate so that the distances can be recalculated.</p>
 * 
 * @author Joshua O'Madadhain
 * @author Tom Nelson converted to jung2
 */
public class DijkstraDistance<V,E> implements Distance<V>
{
    protected Hypergraph<V,E> g;
    protected Transformer/*<E,? extends Number>*/ nev;
    protected Map<V,SourceData> sourceMap;   // a map of source vertices to an instance of SourceData
    protected boolean cached;
    protected double max_distance;
    protected int max_targets;
    
    /**
     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
     * the specified graph and the specified method of extracting weights 
     * from edges, which caches results locally if and only if 
     * <code>cached</code> is <code>true</code>.
     * 
     * @param g     the graph on which distances will be calculated
     * @param nev   the class responsible for returning weights for edges
     * @param cached    specifies whether the results are to be cached
     */
    public DijkstraDistance(Hypergraph<V,E> g, Transformer/*<E,? extends Number>*/ nev, boolean cached) {
        this.g = g;
        this.nev = nev;
        this.sourceMap = new HashMap<V,SourceData>();
        this.cached = cached;
        this.max_distance = Double.POSITIVE_INFINITY;
        this.max_targets = Integer.MAX_VALUE;
    }
    
    /**
     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
     * the specified graph and the specified method of extracting weights 
     * from edges, which caches results locally.
     * 
     * @param g     the graph on which distances will be calculated
     * @param nev   the class responsible for returning weights for edges
     */
    public DijkstraDistance(Hypergraph<V,E> g, Transformer/*<E,? extends Number>*/ nev) {
        this(g, nev, true);
    }
    
    /**
     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
     * the specified unweighted graph (that is, all weights 1) which
     * caches results locally.
     * 
     * @param g     the graph on which distances will be calculated
     */ 
    @SuppressWarnings("unchecked")
    public DijkstraDistance(Graph<V,E> g) {
        this(g, new ConstantTransformer(1), true);
    }

    /**
     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
     * the specified unweighted graph (that is, all weights 1) which
     * caches results locally.
     * 
     * @param g     the graph on which distances will be calculated
     * @param cached    specifies whether the results are to be cached
     */ 
    @SuppressWarnings("unchecked")
    public DijkstraDistance(Graph<V,E> g, boolean cached) {
        this(g, new ConstantTransformer(1), cached);
    }
    
    /**
     * Implements Dijkstra's single-source shortest-path algorithm for
     * weighted graphs.  Uses a <code>MapBinaryHeap</code> as the priority queue, 
     * which gives this algorithm a time complexity of O(m lg n) (m = # of edges, n = 
     * # of vertices).
     * This algorithm will terminate when any of the following have occurred (in order
     * of priority):
     * <ul>
     * <li> the distance to the specified target (if any) has been found
     * <li> no more vertices are reachable 
     * <li> the specified # of distances have been found, or the maximum distance 
     * desired has been exceeded
     * <li> all distances have been found
     * </ul>
     * 
     * @param source    the vertex from which distances are to be measured
     * @param numDests  the number of distances to measure
     * @param targets   the set of vertices to which distances are to be measured
     */
    protected LinkedHashMap<V,Number> singleSourceShortestPath(V source, Collection<V> targets, int numDests)
    {
        SourceData sd = getSourceData(source);

        Set<V> to_get = new HashSet<V>();
        if (targets != null) {
            to_get.addAll(targets);
            Set<V> existing_dists = sd.distances.keySet();
            for(V o : targets) {
                if (existing_dists.contains(o))
                    to_get.remove(o);
            }
        }
        
        // if we've exceeded the max distance or max # of distances we're willing to calculate, or
        // if we already have all the distances we need, 
        // terminate
        if (sd.reached_max ||
            (targets != null && to_get.isEmpty()) ||
            (sd.distances.size() >= numDests))
        {
            return sd.distances;
        }
        
        while (!sd.unknownVertices.isEmpty() && (sd.distances.size() < numDests || !to_get.isEmpty()))
        {
            Map.Entry<V,Number> p = sd.getNextVertex();
            V v = p.getKey();
            double v_dist = p.getValue().doubleValue();
            to_get.remove(v);
            if (v_dist > this.max_distance) 
            {
                // we're done; put this vertex back in so that we're not including
                // a distance beyond what we specified
                sd.restoreVertex(v, v_dist);
                sd.reached_max = true;
                break;
            }
            sd.dist_reached = v_dist;

            if (sd.distances.size() >= this.max_targets)
            {
                sd.reached_max = true;
                break;
            }
            
            for (E e : getEdgesToCheck(v) )
            {
                for (V w : g.getIncidentVertices(e))
                {
                    if (!sd.distances.containsKey(w))
                    {
                        double edge_weight = ((Number) nev.transform(e)).doubleValue();
                        if (edge_weight < 0)
                            throw new IllegalArgumentException("Edges weights must be non-negative");
                        double new_dist = v_dist + edge_weight;
                        if (!sd.estimatedDistances.containsKey(w))
                        {
                            sd.createRecord(w, e, new_dist);
                        }
                        else
                        {
                            double w_dist = ((Double)sd.estimatedDistances.get(w)).doubleValue();
                            if (new_dist < w_dist) // update tentative distance & path for w
                                sd.update(w, e, new_dist);
                        }
                    }
                }
            }
        }
        return sd.distances;
    }

    protected SourceData getSourceData(V source)
    {
        SourceData sd = sourceMap.get(source);
        if (sd == null)
            sd = new SourceData(source);
        return sd;
    }
    
    /**
     * Returns the set of edges incident to <code>v</code> that should be tested.
     * By default, this is the set of outgoing edges for instances of <code>Graph</code>,
     * the set of incident edges for instances of <code>Hypergraph</code>,
     * and is otherwise undefined.
     */
    protected Collection<E> getEdgesToCheck(V v)
    {
        if (g instanceof Graph)
            return ((Graph<V,E>)g).getOutEdges(v);
        else
            return g.getIncidentEdges(v);

    }

    
    /**
     * Returns the length of a shortest path from the source to the target vertex,
     * or null if the target is not reachable from the source.
     * If either vertex is not in the graph for which this instance
     * was created, throws <code>IllegalArgumentException</code>.
     * 
     * @see #getDistanceMap(Object)
     * @see #getDistanceMap(Object,int)
     */
    public Number getDistance(V source, V target)
    {
        if (g.containsVertex(target) == false)
            throw new IllegalArgumentException("Specified target vertex " + 
                    target + " is not part of graph " + g);
        if (g.containsVertex(source) == false)
            throw new IllegalArgumentException("Specified source vertex " + 
                    source + " is not part of graph " + g);
        
        Set<V> targets = new HashSet<V>();
        targets.add(target);
        Map<V,Number> distanceMap = getDistanceMap(source, targets);
        return distanceMap.get(target);
    }
    

    /**
     * Returns a {@code Map} from each element {@code t} of {@code targets} to the 
     * shortest-path distance from {@code source} to {@code t}. 
     */
    public Map<V,Number> getDistanceMap(V source, Collection<V> targets)
    {
       if (g.containsVertex(source) == false)
            throw new IllegalArgumentException("Specified source vertex " + 
                    source + " is not part of graph " + g);
       if (targets.size() > max_targets)
            throw new IllegalArgumentException("size of target set exceeds maximum " +
                    "number of targets allowed: " + this.max_targets);
        
        Map<V,Number> distanceMap = 
        	singleSourceShortestPath(source, targets, 
        			Math.min(g.getVertexCount(), max_targets));
        if (!cached)
            reset(source);
        
        return distanceMap;
    }
    
    /**
     * <p>Returns a <code>LinkedHashMap</code> which maps each vertex 
     * in the graph (including the <code>source</code> vertex) 
     * to its distance from the <code>source</code> vertex.
     * The map's iterator will return the elements in order of 
     * increasing distance from <code>source</code>.</p>
     * 
     * <p>The size of the map returned will be the number of 
     * vertices reachable from <code>source</code>.</p>
     * 
     * @see #getDistanceMap(Object,int)
     * @see #getDistance(Object,Object)
     * @param source    the vertex from which distances are measured
     */
    public Map<V,Number> getDistanceMap(V source)
    {
        return getDistanceMap(source, Math.min(g.getVertexCount(), max_targets));
    }
    


    /**
     * <p>Returns a <code>LinkedHashMap</code> which maps each of the closest 
     * <code>numDist</code> vertices to the <code>source</code> vertex 
     * in the graph (including the <code>source</code> vertex) 
     * to its distance from the <code>source</code> vertex.  Throws 
     * an <code>IllegalArgumentException</code> if <code>source</code>
     * is not in this instance's graph, or if <code>numDests</code> is 
     * either less than 1 or greater than the number of vertices in the 
     * graph.</p>
     * 
     * <p>The size of the map returned will be the smaller of 
     * <code>numDests</code> and the number of vertices reachable from
     * <code>source</code>. 
     * 
     * @see #getDistanceMap(Object)
     * @see #getDistance(Object,Object)
     * @param source    the vertex from which distances are measured
     * @param numDests  the number of vertices for which to measure distances
     */
    public LinkedHashMap<V,Number> getDistanceMap(V source, int numDests)
    {

    	if(g.getVertices().contains(source) == false) {
            throw new IllegalArgumentException("Specified source vertex " + 
                    source + " is not part of graph " + g);
    		
    	}
        if (numDests < 1 || numDests > g.getVertexCount())
            throw new IllegalArgumentException("numDests must be >= 1 " + 
                "and <= g.numVertices()");

        if (numDests > max_targets)
            throw new IllegalArgumentException("numDests must be <= the maximum " +
                    "number of targets allowed: " + this.max_targets);
            
        LinkedHashMap<V,Number> distanceMap = 
        	singleSourceShortestPath(source, null, numDests);
                
        if (!cached)
            reset(source);
        
        return distanceMap;        
    }
    
    /**
     * Allows the user to specify the maximum distance that this instance will calculate.
     * Any vertices past this distance will effectively be unreachable from the source, in
     * the sense that the algorithm will not calculate the distance to any vertices which
     * are farther away than this distance.  A negative value for <code>max_dist</code> 
     * will ensure that no further distances are calculated.
     * 
     * <p>This can be useful for limiting the amount of time and space used by this algorithm
     * if the graph is very large.</p>
     * 
     * <p>Note: if this instance has already calculated distances greater than <code>max_dist</code>,
     * and the results are cached, those results will still be valid and available; this limit
     * applies only to subsequent distance calculations.</p>
     * @see #setMaxTargets(int)
     */
    public void setMaxDistance(double max_dist)
    {
        this.max_distance = max_dist;
        for (V v : sourceMap.keySet())
        {
            SourceData sd = sourceMap.get(v);
            sd.reached_max = (this.max_distance <= sd.dist_reached) || (sd.distances.size() >= max_targets);
        }
    }
       
    /**
     * Allows the user to specify the maximum number of target vertices per source vertex 
     * for which this instance will calculate distances.  Once this threshold is reached, 
     * any further vertices will effectively be unreachable from the source, in
     * the sense that the algorithm will not calculate the distance to any more vertices.  
     * A negative value for <code>max_targets</code> will ensure that no further distances are calculated.
     * 
     * <p>This can be useful for limiting the amount of time and space used by this algorithm
     * if the graph is very large.</p>
     * 
     * <p>Note: if this instance has already calculated distances to a greater number of 
     * targets than <code>max_targets</code>, and the results are cached, those results 
     * will still be valid and available; this limit applies only to subsequent distance 
     * calculations.</p>
     * @see #setMaxDistance(double)
     */
    public void setMaxTargets(int max_targets)
    {
        this.max_targets = max_targets;
        for (V v : sourceMap.keySet())
        {
            SourceData sd = sourceMap.get(v);
            sd.reached_max = (this.max_distance <= sd.dist_reached) || (sd.distances.size() >= max_targets);
        }
    }
    
    /**
     * Clears all stored distances for this instance.  
     * Should be called whenever the graph is modified (edge weights 
     * changed or edges added/removed).  If the user knows that
     * some currently calculated distances are unaffected by a
     * change, <code>reset(V)</code> may be appropriate instead.
     * 
     * @see #reset(Object)
     */
    public void reset()
    {
        sourceMap = new HashMap<V,SourceData>();
    }
        
    /**
     * Specifies whether or not this instance of <code>DijkstraShortestPath</code>
     * should cache its results (final and partial) for future reference.
     * 
     * @param enable    <code>true</code> if the results are to be cached, and
     *                  <code>false</code> otherwise
     */
    public void enableCaching(boolean enable)
    {
        this.cached = enable;
    }
    
    /**
     * Clears all stored distances for the specified source vertex 
     * <code>source</code>.  Should be called whenever the stored distances
     * from this vertex are invalidated by changes to the graph.
     * 
     * @see #reset()
     */
    public void reset(V source)
    {
        sourceMap.put(source, null);
    }

    /**
     * Compares according to distances, so that the BinaryHeap knows how to 
     * order the tree.  
     */
    protected static class VertexComparator<V> implements Comparator<V>
    {
        private Map<V,Number> distances;
        
        protected VertexComparator(Map<V,Number> distances)
        {
            this.distances = distances;
        }

        public int compare(V o1, V o2)
        {
            return ((Double) distances.get(o1)).compareTo((Double) distances.get(o2));
        }
    }
    
    /**
     * For a given source vertex, holds the estimated and final distances, 
     * tentative and final assignments of incoming edges on the shortest path from
     * the source vertex, and a priority queue (ordered by estimated distance)
     * of the vertices for which distances are unknown.
     * 
     * @author Joshua O'Madadhain
     */
    protected class SourceData
    {
        protected LinkedHashMap<V,Number> distances;
        protected Map<V,Number> estimatedDistances;
        protected MapBinaryHeap<V> unknownVertices;
        protected boolean reached_max = false;
        protected double dist_reached = 0;

        protected SourceData(V source)
        {
            distances = new LinkedHashMap<V,Number>();
            estimatedDistances = new HashMap<V,Number>();
            unknownVertices = new MapBinaryHeap<V>(new VertexComparator<V>(estimatedDistances));
            
            sourceMap.put(source, this);
            
            // initialize priority queue
            estimatedDistances.put(source, new Double(0)); // distance from source to itself is 0
            unknownVertices.add(source);
            reached_max = false;
            dist_reached = 0;
        }
        
        protected Map.Entry<V,Number> getNextVertex()
        {
            V v = unknownVertices.remove();
            Double dist = (Double)estimatedDistances.remove(v);
            distances.put(v, dist);
            return new BasicMapEntry<V,Number>(v, dist);
        }
        
        protected void update(V dest, E tentative_edge, double new_dist)
        {
            estimatedDistances.put(dest, new_dist);
            unknownVertices.update(dest);
        }
        
        protected void createRecord(V w, E e, double new_dist)
        {
            estimatedDistances.put(w, new_dist);
            unknownVertices.add(w);
        }
        
        protected void restoreVertex(V v, double dist) 
        {
            estimatedDistances.put(v, dist);
            unknownVertices.add(v);
            distances.remove(v);
        }
    }
}