算法编程题-颜色交替的最短路径
摘要:本文将对LeetCode原题 1129 颜色交替的最短路径进行介绍,先介绍dijkstra算法,然后给出进一步优化得到的bfs算法。代码实现基于golang语言,且已经通过所有测试用例,最后也会给出对应的时间复杂度分析。
关键词:LeetCode,Golang,dijkstra,bfs
原题描述
给定一个整数n,表示图中一共有n个节点,但是图中的边有红边也有蓝边,用两个数组给出red_edges和blue_edges,图中允许有自环和平行边,现在要求求出一个数组res[n],res[i]表示从点0到点i的红色蓝色边交替出现的最短路径。如下图,是一个这样的有向图。
方法一、dijkstra算法
思路简述
稍微变型的单源最短路径问题,所以可以基于dijkstra算法来实现。在实现上,起始时,需要将起点0加入到堆中,并且要加入两次,对应两种颜色。加入到堆中的是一个状态,状态中的颜色表示到某点的一条路径的最后一段的颜色,起点前没有边,但是可以假想存在边,一种是红边,一种是蓝边。
代码实现
type LinkedListNode[T comparable] struct {
Val T
Next *LinkedListNode[T]
}
type LinkedList[T comparable] struct {
Head *LinkedListNode[T]
}
func (l *LinkedList[T]) InsertNode(v T) {
curNode := &LinkedListNode[T]{Val: v}
curNode.Next = l.Head
l.Head = curNode
}
// NewLinkedList 根据数组新建一个链表
func NewLinkedList[T comparable] (arr []T) *LinkedList[T] {
head := LinkedListNode[T]{Val: arr[0], Next: nil}
tr := &head
for i := 1; i < len(arr); i++ {
tr.Next = &LinkedListNode[T]{Val: arr[i], Next: nil}
tr = tr.Next
}
return &LinkedList[T]{Head: &head}
}
type AdjTableNode struct {
Point int
}
type AdjTable struct {
tab [][]*LinkedList[AdjTableNode]
}
func NewAdjTable(edges [][][]int, n int) *AdjTable {
tab := make([][]*LinkedList[AdjTableNode], n)
for i := 0; i < n; i++ {
tab[i] = make([]*LinkedList[AdjTableNode], 2)
}
for i := 0; i < 2; i++ {
for _, edge := range edges[i] {
a, b := edge[0], edge[1]
if tab[a][i] == nil {
tab[a][i] = NewLinkedList[AdjTableNode]([]AdjTableNode{{Point: b}})
} else {
tab[a][i].InsertNode(AdjTableNode{b})
}
}
}
return &AdjTable{tab: tab}
}
type Point struct {
PointNo int // 节点编号
Dis int // 节点距离
Color int // 上一条边蓝边0红边1
}
type Heap []*Point
func (h Heap) Len() int {
return len(h)
}
func (h Heap) Less(i, j int) bool {
return h[i].Dis < h[j].Dis
}
func (h Heap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *Heap) Push(v interface{}) {
*h = append(*h, v.(*Point))
}
func (h *Heap) Pop() interface{} {
ret := (*h)[len(*h)-1]
*h = (*h)[:len(*h)-1]
return ret
}
func shortestAlternatingPaths(n int, redEdges [][]int, blueEdges [][]int) []int {
edges := [][][]int{blueEdges, redEdges}
adjTab := NewAdjTable(edges, n)
dis := make([][]int, n)
for i := 0; i < n; i++ {
dis[i] = []int{-1, -1} // dis[i][0]表上上一条边为蓝色,dis[i][1]表示上一条边为红色
}
h := Heap{}
// 初始化
heap.Push(&h, &Point{0, 0, 0})
heap.Push(&h, &Point{0, 0, 1})
// 正式迭代
// for i := 0; len(h) > 0 && i < 2 * n; i++ {
for len(h) > 0 {
p := heap.Pop(&h).(*Point)
if dis[p.PointNo][p.Color] != -1 && dis[p.PointNo][p.Color] <= p.Dis {
continue
}
dis[p.PointNo][p.Color] = p.Dis
list := adjTab.tab[p.PointNo][1 ^ p.Color]
if list == nil {
continue
}
cur := list.Head
for cur != nil {
heap.Push(&h, &Point{cur.Val.Point, p.Dis + 1, 1 ^ p.Color})
cur = cur.Next
}
}
res := make([]int, n)
for i := 0; i < n; i++ {
res[i] = -1
if dis[i][0] != -1 {
res[i] = dis[i][0]
}
if dis[i][1] != -1 {
if res[i] == -1 || res[i] > dis[i][1] {
res[i] = dis[i][1]
}
}
}
return res
}
运行截图如下:
复杂度分析
- 时间复杂度: O ( n + m l o g m ) O(n+mlogm) O(n+mlogm),其中n为点数,m为边数,在迭代循环中,考虑到堆中的数量级和边数有关
- 空间复杂度: O ( m + n ) O(m+n) O(m+n)
方法二、广度优先搜索
思路简述
还有一种可行的思路不能忘记,考虑到图中每一条边的权重都是1,所以也可以使用广度优先搜索的方法去做。每一个节点第一次被访问到此时的路径长度就是其到源点0的最短路径。
代码实现
type LinkedListNode[T comparable] struct {
Val T
Next *LinkedListNode[T]
}
type LinkedList[T comparable] struct {
Head *LinkedListNode[T]
}
func (l *LinkedList[T]) InsertNode(v T) {
curNode := &LinkedListNode[T]{Val: v}
curNode.Next = l.Head
l.Head = curNode
}
// NewLinkedList 根据数组新建一个链表
func NewLinkedList[T comparable] (arr []T) *LinkedList[T] {
head := LinkedListNode[T]{Val: arr[0], Next: nil}
tr := &head
for i := 1; i < len(arr); i++ {
tr.Next = &LinkedListNode[T]{Val: arr[i], Next: nil}
tr = tr.Next
}
return &LinkedList[T]{Head: &head}
}
type AdjTableNode struct {
Point int
}
type AdjTable struct {
tab [][]*LinkedList[AdjTableNode]
}
func NewAdjTable(edges [][][]int, n int) *AdjTable {
tab := make([][]*LinkedList[AdjTableNode], n)
for i := 0; i < n; i++ {
tab[i] = make([]*LinkedList[AdjTableNode], 2)
}
for i := 0; i < 2; i++ {
for _, edge := range edges[i] {
a, b := edge[0], edge[1]
if tab[a][i] == nil {
tab[a][i] = NewLinkedList[AdjTableNode]([]AdjTableNode{{Point: b}})
} else {
tab[a][i].InsertNode(AdjTableNode{b})
}
}
}
return &AdjTable{tab: tab}
}
type Point struct {
PointNo int // 节点编号
Dis int // 节点距离
Color int // 上一条边蓝边0红边1
}
type Heap []*Point
func (h Heap) Len() int {
return len(h)
}
func (h Heap) Less(i, j int) bool {
return h[i].Dis < h[j].Dis
}
func (h Heap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *Heap) Push(v interface{}) {
*h = append(*h, v.(*Point))
}
func (h *Heap) Pop() interface{} {
ret := (*h)[len(*h)-1]
*h = (*h)[:len(*h)-1]
return ret
}
func shortestAlternatingPaths(n int, redEdges [][]int, blueEdges [][]int) []int {
edges := [][][]int{blueEdges, redEdges}
adjTab := NewAdjTable(edges, n)
dis := make([][]int, n)
for i := 0; i < n; i++ {
dis[i] = []int{-1, -1}
}
oldqueue := []*Point{{0, 0, 0}, {0, 0, 1}}
queue := make([]*Point, 0)
for len(oldqueue) > 0 {
for _, p := range oldqueue {
if dis[p.PointNo][p.Color] == -1 {
dis[p.PointNo][p.Color] = p.Dis
} else {
continue
}
list := adjTab.tab[p.PointNo][1 ^ p.Color]
if list == nil {
continue
}
cur := list.Head
for cur != nil {
queue = append(queue, &Point{cur.Val.Point, p.Dis + 1, 1 ^ p.Color})
cur = cur.Next
}
}
oldqueue = queue
queue = []*Point(nil)
}
res := make([]int, n)
for i := 0; i < n; i++ {
res[i] = -1
if dis[i][0] != -1 {
res[i] = dis[i][0]
}
if dis[i][1] != -1 {
if res[i] == -1 || res[i] > dis[i][1] {
res[i] = dis[i][1]
}
}
}
return res
}
运行截图如下:
复杂度分析
- 时间复杂度: O ( m + n ) O(m+n) O(m+n),m为边数,n为点数
- 空间复杂度: O ( m + n ) O(m+n) O(m+n)