Trova la connessione più veloce a un certo punto

1

Quello che sto cercando di implementare è un programma che sta cercando la connessione più veloce da una stazione all'altra in un dato momento della giornata.

Ho un numero di stazioni n e un numero m di linee che collegano queste stazioni. Ogni riga ha (departStation, arrivalStation, busDepartTime, busTravelTime) .

Quando voglio cercare una connessione dalla stazione A a B avrò l'input (from, to, myDepartTime) .

Se ho iniziato a pensare correttamente, le stazioni saranno Vertex e ogni linea sarà un vantaggio. Volevo utilizzare l'algoritmo di Dijkstra per implementarlo, ma troverò il tempo di viaggio più breve di un autobus (vedi esempio 1).

Cosa devo cambiare nel codice per ottenere il risultato desiderato come nell'esempio 1? C'è qualche altro algoritmo che posso usare? Forse uno migliore? O solo qualche consiglio?

Devo sapere se sono sulla buona strada. Sto solo cercando di creare un semplice programma da riga di comando, niente di complesso.

Sto pensando di calcolare il tempo in cui devo aspettare che l'autobus parte e la durata totale del viaggio e quindi scegliere il più breve da tutti. L'unico problema è che non capisco davvero come posso implementarlo.

Esempio 1:

4 stazioni: 0, 1, 2, 3

4 linee (connessioni)

Nota: i tempi di partenza sono indicati in minuti dall'inizio del giorno (120 - > 02:00)

(departStation, arrivalStation, busDepartTime, busTravelTime)

(0, 1, 10, 30), (0, 1, 70, 30), (0, 2, 130, 10), (1, 2, 60, 30), (1, 3, 100, 60), (3, 1, 30, 20)

Ora devo passare da 0 a 2 a 00 ( 0, 2, 00 ) L'algoritmo di Dijkstra che ho restituirà il busTravelTime più breve che è 30 e stamperà il percorso 0 - > 2(10) . È fantastico, ma non è buono perché devo aspettare 130 minuti. Quello che sarà bello è ottenere il percorso 0 -> 1(30) -> 2(60) perché il bus parte a 30 e devo aspettare solo 10 minuti.

Ecco il codice:

import java.util.*;

public class Dijkstra2 {
    private static final Graph.Edge[] GRAPH = {
        new Graph.Edge(0, 1, 10, 30),
        new Graph.Edge(0, 1, 70, 30),
        new Graph.Edge(0, 2, 130, 10),
        new Graph.Edge(1, 2, 60, 30),
        new Graph.Edge(1, 3, 100, 60),
        new Graph.Edge(3, 1, 30, 20)
    };
    private static final int START = 0;
    private static final int END = 2;

    public static void main(String[] args) {
        Graph g = new Graph(GRAPH);
        g.dijkstra(START);
        g.printPath(END);
    }
}

class Graph {
    private final Map<Integer, Vertex> graph; // mapping of vertex names to Vertex objects, built from a set of Edges

/** One edge of the graph (only used by Graph constructor) */
    public static class Edge {
        public final int v1, v2;
        public final int busTravelTime;
        public  int busDepartTime;

        public Edge(int v1, int v2, int busDepartTime, int busTravelTime) {
            this.v1 = v1;
            this.v2 = v2;
            this.busTravelTime = busTravelTime;
            this.busDepartTime = busDepartTime;
        }
    }

/** One vertex of the graph, complete with mappings to neighbouring vertices */
public static class Vertex implements Comparable<Vertex> {
    public final int stationNumber;
    public int busTravTime = Integer.MAX_VALUE; // MAX_VALUE assumed to be infinity
    public Vertex previous = null;
    public final Map<Vertex, Integer> neighbours = new HashMap<>();

    public Vertex(int stationNumber) {
        this.stationNumber = stationNumber;
    }

    private void printPath() {
        if (this == this.previous) {
            System.out.printf("%s", this.stationNumber);
        } else {
            this.previous.printPath();
            System.out.printf(" -> %s(%d)", this.stationNumber, this.busTravTime);
        }
    }

    public int compareTo(Vertex other) {
        return Integer.compare(busTravTime, other.busTravTime);
    }
}


public Graph(Edge[] edges) { //build the graph
    graph = new HashMap<>(edges.length);

    //one pass to find all vertices
    for (Edge e : edges) {
        if (!graph.containsKey(e.v1)) graph.put(e.v1, new Vertex(e.v1));
        if (!graph.containsKey(e.v2)) graph.put(e.v2, new Vertex(e.v2));
    }

    //another pass to set neighbouring vertices
    for (Edge e : edges) {
        graph.get(e.v1).neighbours.put(graph.get(e.v2), e.busTravelTime);
    }
}

/** Runs dijkstra using a specified source vertex */
public void dijkstra(int startStation) {
    if (!graph.containsKey(startStation)) {
        System.err.printf("Graph doesn't contain start vertex \"%s\"\n", startStation);
        return;
    }
    final Vertex source = graph.get(startStation);
    NavigableSet<Vertex> q = new TreeSet<>();

    // set-up vertices
    for (Vertex v : graph.values()) {
        v.previous = v == source ? source : null;
        v.busTravTime = v == source ? 0 : Integer.MAX_VALUE;
        q.add(v);
    }

    dijkstra(q);
}

    private void dijkstra(final NavigableSet<Vertex> q) {/** dijkstra's algorithm using a binary heap. */
        Vertex u, v;
        while (!q.isEmpty()) {

            u = q.pollFirst(); // vertex with shortest travel time (first iteration will return source)
            if (u.busTravTime == Integer.MAX_VALUE) break; // we can ignore u (and any other remaining vertices) since they are unreachable

        //look at distances to each neighbour
            for (Map.Entry<Vertex, Integer> a : u.neighbours.entrySet()) {
                v = a.getKey(); //the neighbour in this iteration

            final int alternateDist = u.busTravTime + a.getValue();
            if (alternateDist < v.busTravTime) { // shorter path to neighbour found
                q.remove(v);
                v.busTravTime = alternateDist;
                v.previous = u;
                q.add(v);
            }
        }
    }
}

    public void printPath(int endStation) {
        graph.get(endStation).printPath();
        System.out.println();
    }
}
    
posta Andrew 05.05.2016 - 11:29
fonte

1 risposta

1

Sì, penso che questo sia risolvibile con una variante dell'algoritmo di Dijkstra in cui il passaggio all'indietro del grafico calcola il miglior orario di arrivo alla destinazione per ciascun bordo del grafico (dove tale margine può comportare l'arrivo alla destinazione).

Innanzitutto vorrei introdurre un nuovo tipo di dati che estenda il margine del grafico per memorizzare il tempo di arrivo all'obiettivo.

class GoalEdge extends Graph.Edge {

    final int goalArrivalTime;

    GoalEdge(final Graph.Edge edge, final int goalArrivalTime) {
        super(edge.v1, edge.v2, edge.busDepartTime, edge.busTravelTime);
        this.goalArrivalTime = goalArrivalTime;
    }
}

Successivamente scriverei il passaggio all'indietro sul grafico che crea una collezione di questi nuovi bordi che contengono le informazioni extra.

class BackwardsPass {

    private final Graph.Edge[] graph;
    private final List<GoalEdge> results = new ArrayList<>();
    private final Queue<Integer> nodesToProcess = new ArrayDeque<>();
    private final Set<Integer> processedNodes = new HashSet<>();

    BackwardsPass(final Graph.Edge[] graph) {
        this.graph = graph;
    }

    Collection<GoalEdge> goalEdgesTo(final int goal) {

        results.clear();
        nodesToProcess.clear();
        processedNodes.clear();

        buildGoalEdgesToGoal(goal);
        buildGoalEdges();

        return results;
    }

    private void buildGoalEdgesToGoal(final int goal) {
        edgesTo(goal)
                .map(edge -> new GoalEdge(edge, edge.busDepartTime + edge.busTravelTime))
                .forEach(results::add);
        queueForProcessingEdgesTo(goal);
        processed(goal);
    }

    private Stream<Graph.Edge> edgesTo(final int destination) {
        return stream(graph).filter(edge -> edge.v2 == destination);
    }

    private void queueForProcessingEdgesTo(final int node) {
        edgesTo(node).forEach(edge -> nodesToProcess.add(edge.v1));
    }

    private void processed(final int node) {
        processedNodes.add(node);
    }

    private void buildGoalEdges() {
        while (!nodesToProcess.isEmpty()) {
            final int nodeToProcess = nodesToProcess.remove();
            if (processedNodes.contains(nodeToProcess)) continue;

            edgesTo(nodeToProcess).forEach(this::buildGoalEdgeFor);
            queueForProcessingEdgesTo(nodeToProcess);
            processed(nodeToProcess);
        }
    }

    private void buildGoalEdgeFor(final Graph.Edge edge) {
        final Optional<GoalEdge> bestOnwardsEdge = bestOnwardEdge(edge);
        if (!bestOnwardsEdge.isPresent()) return;

        final int bestGoalArrivalTime = bestOnwardsEdge.get().goalArrivalTime;
        results.add(new GoalEdge(edge, bestGoalArrivalTime));
    }

    private Optional<GoalEdge> bestOnwardEdge(final Graph.Edge edge) {
        final int arrivalTime = edge.busDepartTime + edge.busTravelTime;
        return results.stream()
                .filter(goalEdge -> goalEdge.v1 == edge.v2)
                .filter(goalEdge -> goalEdge.busDepartTime >= arrivalTime)
                .min((e1, e2) -> e1.goalArrivalTime - e2.goalArrivalTime);
    }
}

Infine, scriverei il forward forward sui bordi appena calcolati in questo modo.

class BusSolver {

    private final Graph.Edge[] graph;

    private Collection<GoalEdge> goalEdges;

    BusSolver(final Graph.Edge... graph) {
        this.graph = graph;
    }

    Route solveFor(final int start, final int startTime, final int goal) {
        goalEdges = new BackwardsPass(graph).goalEdgesTo(goal);
        return findRouteFrom(start, startTime, goal);
    }

    private Route findRouteFrom(final int start,
                                final int startTime,
                                final int goal) {
        int currentNode = start;
        int currentTime = startTime;
        final List<Hop> results = new ArrayList<>();
        while (currentNode != goal) {

            final Optional<GoalEdge> bestGoalEdge = bestGoalEdgeFrom(currentNode, currentTime);
            if (!bestGoalEdge.isPresent()) return null; // TODO - Signal there is no route

            results.add(new Hop(bestGoalEdge.get().busDepartTime, bestGoalEdge.get().v2));
            currentNode = bestGoalEdge.get().v2;
            currentTime = bestGoalEdge.get().busDepartTime + bestGoalEdge.get().busTravelTime;
        }
        return new Route(results);
    }

    private Optional<GoalEdge> bestGoalEdgeFrom(final int currentNode, final int currentTime) {
        return goalEdges.stream()
                .filter(edge -> edge.v1 == currentNode)
                .filter(entry -> entry.busDepartTime >= currentTime)
                .min((x, y) -> x.goalArrivalTime - y.goalArrivalTime);
    }
}

Questo dovrebbe darti il risultato corretto per il tuo esempio che credo sia:

  1. Attendi fino a 10, quindi passa da 0 a 1.
  2. Attendi fino a 60, quindi passa dall'1 al 2.
risposta data 13.09.2016 - 11:32
fonte

Leggi altre domande sui tag