Dijkstra算法整理

一、Dijkstra’s Algorithm for Adjacency Matrix Representation

使用邻接矩阵存储图,其实就是使用一个二维数组存储图,来实施Dijkatra算法

思路

  1. 创建一个布尔数组sptSet [ ](最短路径树集),用来记录包含在最短路径树中的顶点。如果sptSet[v]值为true,则顶点v包含在SPT中,否则不包含。最初,这个集合是空的。
  2. 为图中的所有顶点分配一个距离值,它表示起点(人为规定,记为src)到当前点i的距离值。将所有距离值初始化为 INFINITE 无穷大。将起点src的距离值指定为 0,以便最先将其加入到集合sptSet 中。
  3. 当sptSet [ ] 不包括所有顶点时:
    …… a) 选择一个不在sptSet中,且距离值最小的顶点 u。
    …… b) 将 u 添加到sptSet中。
    …… c) 遍历所有与 u 相邻的顶点,以更新 u 的所有相邻顶点的距离值。对于 u 的每个相邻的顶点v,如果 u 的距离值和边<u,v>的权重之和小于v的距离值,则更新 v 的距离值。

代码示例

// 使用图的邻接矩阵形式来实现Dijkstra算法
#include <iostream>
using namespace std;
#include <limits.h>

// 图的顶点数
#define V 9


// 从最短路径树集未包含的顶点集合中,找到距离值最小的顶点
int minDistance(int dist[], bool sptSet[])
{

	// 初始化最小值
	int min = INT_MAX, min_index;

	for (int v = 0; v < V; v++)
		if (sptSet[v] == false && dist[v] <= min)
			min = dist[v], min_index = v;

	return min_index;
}

//输出构建好的dist数组。该数组记录了每个顶点与源点之间的最短路径值。
void printSolution(int dist[])
{
	cout << "Vertex \t Distance from Source" << endl;
	for (int i = 0; i < V; i++)
		cout << i << " \t\t" << dist[i] << endl;
}

//使用邻接矩阵表示的图,实现Dijkstra的单源最短路径算法
void dijkstra(int graph[V][V], int src)
{
	int dist[V]; //输出数组。dist[i]表示src到i的最短距离

	bool sptSet[V]; // 如果顶点i包含在最短路径树集中,或者确定了从src到i的最短距离,sptSet[i]的值将为真

	// 初始化所有距离为INFINITE, stpSet[]为false
	for (int i = 0; i < V; i++)
		dist[i] = INT_MAX, sptSet[i] = false;

	// 起点到起点的最短距离是0
	dist[src] = 0;

	// 为所有顶点找到最短路径
	for (int count = 0; count < V - 1; count++) {
		// 从尚未处理的顶点集合中选取距离最小的顶点。在第一次迭代中,u总是等于src。
		int u = minDistance(dist, sptSet);

		// 已经被选进最短路径树集的点,做标记
		sptSet[u] = true;

		// 更新被选中顶点的相邻顶点的距离值。
		for (int v = 0; v < V; v++)
			if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX
				&& dist[u] + graph[u][v] < dist[v])
				dist[v] = dist[u] + graph[u][v];
	}

	// 输出源点到每个点的最短路径
	printSolution(dist);
}


int main()
{

	/* Let us create the example graph discussed above */
	int graph[V][V] = { { 0, 4, 0, 0, 0, 0, 0, 8, 0 },
						{ 4, 0, 8, 0, 0, 0, 0, 11, 0 },
						{ 0, 8, 0, 7, 0, 4, 0, 0, 2 },
						{ 0, 0, 7, 0, 9, 14, 0, 0, 0 },
						{ 0, 0, 0, 9, 0, 10, 0, 0, 0 },
						{ 0, 0, 4, 14, 10, 0, 2, 0, 0 },
						{ 0, 0, 0, 0, 0, 2, 0, 1, 6 },
						{ 8, 11, 0, 0, 0, 0, 1, 0, 7 },
						{ 0, 0, 2, 0, 0, 0, 6, 7, 0 } };

	dijkstra(graph, 0);

	return 0;
}

时间复杂度

上述Dijkstra算法中执行了两个嵌套while循环,每个循环遍历一遍所有图结点。记 V 为图结点个数,则使用邻接矩阵实现的Dijkstra算法的时间复杂度为 O(V^2).



二、Dijkstra’s Algorithm for Adjacency List Representation

图示理解

给定的源点为 0。
在这里插入图片描述
最初,源点的距离值为 0,所有其他点的距离值为 INF(无穷大)。因此从最小堆中提取源点,并更新与 0相邻的点(1 和 7)的距离值。MinHeap 包含除点 0 之外 的所有顶点。绿色的点是已确定最小距离且不在 MinHeap 中的点。

由于点 1 的距离值在 Min Heap 中的所有节点中是最小的,因此从 Min Heap 中提取并更新与 1 相邻的顶点的距离值(如果顶点在 Min Heap 中并且通过 1 的距离小于之前的距离)。最小堆包含除顶点 0 和 1 之外的所有顶点。

从最小堆中继续选择距离值最小的顶点。顶点 7 被选中。所以 MinHeap 现在包含除了 0、1 和 7 之外的所有顶点。更新 7 的相邻顶点的距离值。更新顶点 6 和 8 的距离值(分别为 15 和 9)。

继续选择与最小堆距离最小的顶点。顶点 6 被选中。所以 MinHeap现在包含除了0、1、7、6之外的所有顶点。更新6的相邻顶点的距离值。更新顶点5和8的距离值。

重复上述步骤,直到最小堆不为空。最后,我们得到以下最短路径树。

思路

采用邻接表存储图,实施Dijkatra算法。

与第一种方法相同的是,在 Dijkstra 的算法中需要维护两个集合,一组是已包含在在 SPT(最短路径树)中的顶点列表,另一组是尚未包含的顶点。与第一种方法不同的是,这里使用邻接表表示和存储图,这样可以使用BFS广度优先遍历在 O(V+E) 时间内遍历图的所有顶点,从而降低时间复杂度。

使用BFS 遍历图的所有顶点,并使用最小堆来存储尚未包含在 SPT 中的顶点,或尚未确定最短距离的顶点。最小堆的作用:充当优先队列,从一组尚未包含的顶点中获取最小距离顶点。

对于 Min Heap,extract-min 和 reduction-key value 等操作的时间复杂度为 O(LogV)。

代码思路如下:

  1. 创建一个大小为 V 的最小堆,其中 V 是给定图中的顶点数。最小堆的每个节点都包含顶点数和顶点的距离值。
  2. 以源顶点(人为规定)为根,初始化最小堆 MinHeap。其中分配给源顶点的距离值为 0,分配给所有其他顶点的距离值是 INF(无穷大)。
  3. 当 Min Heap 不为空时:
    (1)从 Min Heap 中提取具有最小距离值节点的顶点。让提取的顶点为 u。
    (2)对于 u 的每个相邻顶点 v,检查 v 是否在最小堆中。如果v在Min Heap中且距离值大于uv的权重加上u的距离值,则更新v的距离值。

代码示例

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

//邻接点的结构定义
struct AdjListNode
{
	int dest;
	int weight;
	struct AdjListNode* next;
};

//邻接表的结构定义
struct AdjList
{

	// 边表头指针,指向表头
	struct AdjListNode* head;
};

//图结构定义。一个图是一个邻接表的数组,数组大小为顶点个数V。
struct Graph
{
	int V;
	struct AdjList* array;
};

// 创建一个新的邻接表节点
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;
}

// 创建包含V个结点的图
struct Graph* createGraph(int V)
{
	struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
	graph->V = V;

	// 创建邻接表,大小为V
	graph->array = (struct AdjList*)malloc(V * sizeof(struct AdjList));

	// 初始化每个邻接表为空
	for (int i = 0; i < V; ++i)
		graph->array[i].head = NULL;

	return graph;
}

// 向图中加边
void addEdge(struct Graph* graph, int src,
	int dest, int weight)
{

	struct AdjListNode* newNode =newAdjListNode(dest, weight);
	newNode->next = graph->array[src].head;
	graph->array[src].head = newNode;

	// 如果是无向图,还需要执行以下插入
	newNode = newAdjListNode(src, weight);
	newNode->next = graph->array[dest].head;
	graph->array[dest].head = newNode;
}


// 最小堆的结点的结构定义
struct MinHeapNode
{
	int v;
	int dist;
};

// 最小堆结构定义
struct MinHeap
{

	// 当前存在的堆结点个数
	int size;

	// 最小堆容量
	int capacity;

	// This is needed for decreaseKey()
	int* pos;
	struct MinHeapNode** array;
};

// 创建一个新的最小堆的堆结点
struct MinHeapNode* newMinHeapNode(int v,
	int dist)
{
	struct MinHeapNode* minHeapNode =(struct MinHeapNode*)malloc(sizeof(struct MinHeapNode));
	minHeapNode->v = v;
	minHeapNode->dist = dist;
	return minHeapNode;
}

// 创建最小堆
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;
}

// 交换最小堆的两个结点
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);
	}
}

// 检查给定的minHeap是否为空
int isEmpty(struct MinHeap* minHeap)
{
	return minHeap->size == 0;
}

// 从堆中提取最小节点
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;
}

// 使用最小堆的pos[]来获取最小堆中节点的当前索引
void decreaseKey(struct MinHeap* minHeap,
	int v, int dist)
{
	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)
	{
		// 当前结点与其父结点交换
		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]);

		
		i = (i - 1) / 2;
	}
}

// 判断当前结点是否在最小堆中
bool isInMinHeap(struct MinHeap* minHeap, int v)
{
	if (minHeap->pos[v] < minHeap->size)
		return true;
	return false;
}

// 输出最短路径的结果
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]);
}

// 计算从src到所有顶点的最短路径距离的主要函数
void dijkstra(struct Graph* graph, int src)
{

	int V = graph->V;

	int dist[V];

	// minHeap represents set E
	struct MinHeap* minHeap = createMinHeap(V);

	// 初始化最小堆
	for (int v = 0; v < V; ++v)
	{
		dist[v] = INT_MAX;
		minHeap->array[v] = newMinHeapNode(v,dist[v]);
		minHeap->pos[v] = v;
	}

	// 将src顶点的dist值设为0,以便首先提取
	minHeap->array[src] = newMinHeapNode(src, dist[src]);
	minHeap->pos[src] = src;
	dist[src] = 0;
	decreaseKey(minHeap, src, dist[src]);


	minHeap->size = V;

	//在接下来的循环中,min堆包含所有最短距离尚未结束的节点。
	while (!isEmpty(minHeap))
	{
		// 提取距离值最小的顶点
		struct MinHeapNode* minHeapNode = extractMin(minHeap);
		int u = minHeapNode->v;

		// 遍历u的所有相邻顶点,并更新它们的距离值
		struct AdjListNode* pCrawl = graph->array[u].head;
		while (pCrawl != NULL)
		{
			int v = pCrawl->dest;

			// 如果到v的最短距离尚未确定,且通过u到v的距离小于之前计算的距离
			if (isInMinHeap(minHeap, v) && dist[u] != INT_MAX && pCrawl->weight + dist[u] < dist[v])
			{
				dist[v] = dist[u] + pCrawl->weight;


				decreaseKey(minHeap, v, dist[v]);
			}
			pCrawl = pCrawl->next;
		}
	}

	printArr(dist, V);
}


int main()
{
	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;
}

时间复杂度

在该算法的dijkstra的函数中,执行了两个嵌套的 while 循环。可以观察到,内部循环中的语句执行了 O(V+E) 次(类似于 BFS广度优先遍历)。另外内部循环有 reduceKey() 操作,需要 O(LogV) 时间。所以总的时间复杂度 = O(E+V)*O(LogV) = O(ELogV)。(V代表图结点数,E代表边数)



三、Dijkstra’s shortest path algorithm using set in STL

思路

使用邻接表的方式在时间复杂度方面更好,但由于手动实现了最小堆(也就是优先队列的功能),因此代码比较复杂。STL 提供了priority_queue,但提供的优先队列不支持减少key和删除操作。在 Dijkstra 的算法中,我们需要一个优先级队列以及对优先级队列的以下操作:
           ExtractMin :从所有尚未找到最短距离的顶点中,我们需要得到距离最小的顶点。
           DecreaseKey :提取顶点后,我们需要更新其相邻顶点的距离,如果新的距离更小,则更新数据结构中的距离。
上面的操作可以通过c++ STL的set数据结构实现。set保持它所有的key都是有序的,所以最小距离的顶点总是在开始,我们可以从那里提取它,这就是ExtractMin操作,并相应地更新其他相邻的顶点如果任何顶点的距离变小,则删除其先前的条目并插入新的更新条目,即 DecreaseKey 操作。

代码思路:

  1. 将所有顶点的距离初始化为INF无穷大。
  2. 创建一个set类型的空集setds,集合中的每一项元素都是一对pair(weight,vertex)。weight表示权重或者距离,vertex表示顶点。把weight放在pair的第一位,是为了令集合中的元素按照weight值来排序。
  3. 将源点src插入集合setds,并使其距离为 0。
  4. while(setds不为空):
            a) 提取setds中的第一项元素,该元素就是距离值最小的点,记为u。(u = setds.begin().second)
            b) 遍历u的所有相邻结点(记为),以更新v最短路径:
                  if(dist[v] > dist[u] + weight(u, v)) {
                      从setds中找到顶点v的对应项,删除之;
                      更新v的距离值:dist[v] = dist[u] + weight(u, v);
                      将更新后的距离值和点v下标重新以pair形式插入setds,完成更新。(setds.insert(make_pair(dist[v], v)))
  5. 输出距离数组 dist[ ],打印所有的最短路径。

代码示例

#include<bitsstdc++.h>
using namespace std;
# define INF 0x3f3f3f3f

/* 该类用邻接表表示有向图 */
class Graph
{
	int V; 
	list< pair<int, int> >* adj;//加权图需要存储每条边的顶点和权值对

    public:
	    Graph(int V); 
	    void addEdge(int u, int v, int w);
	    void shortestPath(int s);
};



Graph::Graph(int V)
{
	this->V = V;
	adj = new list< pair<int, int> >[V];
}



void Graph::addEdge(int u, int v, int w)
{
	adj[u].push_back(make_pair(v, w));
	adj[v].push_back(make_pair(u, w));
}



/* 执行Dijkstra算法的主函数 */
void Graph::shortestPath(int src)
{
	// 创建一个集合来存储正在处理的顶点
	set< pair<int, int> > setds;

	// 为距离创建一个矢量,并将所有距离初始化为无穷大(INF)
	vector<int> dist(V, INF);

	// 首先向集合setds中插入起点src,其距离值为0
	setds.insert(make_pair(0, src));
	dist[src] = 0;


	while (!setds.empty())
	{
		// 集合中的第一个顶点为距离最小顶点,将其从集合中提取出来
		pair<int, int> tmp = *(setds.begin());
		setds.erase(setds.begin());

		int u = tmp.second;//所提取出来的结点的序号为pair的后者(前者为距离值)

		// 遍历当前结点的所有相邻结点
		list< pair<int, int> >::iterator i;
		for (i = adj[u].begin(); i != adj[u].end(); ++i)
		{
			// 得到u的顶点标签和当前邻边的权值
			int v = (*i).first;
			int weight = (*i).second;

			// 更新最短路径
			if (dist[v] > dist[u] + weight)
			{
				if (dist[v] != INF)
					setds.erase(setds.find(make_pair(dist[v], v)));
				dist[v] = dist[u] + weight;
				setds.insert(make_pair(dist[v], v));
			}
		}
	}

	// 输出
	printf("Vertex Distance from Source\n");
	for (int i = 0; i < V; ++i)
		printf("%d \t\t %d\n", i, dist[i]);
}



/* 测试 */
int main()
{
	int V = 9;
	Graph g(V);
	g.addEdge(0, 1, 4);
	g.addEdge(0, 7, 8);
	g.addEdge(1, 2, 8);
	g.addEdge(1, 7, 11);
	g.addEdge(2, 3, 7);
	g.addEdge(2, 8, 2);
	g.addEdge(2, 5, 4);
	g.addEdge(3, 4, 9);
	g.addEdge(3, 5, 14);
	g.addEdge(4, 5, 10);
	g.addEdge(5, 6, 2);
	g.addEdge(6, 7, 1);
	g.addEdge(6, 8, 6);
	g.addEdge(7, 8, 7);

	g.shortestPath(0);

	return 0;
}

时间复杂度

该算法使用了STL中的SET。C++ 中的集合通常使用自平衡二叉搜索树来实现,其插入、删除等集合操作的时间复杂度是对数的,因此该解决方案的时间复杂度为 O(ELogV))。E表示边数,V表示结点数。



四、Dijkstra’s Shortest Path Algorithm using priority_queue of STL

思路

本质上是利用stl中已写好的优先队列,来代替“最小堆”在算法中起到的作用——从一组尚未包含的顶点中获取最小距离顶点。

在上面的第三种方法中,我们使用了SET集合,而集合自身使用的是自平衡二叉搜索树。对于 Dijkstra 算法,始终建议使用堆(或优先队列),因为所需的操作(提取最小值和减少键)与堆(或优先级队列)的特性相匹配。

使用优先队列的问题是:priority_queue 不支持减键(decrease key)。要解决此问题,我们考虑,不是去更新key值,而是再插入一份key的副本。也就是说,允许优先队列中相同顶点存在多个实例。这种方法不需要减键操作,并且具有以下重要属性:
每当一个顶点的距离减少时,我们就会在priority_queue 中再添加一个顶点实例。即使有多个实例,我们也只考虑距离最小的实例,同一顶点的其他实例都可以不要去考虑。

代码思路

代码示例

时间复杂度



五、总结

请添加图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值