public Double traverse(GraphNode2<T> source, GraphNode2<T> sink) {
        // if the graph is empty do nothing
        if (source == null) { return null; }
        if (sink == source) { return 0.0; }
        
        // initialization
        frontLine.clear();
        finalized.clear();        
        
        // visit the root
        frontLine.push(source, 0.0);
        finalized.add(source);
        
        while (!frontLine.isEmpty()) {
            // take out the node with smallest tentative distance
            WeightedValue<GraphNode2<T>, Double> parentAndWeight = frontLine.pop();
            GraphNode2<T> parent = parentAndWeight.value;
            Double parentDist = parentAndWeight.weight;
            if (parent == sink) { return parentDist; }
            finalized.add(parent);
            
            // add children to the priority queue
            for (WeightedValue<GraphNode2<T>,Double> edge : parent.edges) {
                GraphNode2<T> child = edge.value;
                if (finalized.contains(child)) { continue; }
                Double oldDist = frontLine.getPriority(child);
                Double newDist = parentDist + edge.weight;
                // update the distance of current child if either it has never
                // visited or it is not finalized yet (but still in frontLine)
                if (oldDist == null || newDist < oldDist) {
                    frontLine.push(child, newDist);
                }
            } // for
        } // while
        return null;
    }