狄克斯特拉算法

590 阅读2分钟

广度优先算法: 解决 对于非加权图, 找出两点之间最短路径

狄克斯特拉算法: 解决 加权图(非负向), 找出两点之间耗时最短的路径

问题: 找出起点——终点的耗时最短的路径

image.png

解决: 狄克斯特拉算法

  • 第一步: 假设你站在 起点, 你到A点耗时 6分钟, 到B点耗时2分钟, 那么选出耗时最短的节点B, 现在还不确定到终点的时间, 假设为无穷大

image.png

image.png

  • 第二步: 计算B点到附近节点的时间

image.png (此时,可以发现 从起点——A的最短路径 不是 6 而是从起点——B——A 耗时5分钟)

image.png

1. 前往节点A的更短路径(时间从6分钟缩短到5分钟)

2. 前往终点的更短路径(时间从无穷大缩短到7分钟)
  • 第三步: 重复以上步骤
    • 找出 B点到 附近节点耗时最短的节点A
    • 计算 A点到 附近节点的时间

image.png (此时,可以发现 从起点——终点的最短路径 不是 7 而是从起点——B——A——终点 耗时6分钟)

image.png

image.png

代码实现

第一步: 需要三个Hash表,第一个存储整个图graph, 第二个存储耗时costs, 第三个 更新路径parents

image.png

const graph: Graph = {
  start: { A: 6, B: 2 },
  A: { fin: 1 },
  B: { A: 3, fin: 5 },
  fin: {}
} // 图 
const costs: Graph = {} // 起点到各个点的耗时 
const parents: Graph = {} // 路径更新
const processed: Array<string> = [] // 保存处理过的节点, 以防出现 环图
Object.keys(graph).forEach(key => {
  if (key !== 'start') {
    costs[key] = graph.start[key]??Infinity
    parents[key] = graph.start[key] ? 'start' : null
  }
})

第二步: 从 耗时costs中找出 耗时最短的节点, 并且计算 和 更新 到附近节点的开销

function updateCosts () {
  const node: null|string = findLowsetCostNode(costs) // 获取耗时最短的节点
  if (!!node) {
    const cost = costs[node]
    const neigbors = graph[node]
    for(const n in neigbors) {
      const new_cost = cost + neigbors[n]
      if (new_cost < costs[n]) {
        costs[n] = new_cost
        parents[n] = node
      }
    }
    processed.push(node)
    updateCosts()
  }
}
updateCosts()
    1. 从起点(假设为O)开始, 得到 耗时最短的节点是B点
    1. 从B点出发, 计算B点到 附近节点(A点和终点)的耗时: 起点到B点耗时 + B点到该点的耗时 = 起点到该点的耗时,类似于向量的计算 : OB+BA=OA\overrightarrow{OB} + \overrightarrow{BA} = \overrightarrow{OA} 如果 耗时 小于costs表 则更新 值,同时更新 parents表

image.png image.png image.png

  • 3. 从 耗时表里 找出耗时 最点的节点 (B点已经被处理过,所以排出在外), 那么得到耗时最短的点是A点
    1. 请A点出发, 计算 A点 到 附近点(这里只有 终点了)的耗时, 和 第2步一样,计算 更新

image.png image.png

  • 5. 最后得出, 从起点到终点的耗时最短的时间是起点——B点——A点——终点, 共计耗时: 6分钟

完整代码:

const graph: Graph = {
  start: { A: 6, B: 2 },
  A: { fin: 1 },
  B: { A: 3, fin: 5 },
  fin: {}
} // 图 
const costs: Graph = {} // 起点到各个点的耗时 
const parents: Graph = {} // 路径更新
const processed: Array<string> = [] // 保存处理过的节点, 以防出现 环图
Object.keys(graph).forEach(key => {
  if (key !== 'start') {
    costs[key] = graph.start[key]??Infinity
    parents[key] = graph.start[key] ? 'start' : null
  }
})
// 找出耗时最点的节点
function findLowsetCostNode (costs: Graph): string|null {
  const keys:Array<string> = Object.keys(costs).filter(key => {
    return !processed.includes(key)
  })
  if (!keys.length) return null
  let min = 0
  for (let i = 1; i < keys.length; i++) {
    costs[keys[i]] < costs[keys[min]] && (min = i)
  }
  return keys[min]
}
// 主程序
function main () {
  const node: null|string = findLowsetCostNode(costs)
  if (!!node) {
    const cost = costs[node]
    const neigbors = graph[node]
    for(const n in neigbors) {
      const new_cost = cost + neigbors[n]
      if (new_cost < costs[n]) {
        costs[n] = new_cost
        parents[n] = node
      }
    }
    processed.push(node)
    updateCosts()
  }
}
main()

练习:找出起点到终点的耗时最短的路径

image.png

const graph: Graph = {
  start: {
    A: 5,
    B: 2,
  },
  A: {
    C: 4,
    D: 2
  },
  B: {
    A: 8,
    D: 7 
  },
  C: {
    D: 6,
    fin: 3
  },
  D: {
    fin: 1
  },
  fin: {}
}
// parents: { A: 'start', B: 'start', C: 'A', D: 'A', fin: 'D' }
// costs: { A: 5, B: 2, C: 9, D: 7, fin: 8 }

答案: 起点 —— A点 —— D点 —— 终点 共计耗时: 8分钟