我们建议阅读以下两篇文章作为阅读这篇文章的先决条件。
1.贪心算法| Set 7(Dijkstra 的最短路径算法)
2.图及其表示
我们已经讨论了 Dijkstra 算法及其在图的邻接矩阵表示中的实现。矩阵表示的时间复杂度为 O(V^2)。在这篇文章中,讨论了用于邻接列表表示的 O(ELogV) 算法。
如上一篇文章所述,在 Dijkstra 的算法中,维护了两个集合,一个集合包含已包含在 SPT(最短路径树)中的顶点列表,另一个集合包含尚未包含的顶点。使用邻接表表示,可以使用 BFS 在 O(V+E) 时间内遍历图的所有顶点。这个想法是使用 BFS 遍历图的所有顶点,并使用最小堆来存储尚未包含在 SPT 中的顶点(或尚未确定最短距离的顶点)。最小堆用作优先队列,以从尚未包含的顶点集获取最小距离顶点。对于 Min Heap,extract-min 和 reduce-key value 等操作的时间复杂度为 O(LogV)。
以下是详细步骤。
1)创建一个大小为 V 的最小堆,其中 V 是给定图中的顶点数。最小堆的每个节点都包含该顶点的顶点编号和距离值。
2)以源顶点为根初始化Min Heap(分配给源顶点的距离值为0)。分配给所有其他顶点的距离值为 INF(无限)。
3)当 Min Heap 不为空时,请执行以下操作
….. a)从最小堆中提取具有最小距离值节点的顶点。令提取的顶点为 u。
….. b)对于 u 的每个相邻顶点 v,检查 v 是否在最小堆中。如果 v 在 Min Heap 并且距离值大于 uv 的权重加上 u 的距离值,则更新 v 的距离值。
让我们通过下面的例子来理解。让给定的源顶点为 0
最初,源顶点的距离值为 0,所有其他顶点的距离值为 INF(无限)。因此从最小堆中提取源顶点并更新与 0(1 和 7)相邻的顶点的距离值。最小堆包含除顶点 0 之外的所有顶点。
绿色的顶点是最小距离已确定且不在最小堆中的顶点
由于顶点 1 的距离值在最小堆中的所有节点中最小,因此从最小堆中提取它并更新与 1 相邻顶点的距离值(如果顶点在最小堆中并且通过 1 的距离小于之前的距离)。最小堆包含除顶点 0 和 1 之外的所有顶点。
从最小堆中选取距离值最小的顶点。顶点 7 被选取。所以最小堆现在包含除 0、1 和 7 之外的所有顶点。更新 7 的相邻顶点的距离值。顶点 6 和 8 的距离值变得有限(分别为 15 和 9)。
选择与最小堆距离最小的顶点。顶点 6 被选取。所以 min heap 现在包含除 0、1、7 和 6 之外的所有顶点。更新 6 的相邻顶点的距离值。更新顶点 5 和 8 的距离值。
重复上述步骤,直到最小堆不会变空。最后,我们得到如下最短路径树。
C++
// C / C++ program for Dijkstra's
// shortest path algorithm for adjacency
// list representation of graph
#include
#include
#include
// A structure to represent a
// node in adjacency list
struct AdjListNode
{
int dest;
int weight;
struct AdjListNode* next;
};
// A structure to represent
// an adjacency list
struct AdjList
{
// Pointer to head node of list
struct AdjListNode *head;
};
// A structure to represent a graph.
// A graph is an array of adjacency lists.
// Size of array will be V (number of
// vertices in graph)
struct Graph
{
int V;
struct AdjList* array;
};
// A utility function to create
// a new adjacency list node
struct AdjListNode* newAdjListNode(
int dest, int weight)
{
struct AdjListNode* newNode =
(struct AdjListNode*)
malloc(sizeof(struct AdjListNode));
newNode->dest = dest;
newNode->weight = weight;
newNode->next = NULL;
return newNode;
}
// A utility function that creates
// a graph of V vertices
struct Graph* createGraph(int V)
{
struct Graph* graph = (struct Graph*)
malloc(sizeof(struct Graph));
graph->V = V;
// Create an array of adjacency lists.
// Size of array will be V
graph->array = (struct AdjList*)
malloc(V * sizeof(struct AdjList));
// Initialize each adjacency list
// as empty by making head as NULL
for (int i = 0; i < V; ++i)
graph->array[i].head = NULL;
return graph;
}
// Adds an edge to an undirected graph
void addEdge(struct Graph* graph, int src,
int dest, int weight)
{
// Add an edge from src to dest.
// A new node is added to the adjacency
// list of src. The node is
// added at the beginning
struct AdjListNode* newNode =
newAdjListNode(dest, weight);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;
// Since graph is undirected,
// add an edge from dest to src also
newNode = newAdjListNode(src, weight);
newNode->next = graph->array[dest].head;
graph->array[dest].head = newNode;
}
// Structure to represent a min heap node
struct MinHeapNode
{
int v;
int dist;
};
// Structure to represent a min heap
struct MinHeap
{
// Number of heap nodes present currently
int size;
// Capacity of min heap
int capacity;
// This is needed for decreaseKey()
int *pos;
struct MinHeapNode **array;
};
// A utility function to create a
// new Min Heap Node
struct MinHeapNode* newMinHeapNode(int v,
int dist)
{
struct MinHeapNode* minHeapNode =
(struct MinHeapNode*)
malloc(sizeof(struct MinHeapNode));
minHeapNode->v = v;
minHeapNode->dist = dist;
return minHeapNode;
}
// A utility function to create a Min Heap
struct MinHeap* createMinHeap(int capacity)
{
struct MinHeap* minHeap =
(struct MinHeap*)
malloc(sizeof(struct MinHeap));
minHeap->pos = (int *)malloc(
capacity * sizeof(int));
minHeap->size = 0;
minHeap->capacity = capacity;
minHeap->array =
(struct MinHeapNode**)
malloc(capacity *
sizeof(struct MinHeapNode*));
return minHeap;
}
// A utility function to swap two
// nodes of min heap.
// Needed for min heapify
void swapMinHeapNode(struct MinHeapNode** a,
struct MinHeapNode** b)
{
struct MinHeapNode* t = *a;
*a = *b;
*b = t;
}
// A standard function to
// heapify at given idx
// This function also updates
// position of nodes when they are swapped.
// Position is needed for decreaseKey()
void minHeapify(struct MinHeap* minHeap,
int idx)
{
int smallest, left, right;
smallest = idx;
left = 2 * idx + 1;
right = 2 * idx + 2;
if (left < minHeap->size &&
minHeap->array[left]->dist <
minHeap->array[smallest]->dist )
smallest = left;
if (right < minHeap->size &&
minHeap->array[right]->dist <
minHeap->array[smallest]->dist )
smallest = right;
if (smallest != idx)
{
// The nodes to be swapped in min heap
MinHeapNode *smallestNode =
minHeap->array[smallest];
MinHeapNode *idxNode =
minHeap->array[idx];
// Swap positions
minHeap->pos[smallestNode->v] = idx;
minHeap->pos[idxNode->v] = smallest;
// Swap nodes
swapMinHeapNode(&minHeap->array[smallest],
&minHeap->array[idx]);
minHeapify(minHeap, smallest);
}
}
// A utility function to check if
// the given minHeap is ampty or not
int isEmpty(struct MinHeap* minHeap)
{
return minHeap->size == 0;
}
// Standard function to extract
// minimum node from heap
struct MinHeapNode* extractMin(struct MinHeap*
minHeap)
{
if (isEmpty(minHeap))
return NULL;
// Store the root node
struct MinHeapNode* root =
minHeap->array[0];
// Replace root node with last node
struct MinHeapNode* lastNode =
minHeap->array[minHeap->size - 1];
minHeap->array[0] = lastNode;
// Update position of last node
minHeap->pos[root->v] = minHeap->size-1;
minHeap->pos[lastNode->v] = 0;
// Reduce heap size and heapify root
--minHeap->size;
minHeapify(minHeap, 0);
return root;
}
// Function to decreasy dist value
// of a given vertex v. This function
// uses pos[] of min heap to get the
// current index of node in min heap
void decreaseKey(struct MinHeap* minHeap,
int v, int dist)
{
// Get the index of v in heap array
int i = minHeap->pos[v];
// Get the node and update its dist value
minHeap->array[i]->dist = dist;
// Travel up while the complete
// tree is not hepified.
// This is a O(Logn) loop
while (i && minHeap->array[i]->dist <
minHeap->array[(i - 1) / 2]->dist)
{
// Swap this node with its parent
minHeap->pos[minHeap->array[i]->v] =
(i-1)/2;
minHeap->pos[minHeap->array[
(i-1)/2]->v] = i;
swapMinHeapNode(&minHeap->array[i],
&minHeap->array[(i - 1) / 2]);
// move to parent index
i = (i - 1) / 2;
}
}
// A utility function to check if a given vertex
// 'v' is in min heap or not
bool isInMinHeap(struct MinHeap *minHeap, int v)
{
if (minHeap->pos[v] < minHeap->size)
return true;
return false;
}
// A utility function used to print the solution
void printArr(int dist[], int n)
{
printf("Vertex Distance from Source\n");
for (int i = 0; i < n; ++i)
printf("%d \t\t %d\n", i, dist[i]);
}
// The main function that calculates
// distances of shortest paths from src to all
// vertices. It is a O(ELogV) function
void dijkstra(struct Graph* graph, int src)
{
// Get the number of vertices in graph
int V = graph->V;
// dist values used to pick
// minimum weight edge in cut
int dist[V];
// minHeap represents set E
struct MinHeap* minHeap = createMinHeap(V);
// Initialize min heap with all
// vertices. dist value of all vertices
for (int v = 0; v < V; ++v)
{
dist[v] = INT_MAX;
minHeap->array[v] = newMinHeapNode(v,
dist[v]);
minHeap->pos[v] = v;
}
// Make dist value of src vertex
// as 0 so that it is extracted first
minHeap->array[src] =
newMinHeapNode(src, dist[src]);
minHeap->pos[src] = src;
dist[src] = 0;
decreaseKey(minHeap, src, dist[src]);
// Initially size of min heap is equal to V
minHeap->size = V;
// In the followin loop,
// min heap contains all nodes
// whose shortest distance
// is not yet finalized.
while (!isEmpty(minHeap))
{
// Extract the vertex with
// minimum distance value
struct MinHeapNode* minHeapNode =
extractMin(minHeap);
// Store the extracted vertex number
int u = minHeapNode->v;
// Traverse through all adjacent
// vertices of u (the extracted
// vertex) and update
// their distance values
struct AdjListNode* pCrawl =
graph->array[u].head;
while (pCrawl != NULL)
{
int v = pCrawl->dest;
// If shortest distance to v is
// not finalized yet, and distance to v
// through u is less than its
// previously calculated distance
if (isInMinHeap(minHeap, v) &&
dist[u] != INT_MAX &&
pCrawl->weight + dist[u] < dist[v])
{
dist[v] = dist[u] + pCrawl->weight;
// update distance
// value in min heap also
decreaseKey(minHeap, v, dist[v]);
}
pCrawl = pCrawl->next;
}
}
// print the calculated shortest distances
printArr(dist, V);
}
// Driver program to test above functions
int main()
{
// create the graph given in above fugure
int V = 9;
struct Graph* graph = createGraph(V);
addEdge(graph, 0, 1, 4);
addEdge(graph, 0, 7, 8);
addEdge(graph, 1, 2, 8);
addEdge(graph, 1, 7, 11);
addEdge(graph, 2, 3, 7);
addEdge(graph, 2, 8, 2);
addEdge(graph, 2, 5, 4);
addEdge(graph, 3, 4, 9);
addEdge(graph, 3, 5, 14);
addEdge(graph, 4, 5, 10);
addEdge(graph, 5, 6, 2);
addEdge(graph, 6, 7, 1);
addEdge(graph, 6, 8, 6);
addEdge(graph, 7, 8, 7);
dijkstra(graph, 0);
return 0;
}
Python
# A Python program for Dijkstra's shortest
# path algorithm for adjacency
# list representation of graph
from collections import defaultdict
import sys
class Heap():
def __init__(self):
self.array = []
self.size = 0
self.pos = []
def newMinHeapNode(self, v, dist):
minHeapNode = [v, dist]
return minHeapNode
# A utility function to swap two nodes
# of min heap. Needed for min heapify
def swapMinHeapNode(self,a, b):
t = self.array[a]
self.array[a] = self.array[b]
self.array[b] = t
# A standard function to heapify at given idx
# This function also updates position of nodes
# when they are swapped.Position is needed
# for decreaseKey()
def minHeapify(self, idx):
smallest = idx
left = 2*idx + 1
right = 2*idx + 2
if left < self.size and
self.array[left][1] \
< self.array[smallest][1]:
smallest = left
if right < self.size and
self.array[right][1]\
< self.array[smallest][1]:
smallest = right
# The nodes to be swapped in min
# heap if idx is not smallest
if smallest != idx:
# Swap positions
self.pos[ self.array[smallest][0]]
= idx
self.pos[ self.array[idx][0]] =
smallest
# Swap nodes
self.swapMinHeapNode(smallest, idx)
self.minHeapify(smallest)
# Standard function to extract minimum
# node from heap
def extractMin(self):
# Return NULL wif heap is empty
if self.isEmpty() == True:
return
# Store the root node
root = self.array[0]
# Replace root node with last node
lastNode = self.array[self.size - 1]
self.array[0] = lastNode
# Update position of last node
self.pos[lastNode[0]] = 0
self.pos[root[0]] = self.size - 1
# Reduce heap size and heapify root
self.size -= 1
self.minHeapify(0)
return root
def isEmpty(self):
return True if self.size == 0 else False
def decreaseKey(self, v, dist):
# Get the index of v in heap array
i = self.pos[v]
# Get the node and update its dist value
self.array[i][1] = dist
# Travel up while the complete tree is
# not hepified. This is a O(Logn) loop
while i > 0 and self.array[i][1] <
self.array[(i - 1) / 2][1]:
# Swap this node with its parent
self.pos[ self.array[i][0] ] = (i-1)/2
self.pos[ self.array[(i-1)/2][0] ] = i
self.swapMinHeapNode(i, (i - 1)/2 )
# move to parent index
i = (i - 1) / 2;
# A utility function to check if a given
# vertex 'v' is in min heap or not
def isInMinHeap(self, v):
if self.pos[v] < self.size:
return True
return False
def printArr(dist, n):
print "Vertex\tDistance from source"
for i in range(n):
print "%d\t\t%d" % (i,dist[i])
class Graph():
def __init__(self, V):
self.V = V
self.graph = defaultdict(list)
# Adds an edge to an undirected graph
def addEdge(self, src, dest, weight):
# Add an edge from src to dest. A new node
# is added to the adjacency list of src. The
# node is added at the beginning. The first
# element of the node has the destination
# and the second elements has the weight
newNode = [dest, weight]
self.graph[src].insert(0, newNode)
# Since graph is undirected, add an edge
# from dest to src also
newNode = [src, weight]
self.graph[dest].insert(0, newNode)
# The main function that calculates distances
# of shortest paths from src to all vertices.
# It is a O(ELogV) function
def dijkstra(self, src):
V = self.V # Get the number of vertices in graph
dist = [] # dist values used to pick minimum
# weight edge in cut
# minHeap represents set E
minHeap = Heap()
# Initialize min heap with all vertices.
# dist value of all vertices
for v in range(V):
dist.append(sys.maxint)
minHeap.array.append( minHeap.
newMinHeapNode(v, dist[v]))
minHeap.pos.append(v)
# Make dist value of src vertex as 0 so
# that it is extracted first
minHeap.pos[src] = src
dist[src] = 0
minHeap.decreaseKey(src, dist[src])
# Initially size of min heap is equal to V
minHeap.size = V;
# In the following loop,
# min heap contains all nodes
# whose shortest distance is not yet finalized.
while minHeap.isEmpty() == False:
# Extract the vertex
# with minimum distance value
newHeapNode = minHeap.extractMin()
u = newHeapNode[0]
# Traverse through all adjacent vertices of
# u (the extracted vertex) and update their
# distance values
for pCrawl in self.graph[u]:
v = pCrawl[0]
# If shortest distance to v is not finalized
# yet, and distance to v through u is less
# than its previously calculated distance
if minHeap.isInMinHeap(v) and
dist[u] != sys.maxint and \
pCrawl[1] + dist[u] < dist[v]:
dist[v] = pCrawl[1] + dist[u]
# update distance value
# in min heap also
minHeap.decreaseKey(v, dist[v])
printArr(dist,V)
# Driver program to test the above functions
graph = Graph(9)
graph.addEdge(0, 1, 4)
graph.addEdge(0, 7, 8)
graph.addEdge(1, 2, 8)
graph.addEdge(1, 7, 11)
graph.addEdge(2, 3, 7)
graph.addEdge(2, 8, 2)
graph.addEdge(2, 5, 4)
graph.addEdge(3, 4, 9)
graph.addEdge(3, 5, 14)
graph.addEdge(4, 5, 10)
graph.addEdge(5, 6, 2)
graph.addEdge(6, 7, 1)
graph.addEdge(6, 8, 6)
graph.addEdge(7, 8, 7)
graph.dijkstra(0)
# This code is contributed by Divyanshu Mehta
输出:
Vertex Distance from Source
0 0
1 4
2 12
3 19
4 21
5 11
6 9
7 8
8 14
时间复杂度:上述代码/算法的时间复杂度看起来是 O(V^2),因为有两个嵌套的 while 循环。如果我们仔细观察,我们可以观察到内循环中的语句执行了 O(V+E) 次(类似于 BFS)。内循环有 reduceKey() 操作,需要 O(LogV) 时间。所以整体时间复杂度是 O(E+V)*O(LogV) 也就是 O((E+V)*LogV) = O(ELogV)
请注意,上面的代码使用二叉堆来实现优先级队列。使用斐波那契堆可以将时间复杂度降低到 O(E + VLogV)。原因是,Fibonacci Heap 需要 O(1) 时间进行减键操作,而 Binary Heap 需要 O(Logn) 时间。
笔记:
- 该代码计算最短距离,但不计算路径信息。我们可以创建一个父数组,在距离更新时更新父数组(如 prim 的实现),并使用它显示从源到不同顶点的最短路径。
- 该代码用于无向图,同样的 dijekstra函数也可用于有向图。
- 该代码查找从源到所有顶点的最短距离。如果我们只对从源到单个目标的最短距离感兴趣,我们可以在选择的最小距离顶点等于目标时中断 for 循环(算法的步骤 3.a)。
- Dijkstra 算法不适用于具有负权重边的图。对于具有负权重边的图,可以使用 Bellman-Ford 算法,我们很快将在单独的帖子中讨论它。
在 Dijkstra 的最短路径算法中打印路径
Dijkstra 的最短路径算法使用 STL 中的集合
参考:
Clifford Stein、Thomas H. Cormen、Charles E. Leiserson、Ronald L.
Sanjoy Dasgupta、Christos Papadimitriou、Umesh Vazirani 的算法
如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程和学生竞争性编程现场课程。