Dijkstra最短路径(稠密图+稀疏图)堆优化

468 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

伪代码描述
在这里插入图片描述
时间复杂度
在这里插入图片描述
方法1: 解决稠密图

#include <iostream>		//解决稠密图时
#include <algorithm>
using namespace std;
const int MaxV = 100;
const int INF = 10000;	//表示无穷大,Weight < INF
/*定义边*/
typedef struct ENode{
	int v1, v2;
	int Weight;
} * Edge;

/*定义邻接矩阵图*/
typedef struct GNode{
	int Nv, Ne;
	int W[MaxV][MaxV];
	string Data[MaxV];
} * MGraph;
/*初始化图*/
MGraph CreateGraph(int Nv)
{
	MGraph G = new GNode;
	G->Nv = Nv; G->Ne = 0;
	for (int i = 0; i < G->Nv; i++)
		for (int j = 0; j < G->Nv; j++)
			G->W[i][j] = INF;

	return G;
}
/*无向边的插入*/
void InsertEdge(MGraph G, Edge E)
{
	G->W[E->v1][E->v2] = E->Weight;
	G->W[E->v2][E->v1] = E->Weight;
}
/*输入图的数据,建立图*/
MGraph BuildGraph()
{
	int Nv; cin >> Nv;
	MGraph G = CreateGraph(Nv);
	cin >> G->Ne;
	Edge E = new ENode;
	for (int i = 0; i < G->Ne; i++)
	{
		cin >> E->v1 >> E->v2 >> E->Weight;
		InsertEdge(G, E);
	}
	delete E;
	return G;
}
/*稠密图的找最小dist*/
int FindMinDist(MGraph G, int dist[], bool tag[])
{
	int MinDist = INF;
	int MinV;
	for (int i = 0; i < G->Nv; i++)
		if (!tag[i] && dist[i] < MinDist)
		{
			MinDist = dist[i];
			MinV = i;
		}
	if(MinDist != INF)
		return MinV;
	else
		return -1;
}

bool Dijkstra(MGraph G, int dist[], int path[], int s)	//s为起点
{
	/*初始化数组*/
	bool tag[MaxV] = {0};
	for (int i = 0; i < G->Nv; i++)
	{
		dist[i] = G->W[s][i];
		if(dist[i] < INF)
			path[i] = s;
		else
			path[i] = -1;
	}
	dist[s] = 0;
	tag[s] = true;

	while (1)
	{
		int v = FindMinDist(G, dist, tag);
		if(v == -1)
			break;
		tag[v] = true;
		for (int i = 0; i < G->Nv; i++)
			if(!tag[i] && G->W[v][i] < INF)	//如果没有访问过,且有边
			{
				if(G->W[v][i] < 0)
					return false;		//存在负边
				if(dist[i] > dist[v] + G->W[v][i])
				{
					dist[i] = dist[v] + G->W[v][i];
					path[i] = v;
				}
			}
	}

	return true;
}
/*输出最短路径*/
void Print(int path[], int v, int s) //给定终点输出最短路径
{
	if (v < 0)
		return;
	Print(path, path[v], s);
	if (v == s)
		cout << v;
	else
		cout << "->" << v;
}
int main()
{
	int dist[MaxV], path[MaxV];
	MGraph G = BuildGraph();
	if(!Dijkstra(G, dist, path, 0))
		cout << "存在负边" << endl;
	else
	{
		cout << "以0为起点到各点的最短路径为:" << endl;
		for (int i = 1; i < G->Nv; i++)
		{
			Print(path, i, 0);
			cout << " dist=" << dist[i] << endl;
		}
	}
	delete G;
	system("pause");
	return 0;
}
input:
10 17
0 1 2
0 3 5
1 2 5
1 3 2
2 4 8
2 5 4
5 3 4
3 6 2
4 5 2
6 5 3
4 7 5
5 7 9
5 8 6
6 8 7
7 8 3
7 9 4
8 9 8
output:
以0为起点到各点的最短路径为:
0->1 dist=2
0->1->2 dist=7
0->1->3 dist=4
0->1->3->5->4 dist=10
0->1->3->5 dist=8
0->1->3->6 dist=6
0->1->3->5->4->7 dist=15
0->1->3->6->8 dist=13
0->1->3->5->4->7->9 dist=19
请按任意键继续. . .

方法2: 解决稀疏图

图的结构定义

#include <iostream>		//解决稀疏图的Dijkstra算法
#include <algorithm>
using namespace std;
const int MaxV = 100;
const int INF = 100000;
/*************图的结构定义***************/
/*定义边*/
typedef struct ENode{
	int v1, v2;
	int Weight;
} * Edge;
/*定义邻接顶点*/
struct AdjVNode{
	int AdjV;
	int Weight;
	AdjVNode *Next;
};
/*定义邻接表头*/
typedef struct VNode{
	AdjVNode *EdgeFirst;
	//string Data;
} AdjList[MaxV];
/*定义图*/
typedef struct GNode{
	int Nv, Ne;
	AdjList L;
} * LGraph;

图的操作

/*创造无边的图*/
LGraph CreateGraph(int Nv)
{
	LGraph G = new GNode();
	G->Nv = Nv;
	return G;
}
/*给图插入有向边*/
void InsertEdge(LGraph G, Edge E)
{
	AdjVNode *A = new AdjVNode;
	A->AdjV = E->v2; A->Weight = E->Weight;
	A->Next = G->L[E->v1].EdgeFirst;
	G->L[E->v1].EdgeFirst = A;
}
/*建立一个有向网图*/
LGraph BuildGraph()
{
	int Nv; cin >> Nv;
	LGraph G = CreateGraph(Nv);
	cin >> G->Ne;
	Edge E = new ENode;
	for (int i = 0; i < G->Ne; i++)
	{
		cin >> E->v1 >> E->v2 >> E->Weight;
		InsertEdge(G, E);
	}
	delete E;
	return G;
}
/*删除图*/
void DeleteGraph(LGraph G)
{
	AdjVNode *A;
	for (int i = 0; i < G->Nv; i++)
		while (G->L[i].EdgeFirst)
		{
			A = G->L[i].EdgeFirst;
			G->L[i].EdgeFirst = A->Next;
			delete A;
		}
	delete G;
}

索引堆的结构定义

/***********最小索引堆结构定义************/

typedef struct HNode{
	int *Index;		//dist的索引
	int *Reverse;	//dist的索引的索引
	int Size;		//当前堆大小
	int Capacity;	//堆的容量
} * MinHeap;

堆的相关操作

/*************堆的操作**************/
/*最小索引堆的初始化*/
MinHeap CreateMinHeap(int MaxSize, int s)	//存顶点的堆
{
	MinHeap H = new HNode;
	H->Size = 0; H->Capacity = MaxSize;
	H->Index = new int[MaxSize + 1];
	H->Reverse = new int[MaxSize + 1];
	H->Index[0] = s;		//哨兵,具体等于单源最短路径的起点S
	return H;
}
/*由改过的Index[i] = j得到Rev[j] = i*/
void SwapIndex(MinHeap H, int i)	//当Index有修改是修改Reverse
{
	H->Reverse[H->Index[i]] = i;
}

/*最小堆元素改小,上滤, 与插入操作类似*/		//如果将i改为 ++H->Size就是插入操作
void Fix_up(MinHeap H, int i, int x, int dist[])	//将dist[i] 改为x,并调整最小堆, 相当于 dist[i] = x
{
	dist[i] = x;	//先修改
	int child = H->Reverse[i];
	for ( ; x < dist[H->Index[child / 2]]; child /= 2)
	{
		H->Index[child] = H->Index[child / 2];	//相当于 dist[child] = dist[child / 2]
		SwapIndex(H, child);
	}
	H->Index[child] = i;	//用dist作比较,Index做交换. 相当于 dist[child] = x
	SwapIndex(H, child);
}

/*最小堆的插入, 上滤*/
void InsertIndex(MinHeap H, int v, int dist[])	//相当于 dist[i] = x	(这与上面的不同, 这是在初始)
{
	int child = ++H->Size;	//dist[]元素个数与H->Index[]个数相同!!!!  (在本算法中插入在开始前一次完成)
	int x = dist[v];
	for ( ; x < dist[H->Index[child / 2]] ; child /= 2)	//上滤
	{
		H->Index[child] = H->Index[child / 2];
		SwapIndex(H, child);
	}
	H->Index[child] = v;	//此时插在dist[]的最后一个位置	, 最后得到dist[H->Size]的位置
	SwapIndex(H, child);
}
/*返回堆最小元素的索引*/
int GetMinHeap(MinHeap H, int dist[])	//删除后dist[Index[1]] = 0
{
	int v = H->Index[1];
	int x = dist[H->Index[H->Size--]];
	int child, parent;
	for( parent = 1; parent * 2 <= H->Size; parent = child)	//下滤
	{
		child = 2 * parent;
		if(dist[H->Index[child + 1]] < dist[H->Index[child]] && child != H->Size)
			child++;
		if(x > dist[H->Index[child]])
		{
			H->Index[parent] = H->Index[child];
			SwapIndex(H, parent);
		}
		else
			break;
	}
	H->Index[parent] = H->Index[H->Size + 1];
	SwapIndex(H, parent);
	return v;
}

Dijkstra算法解决稀疏图

/****************Dijkstra算法*******************/
bool Dijkstra(LGraph G, int dist[], int path[], int s)	//s为起点
{
	/*初始化数组*/
	bool tag[MaxV] = {0};
	AdjVNode *A = G->L[s].EdgeFirst;
	while (A)	//遍历起点的所有边
	{
		dist[A->AdjV] = A->Weight;
		path[A->AdjV] = s;
		A = A->Next;
	}
	dist[s] = 0;
	tag[s] = true;
	/*建立索引最小堆存储dist[], 初始化堆*/
	MinHeap H = CreateMinHeap(G->Nv, s);
	for (int i = 0; i < G->Nv; i++)
		if(i != s)
			InsertIndex(H, i, dist);

	while (H->Size > 0)	//最小堆非空
	{
		int v = GetMinHeap(H, dist);
		tag[v] = true;	//表示已访问
		A = G->L[v].EdgeFirst;
		while (A)
		{
			if(!tag[A->AdjV])	//如果是没访问过的顶点
			{
				if(A->Weight < 0)	//出现负边
					return false;
				if (dist[v] + A->Weight < dist[A->AdjV]) //如果是更短的路
				{
					Fix_up(H, A->AdjV, dist[v] + A->Weight, dist); //dist[A->AdjV] = dist[v] + A->Weight
					path[A->AdjV] = v;
				}
			}
			A = A->Next;
		}
	}
	/*删除堆*/
	delete[] H->Index;
	delete[] H->Reverse;
	delete H;

	return true;
}
/*输出给定终点的最短路径*/
void Print(int path[], int v, int s)
{
	if(v < 0)
		return;
	Print(path, path[v], s);
	if(v == s)
		cout << v;
	else
		cout << "->" << v;
}

main()函数

int main()
{
	int dist[MaxV], path[MaxV];
	int s;	//起点
	LGraph G = BuildGraph();
	for (int i = 0; i < G->Nv; i++)
	{
		dist[i] = INF;
		path[i] = -1;
	}
	cin >> s;
	if(!Dijkstra(G, dist, path, s))
		cout << "存在负边" << endl;
	else
	{
		cout << "以" << s << "为起点到各点的最短路径为:" << endl;
		for (int i = 1; i < G->Nv; i++)
		{
			Print(path, i, s);
			cout << " dist=" << dist[i] << endl;
		}
	}
	DeleteGraph(G);

	system("pause");
	return 0;
}

运行结果

input:
10 17
0 2 5
0 1 8
1 4 5
1 2 5
1 5 4
4 5 4
2 3 8
2 6 4
5 6 4
5 8 2
3 6 7
6 8 3
3 7 5
6 7 5
6 9 6
8 9 7
7 9 6
0
output:
以0为起点到各点的最短路径为:
0->1 dist=8
0->2 dist=5
0->2->3 dist=13
0->1->4 dist=13
0->1->5 dist=12
0->2->6 dist=9
0->2->6->7 dist=14
0->2->6->8 dist=12
0->2->6->9 dist=15
请按任意键继续. . .