十、假设给定带权重的有向图 G=(V,E)G=(V,E)G=(VE),从源结点 sss 发出的边的权重可以为负值,而其他所有边的权重全部是非负值,同时,图中不包含权重为负值的环路。证明:Dikstra 算法可以正确计算出从源结点 sss 到所有其他结点之间的最短路径。如果要写代码,请用go语言。

文心一言:

首先,让我们证明在给定条件下,Dijkstra 算法可以正确计算出从源结点 sss 到所有其他结点之间的最短路径。

证明

  1. 初始化:Dijkstra 算法从一个源结点 sss 开始,初始化距离数组 dist,使得 dist[s] = 0,而 dist[v] 对于所有其他结点 vvv 设置为无穷大(或图中的最大权重值,确保这些值在更新过程中会被覆盖)。

  2. 没有负权环:由于图中不包含权重为负值的环路,因此任何从 sss 出发的路径如果包含了一个负权重的边,那么这条路径必定在有限步内结束,因为之后的边都是非负权重。

  3. 贪心选择:算法每次从未处理结点中选择 dist 值最小的结点 uuu,这个选择在当前已知信息下是最优的,因为如果存在一个更短的路径从 sssuuu,那么这条路径上的某个结点必然已经被处理过并且其距离已经被更新过。

  4. 边权重特性:由于从 sss 发出的边的权重可以是负值,而其他所有边的权重是非负值,因此在处理从 sss 直接连接的边时,这些负权重边可以立即减少 dist 值。由于后续边都是非负权重,这些更新后的 dist 值将不会再次被减少(除非是通过一个更长的路径,这在贪心选择过程中已经排除了)。

  5. 正确性:最终,当所有结点都被处理过之后,dist 数组中的值就是从 sss 到各个结点的最短路径长度。因为每一步的选择都是基于当前已知信息下的最优选择,并且不存在通过未处理结点更短的路径(否则在前面的步骤中就已经更新了)。

Go 语言实现

下面是 Dijkstra 算法在 Go 语言中的实现:

package main

import (
	"container/heap"
	"fmt"
	"math"
)

// Edge represents a directed edge in the graph
type Edge struct {
   
   
	to     int
	weight int
}

// Graph represents a weighted directed graph
type Graph struct {
   
   
	vertices int
	edges    map[int][]Edge
}

// NewGraph creates a new graph with a given number of vertices
func NewGraph(vertices int) *Graph {
   
   
	return &Graph{
   
   
		vertices: vertices,
		edges:    make(map[int][]Edge),
	}
}

// AddEdge adds a directed edge to the graph
func (g *Graph) AddEdge(from, to, weight int) {
   
   
	g.edges[from] = append(g.edges[from], Edge{
   
   to, weight})
}

// Item represents an item in the priority queue
type Item struct {
   
   
	vertex int
	dist   int
	index  int
}

// PriorityQueue implements heap.Interface and holds Items
type PriorityQueue []*Item

func (pq PriorityQueue) Len() int {
   
    return len(pq) }

func (pq PriorityQueue) Less(i, j int) bool {
   
   
	return pq[i].dist < pq[j].dist
}

func (pq PriorityQueue) Swap(i, j int) {
   
   
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index = i
	pq[j].index = j
}

func (pq *PriorityQueue) Push(x interface{
   
   }) {
   
   
	n := len(*pq)
	item := x.(*Item)
	item.index = n
	*pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() interface{
   
   } {
   
   
	old := *pq
	n := len(old)
	item := old[n-1]
	old[n-1] = nil  // avoid memory leak
	item.index = -1 // for safety
	*pq = old[0 : n-1]
	return item
}

func (pq *PriorityQueue) update(item *Item, dist int) {
   
   
	item.dist = dist
	heap.Fix(pq, item.index)
}

// Dijkstra calculates the shortest paths from a source vertex
func (g *Graph) Dijkstra(source int) []int {
   
   
	dist := make([]int, g.vertices)
	for i := range dist {
   
   
		dist[i] = math.MaxInt32
	}
	dist[source] = 0

	pq := make(PriorityQueue, 0, g.vertices)
	heap.Init(&pq)
	heap.Push(&pq, &Item{
   
   vertex: source, dist: 0})

	for pq.Len() > 0 {
   
   
		u := heap.Pop(&pq).(*Item).vertex

		for _, edge := range g.edges[u] {
   
   
			alt := dist[u] + edge.weight
			if alt < dist[edge.to] {
   
   
				dist[edge.to] = alt
				heap.Push(&pq, &Item{
   
   vertex: edge.to, dist: alt})
			}
		}
	}

	return dist
}

func main() {
   
   
	graph := NewGraph(5)
	graph.AddEdge(0, 1, -1)
	graph.AddEdge(0, 2, 4)
	graph.AddEdge(1, 2, 3)
	graph.AddEdge(1, 3, 2)
	graph.AddEdge(1, 4, 2)
	graph.AddEdge(3, 2, 5