MaiDeveloper

Dijkstra's Shortest Path Algorithm

What is Dijkstra's Algorithm?

Dijkstra's algorithm is an algorithm for finding the shortest paths between nodes in a graph, which may represent, for example, road networks. It was conceived by computer scientist Edsger W. Dijkstra in 1956 and published three years later. The algorithm exists in many variants.
wikipedia

Scenario

Basically, Dijkstra's algorithm determine the fastest path from one location to another. To demonstrate a real life example of using this algorithm. Here I will present an example of Governors Island in New York City, New York. In this example, I will exclude all other factors, such as speed, weather, construction, etc.

I took a screenshot of the Governors Island from Google map; and then I marked and drew a couple of places and routes using photoshop. Using the Google map, I was able to measure the distance between each locations. So I also labeled the distances.

govern-island-map

Problem

I went to Governors Island in summer 2017 and 2019. When I was there, I have the following questions:

  1. What is the shortest path to get to Bike Rentals from Soissons Landing?
  2. After I rent a bike, I want to ride to Kayak Dock. What is the fastest path from Bike Rentals to Kayak Docke?
  3. After a fun ride, I need to go to Picnic Point where I can have something to eat. What is the shortest path from Kayak Dock to Picnic Point?
  4. After having a picnic, I realize I need to return the bike as soon as possible, otherwise I have to pay for another hour (approximately $48). What is the shortest path from Picnic Point to Bike Rentals?

After we implemented the solution, it should answer the above questions.

Graph

First of all, we need some kind of data. And the graph data already presented on the map; we need to convert it to object in JavaScript. For simplicity, readability, and error-free; I store the name of the locations in a constant variable.

const NAME = {
  SOISSONS_LANDING: 'Soissons Landing',
  CASTLE_WILLIAMS: 'Castle Williams',
  KAYAK_DOCK: 'Kayak Dock',
  COLONELS_ROW: 'Colonels Row',
  NOLAN_PARK: 'Nolan Park',
  BIKE_RENTALS: 'Bike Rentals',
  TEACHING_GARDEN: 'Teaching Garden',
  YANKEE_PIER: 'Yankee Pier',
  THE_HILLS: 'The Hills',
  PICNIC_POINT: 'Picnic Point'
};

Next, let's construct our graph data. It stores the location name and the next location name and distance. For instance, from Soissons Landing, we can reach four locations: Castle Williams, Colonels Row, Nolan Park, and Kayak Dock. It should be more like this.

const soissonLanding = {
	castleWilliams: 1376,
    colonelsRow: 1513,
    nolanPark: 1200,
    kayakDock: 1063
};

We have more than one location, so we are going to store them in a variable called graph.

const graph = {
  [NAME.SOISSONS_LANDING]: {
    [NAME.CASTLE_WILLIAMS]: 1376,
    [NAME.COLONELS_ROW]: 1513,
    [NAME.NOLAN_PARK]: 1200,
    [NAME.KAYAK_DOCK]: 1063,  
  },
  [NAME.CASTLE_WILLIAMS]: {
    [NAME.SOISSONS_LANDING]: 1376,
    [NAME.COLONELS_ROW]: 1143,
  },
  [NAME.COLONELS_ROW]: {
    [NAME.SOISSONS_LANDING]: 1513,
    [NAME.BIKE_RENTALS]: 1176,
    [NAME.CASTLE_WILLIAMS]: 1143,
    [NAME.NOLAN_PARK]: 1700,
  },
  [NAME.NOLAN_PARK]: {
    [NAME.SOISSONS_LANDING]: 1200,
    [NAME.COLONELS_ROW]: 1700,
    [NAME.KAYAK_DOCK]: 820,
    [NAME.YANKEE_PIER]: 1676,
  },
  [NAME.KAYAK_DOCK]: {
    [NAME.SOISSONS_LANDING]: 1063,
    [NAME.NOLAN_PARK]: 820,
  },
  [NAME.BIKE_RENTALS]: {
    [NAME.COLONELS_ROW]: 1176,
    [NAME.YANKEE_PIER]: 652,
    [NAME.TEACHING_GARDEN]: 1117,
  },
  [NAME.YANKEE_PIER]: {
    [NAME.BIKE_RENTALS]: 652,
    [NAME.NOLAN_PARK]: 1676,
    [NAME.TEACHING_GARDEN]: 1049,
    [NAME.PICNIC_POINT]: 2862,
  },
  [NAME.TEACHING_GARDEN]: {
    [NAME.BIKE_RENTALS]: 1117,
    [NAME.YANKEE_PIER]: 1049,
    [NAME.THE_HILLS]: 1053,
    [NAME.PICNIC_POINT]: 1796,
  },
  [NAME.THE_HILLS]: {
    [NAME.TEACHING_GARDEN]: 1053,
    [NAME.PICNIC_POINT]: 1243,
  },
  [NAME.PICNIC_POINT]: {
    [NAME.THE_HILLS]: 1243,
    [NAME.TEACHING_GARDEN]: 1796,
    [NAME.YANKEE_PIER]: 2862,
  },
};

Approach

From the start location, loop through each path, and keep the minimum distance path; and at the same time, keep a reference of both locations (current location to next location, next location to previous location; for example: parent <-> child). Also keep a list of visited locations; at this point, start location is already visited. So set start location as visited.

Repeat this step on possible paths that is unvisited and is the minimum distance path.

At the end, we have the minimum distance from start location to the finish location. Now we can retrieve the shortest path by using the references that we kept earlier.

Solution

/**
 * @typeof {object} ShortestPathResult
 * @param {number}    distance
 * @param {string[]}  paths
 */

/**
 * Get the shortest path from start location to finish location
 * 
 * @param {object} graph 
 * @param {string} start 
 * @param {string} finish 
 * 
 * @return {ShortestPathResult}
 */
 const shortestPath = (graph, start, finish) => {
  
  // RETURN UNDEFINED
  // IF START OR FINISH LOCATION DOES NOT EXISTS
  if (graph[start] === undefined || graph[finish] === undefined) {
    return;
  }

  // KEEP TRACK OF MINIMUM DISTANCE FOR EACH POSSIBLE PATH
  const distances = {
    [finish]: Infinity,
    ...graph[start],
  };

  // KEEP REFERENCE OF
  // CURRENT LOCATION TO NEXT LOCATION
  // NEXT LOCATION TO PREVIOUS LOCATION
  // EXAMPLE: PARENT <-> CHILD
  const parents = {
    [finish]: null,
  };

  // CREATE A REFERENCE FOR THE START LOCATION AND NEXT LOCATIONS
  for (const child in graph[start]) {
    parents[child] = start;
  }

  // KEEP A LIST OF LOCATIONS THAT HAS BEEN PROCESSED 
  // WE SKIP START AND FINISH LOCATION
  const visited = { [start]: true, [finish]: true };

  // GET THE UNVISITED MINIMUM DISTANCE LOCATION
  let node = getMinimumDistanceLocation(distances, visited);

  while (node !== null) {
    // GET THE CURRENT DISTANCE
    let currentDistance = distances[node];

    // GET ITS ADJACENT LOCATIONS
    const locations = graph[node];

    for (const name in locations) {
      // TOTAL DISTANCE = CURRENT DISTANCE + NEXT LOCATION DISTANCE
      let totalDistance = currentDistance + locations[name];


      if (name !== start && (distances[name] === undefined || totalDistance < distances[name])) {
        // KEEP THE SHORTEST DISTANCE
        distances[name] = totalDistance;
        parents[name] = node;
      }
    }

    // SET THIS LOCATION AS VISITED
    visited[node] = true;

    // GET THE NEXT UNVISITED MINIMUM DISTANCEE PATH
    node = getMinimumDistanceLocation(distances, visited);
  }

  // HOLD SHORTEST PATH'S LOCATIONS
  const paths = [];
  // RETRIEVE THE PATH IN REVERSE ORDER
  let previousNode = finish;

  // BACK TRACE TO THE START LOCATION
  while (previousNode) {
    paths.unshift(previousNode);
    previousNode = parents[previousNode];
  }

  // RETURN THE DISTANCE AND THE SHORTEST PATH
  return {
    distance: distances[finish] || 0,
    paths,
  };
};

/**
 * Get the minimum distance of the given locations
 * 
 * @param {object} locations  // Ex. { A: 2, B: 3, C: 4}
 * @param {object} visited    // Ex. { B: true }
 * @return {string} the name of the location
 */
const getMinimumDistanceLocation = (locations, visited) => {
  let minimum = null;

  for (const name in locations) {
    if (!visited[name] && (minimum === null || locations[name] < locations[minimum])) {
      minimum = name;
    }
  }

  return minimum;
};

Answers to the Questions

  1. What is the shortest path to get to Bike Rentals from Soissons Landing?
shortestPath(graph, NAME.SOISSONS_LANDING, NAME.BIKE_RENTALS);

// Output
{
  distance: 2689,
  paths: [
    'Soissons Landing',
    'Colonels Row',
    'Bike Rentals'
  ]
}

govern-island-map

  1. After I rent a bike, I want to ride to Kayak Dock. What is the fastest path from Bike Rentals to Kayak Docke?
shortestPath(graph, NAME.BIKE_RENTALS, NAME.KAYAK_DOCK);

// Output
{
  distance: 3148,
  paths: [
    'Bike Rentals',
    'Yankee Pier',
    'Nolan Park',
    'Kayak Dock'
  ]
}

govern-island-map

  1. After a fun ride, I need to go to Picnic Point where I can have something to eat. What is the shortest path from Kayak Dock to Picnic Point?
shortestPath(graph, NAME.KAYAK_DOCK, NAME.PICNIC_POINT);

// Output
{
  distance: 5341,
  paths: [
    'Kayak Dock',
    'Nolan Park',
    'Yankee Pier',
    'Teaching Garden',
    'Picnic Point'
  ]
}

govern-island-map

  1. After having a picnic, I realize I need to return the bike as soon as possible, otherwise I have to pay for another hour (approximately $48). What is the shortest path from Picnic Point to Bike Rentals?
shortestPath(graph, NAME.PICNIC_POINT, NAME.BIKE_RENTALS);

// Output
{
  distance: 2913,
  paths: [
    'Picnic Point',
    'Teaching Garden',
    'Bike Rentals'
  ]
}

govern-island-map

Real World

In real world, it would become more complicated. There are bewildering number of paths. Each path are connected to each other in intersection (a dot/point in the picture below).

govern-island-map


Mike Mai
Mike Mai   Brooklyn, New York
I am full-stack web developer, passionate about building world class web applications. Knowledge in designing, coding, testing, and debugging. I love to solve problems.