发布于 2026-01-06 3 阅读
0

使用 Go 语言堆实现 Dijkstra 算法。DEV 全球项目展示挑战赛,由 Mux 主办:展示你的项目!

使用堆在 Go 语言中实现 Dijkstra 算法

使用 Go 语言堆实现 Dijkstra 算法的简单方法。

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

使用 Go 语言堆实现 Dijkstra 算法的简单方法。

什么是迪杰斯特拉?

迪克斯特拉

超简短描述:Dijkstra 算法用于查找 a 和 b 之间的最短路径。它选择距离最近的未访问节点,计算通过该节点到每个未访问邻居的距离,如果邻居的距离更小,则更新邻居的距离。

  • 将所有节点标记为未访问节点。创建一个包含所有未访问节点的集合,称为未访问集;在本例中,我们将使用一个集合来存储已访问节点,而不是未访问节点。

  • 为每个节点分配一个暂定距离值:初始节点的距离值设为零。将初始节点设为当前节点。

  • 对于当前节点,考虑其所有未访问的邻居节点,并计算它们通过当前节点的暂定距离。将新计算的暂定距离与当前赋值进行比较,并赋值较小的值。例如,如果当前节点 A 的距离标记为 6,且连接 A 和邻居节点 B 的边的长度为 2,则 B 通过 A 的距离为 6 + 2 = 8。如果 B 之前被标记为大于 8 的距离,则将其更改为 8。否则,保持当前值不变。

  • 当我们检查完当前节点的所有未访问邻居节点后,将当前节点标记为已访问。已访问的节点将不再被检查。

  • 选择下一个未访问的节点,该节点的暂定距离最小,将其设置为新的“当前节点”,然后返回步骤 3。

维基百科

什么是堆?

堆

在计算机科学中,堆是一种特殊的基于树的数据结构,它本质上是一个几乎完全的树,满足堆的性质:在最大堆中,对于任意给定的节点 C,如果 P 是 C 的父节点,则 P 的键(值)大于或等于 C 的键。在最小堆中,P 的键小于或等于 C 的键。堆的“顶部”(没有父节点)的节点称为根节点。

维基百科

堆可以被视为一个优先级队列;最重要的节点始终位于顶部,移除该节点后,替换它的节点将是最重要的。这在编写需要按​​完整顺序处理某些内容,但又不想执行完全排序或无需了解其余节点信息的算法时非常有用。例如,用于查找图中节点间最短距离的著名算法——Dijkstra 算法,可以通过使用优先级队列进行优化。

C语言编程

为什么?

我正在学习图论及其算法,因此我会在空闲时间阅读和学习这方面的知识。最近我观看了一个关于使用堆在 Python 中实现 Dijkstra 算法的视频,觉得很有趣,所以我决定用 Go 语言也实现同样的算法。

我知道有很多关于同一主题的文章,这些文章很好地解释了什么是 Dijkstra 算法或什么是堆,本文将是一篇简短的文章,只专注于实现,我想向你展示一个使用 Golang 中的堆来实现 Dijkstra 算法的非常简单的方法。

如果你想了解更多关于迪杰斯特拉的信息,你应该读读这篇文章,我发现它非常棒。

一篇很棒的文章

执行

Dijkstra 算法用于搜索两个节点之间的最短路径,它访问每个节点的邻居,计算成本和从起始节点出发的路径,并始终保持最小值。为此,我们可以使用最小堆在每次迭代中保存最小值,使用 push 和 pop 操作,这两个操作的时间复杂度均为 O(log n)。

首先,我们需要实现最小堆,golang 的标准库中有一个包可以实现这一点。

heap 包为任何实现了 heap.Interface 接口的类型提供堆操作。堆是一种树,其特性是每个节点都是其子树中值最小的节点。

Godoc 堆

堆.go



package main

import hp "container/heap"

type path struct {
    value int
    nodes []string
}

type minPath []path

func (h minPath) Len() int           { return len(h) }
func (h minPath) Less(i, j int) bool { return h[i].value < h[j].value }
func (h minPath) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *minPath) Push(x interface{}) {
    *h = append(*h, x.(path))
}

func (h *minPath) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

type heap struct {
    values *minPath
}

func newHeap() *heap {
    return &heap{values: &minPath{}}
}

func (h *heap) push(p path) {
    hp.Push(h.values, p)
}

func (h *heap) pop() path {
    i := hp.Pop(h.values)
    return i.(path)
}




Enter fullscreen mode Exit fullscreen mode

其次,我们需要实现图的逻辑,为此,我们使用一个结构体,其中包含一个映射来保存节点之间的边,以及用于添加边和从一个节点获取所有边的函数。

getPath 函数实现了 Dijkstra 算法,用于获取起点和终点之间的最短路径。

graph.go



package main

type edge struct {
    node   string
    weight int
}

type graph struct {
    nodes map[string][]edge
}

func newGraph() *graph {
    return &graph{nodes: make(map[string][]edge)}
}

func (g *graph) addEdge(origin, destiny string, weight int) {
    g.nodes[origin] = append(g.nodes[origin], edge{node: destiny, weight: weight})
    g.nodes[destiny] = append(g.nodes[destiny], edge{node: origin, weight: weight})
}

func (g *graph) getEdges(node string) []edge {
    return g.nodes[node]
}


func (g *graph) getPath(origin, destiny string) (int, []string) {
    h := newHeap()
    h.push(path{value: 0, nodes: []string{origin}})
    visited := make(map[string]bool)

    for len(*h.values) > 0 {
        // Find the nearest yet to visit node
        p := h.pop()
        node := p.nodes[len(p.nodes)-1]

        if visited[node] {
            continue
        }

        if node == destiny {
            return p.value, p.nodes
        }

        for _, e := range g.getEdges(node) {
            if !visited[e.node] {
                // We calculate the total spent so far plus the cost and the path of getting here
                h.push(path{value: p.value + e.weight, nodes: append([]string{}, append(p.nodes, e.node)...)})
            }
        }

        visited[node] = true
    }

    return 0, nil
}




Enter fullscreen mode Exit fullscreen mode

main.go



package main

import (
    "fmt"
)

func main() {
    fmt.Println("Dijkstra")
    // Example
    graph := newGraph()
    graph.addEdge("S", "B", 4)
    graph.addEdge("S", "C", 2)
    graph.addEdge("B", "C", 1)
    graph.addEdge("B", "D", 5)
    graph.addEdge("C", "D", 8)
    graph.addEdge("C", "E", 10)
    graph.addEdge("D", "E", 2)
    graph.addEdge("D", "T", 6)
    graph.addEdge("E", "T", 2)
    fmt.Println(graph.getPath("S", "T"))
}



Enter fullscreen mode Exit fullscreen mode


$ go run .
Dijkstra
12 [S C B D E T]


Enter fullscreen mode Exit fullscreen mode

Github

文章来源:https://dev.to/douglasmakey/implementation-of-dijkstra-using-heap-in-go-6e3