Dijkstra算法是最经典的单源最短路算法,它很像是广度优先遍历与最小生成树的结合,从出发点开始向四周扩散,直到搜索完毕所有节点为止,因此它很适合用来计算节点较少的稀疏图的最短路径问题,但是它以简单粗暴著称,算法思想属于贪心法的范畴。
在初始时先将起点放入,从起点开始依次遍历邻居节点,将邻居的边权值设置为目前邻居的路径长度,再从中找到从起点开始路径最短的邻居节点,作为下一次遍历的起点,纳入到已搜索的节点中。在每次遍历过程中如果发现某节点由该起点出发的路径比之前存储的长度更短,则更新该节点的路径长度。算法结束的条件是所有节点都被纳入到已搜索的范围中,这样整个算法的伪代码如下:
vertexfound = 0;
while(vertexfound != vertexnum)
{
for(1 to vertexnum)
find shortest;
shortest.visitied = true;
vertexfound++;
for(1 to vertexnum)
if(distance(shortest) + edge(shortest)(i) < distance(i))
distance(i) = distance(shortest) + edge(shortest)(i);
path(i) = path(shortest) + i;
}
我们先用邻接矩阵存储图以及粗暴遍历进行求解,代码如下:
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#ifndef INT_MAX;
#define INT_MAX 1000;
using namespace std;
int x, y, i, j, value, start, goal;
int mark = 0;
int vertex_num;
struct Line
{
int i, j, value;
};
struct Node
{
int i;
int x, y;
bool visited = false;
int value;
vector<int> path;
};
struct Graph
{
struct Node vertex[10];
int edge[10][10];
};
vector<struct Node> nodes;
vector<struct Line> lines;
//构造一个无向正权图
void makeGraph(Graph& G)
{
for(int i = 0; i < 10; i++)
{
for(int j = 0; j < 10; j++)
{
G.edge[i][j] = INT_MAX;
}
}
vertex_num = nodes.size();
for(int i = 0; i < vertex_num; i++)
{
G.vertex[i] = nodes[i];
}
for(int j = 0; j < lines.size(); j++)
{
G.edge[lines[j].i][lines[j].j] = lines[j].value;
G.edge[lines[j].j][lines[j].i] = lines[j].value;
}
}
void pathDijkstra(Graph& G, int start, int goal)
{
int visited_num = 0;
int shortest;
//把起点纳入已搜索范围中
G.vertex[start].visited = true;
G.vertex[start].value = 0;
++visited_num;
//搜索与起点相连接的顶点
for(int i = 0; i < vertex_num; i++)
{
G.vertex[i].value = G.edge[start][i];
G.vertex[i].path.push_back(start);
G.vertex[i].path.push_back(i);
}
//当所有顶点均被搜索完毕则结束
while(visited_num != vertex_num)
{
int min = INT_MAX;
//在图中找寻路径最短的点
for(int i = 0; i < vertex_num; i++)
{
if(!G.vertex[i].visited && G.vertex[i].value < min && G.vertex[i].value > 0)
{
min = G.vertex[i].value;
shortest = i;
}
}
//将其纳入已搜索范围
G.vertex[shortest].visited = true;
++visited_num;
//若经过本轮最近点到达某点会更近,则更新该点的路径与长度
for(int j = 0; j < vertex_num; j++)
{
if(!G.vertex[j].visited && G.vertex[shortest].value + G.edge[shortest][j] < G.vertex[j].value)
{
G.vertex[j].value = G.vertex[shortest].value + G.edge[shortest][j];
G.vertex[j].path = G.vertex[shortest].path;
G.vertex[j].path.push_back(j);
}
}
}
cout << "value = " << G.vertex[goal].value << endl;
for(int k = 0; k < G.vertex[goal].path.size(); k++)
{
cout << G.vertex[goal].path[k];
}
}
int main(int argc, char **argv)
{
Graph G;
nodes.clear();
lines.clear();
cout << "Please input vertex" << endl;
while(cin >> x >> y)
{
struct Node node;
node.i = mark++;
node.x = x;
node.y = y;
nodes.push_back(node);
}
cin.clear();
cin.ignore();
cout << "Please input edge" << endl;
while(cin >> i >> j >> value)
{
struct Line line;
line.i = i;
line.j = j;
line.value = value;
lines.push_back(line);
}
cin.clear();
cin.ignore();
makeGraph(G);
cout << "Graph is made" << endl;
cout << "Please input begin and end" << endl;
while(cin >> start >> goal)
{
cout << "Dijkstra begin" << endl;
pathDijkstra(G, start, goal);
}
return 0;
}
#endif
代码写的有点随意,不过不要在意这些细节。。。算法由两层遍历循环组成,因此时间复杂度为O(n²),大家应该都发现了在遍历所有节点后寻找最短路径的某个邻居节点用堆优化可以降低到O(nlogn)。所谓堆优化是运用优先级队列求每一次遍历的最短路径。
另外,如果节点非常多(像算法题中几千几万个都有可能),但其中的边比较少,用邻接矩阵遍历就比较浪费了,明显邻接表更适合,而且用邻接表在搜索更新路径的时候可以直接只更新与该点相邻的顶点,防止再次遍历图顶点,这样进一步减小时间复杂度。因此优化后的代码如下:
struct Edge
{
int end_vertex;
int weight;
struct Edge* next = NULL;
};
struct Node
{
int i;
int x, y;
bool visited = false;
int value = INT_MAX;
vector<int> path;
struct Edge* first_edge;
bool operator< (const struct Node& another) const
{
return value > another.value;
}
};
struct Graph
{
vector<struct Node> nodes;
};
vector<struct Node> nodes;
vector<struct Line> lines;
void makeGraph(Graph& G)
{
struct Edge* link, *q;
for(int i = 0; i < nodes.size(); i++)
{
for(int j = 0; j < lines.size(); j++)
{
if(lines[j].i == nodes[i].i)
{
link->end_vertex = lines[j].j;
link->weight = lines[j].weight;
nodes[i].first_edge = link;
break;
}
else if(lines[j].j == nodes[i].i)
{
link->end_vertex = lines[j].i;
link->weight = lines[j].weight;
nodes[i].first_edge = link;
break;
}
}
}
for(int i = 0; i < nodes.size(); i++)
{
for(int j = 0; j < lines.size(); j++)
{
if(lines[j].i == nodes[i].i && lines[j].j != nodes[i].first_edge->end_vertex)
{
link->end_vertex = lines[j].j;
link->weight = lines[j].weight;
q = nodes[i].first_edge;
while(q->next)
{
q = q->next;
}
q->next = link;
}
}
}
G.nodes = nodes;
}
void pathDijkstra(Graph& G, int start, int goal)
{
std::priority_queue<struct Node> queue;
struct Edge* link;
G.nodes[start].visited = true;
G.nodes[start].value = 0;
queue.push(G.nodes[start]);
link = G.nodes[start].first_edge;
while(link->next)
{
link = link->next;
queue.push(G.nodes[link->end_vertex]);
}
struct Node shortest = queue.top();
queue.pop();
G.nodes[shortest.i].visited = true;
G.nodes[shortest.i].value = G.nodes[shortest.i].first_edge->weight;
G.nodes[shortest.i].path.push_back(shortest.i);
while(!queue.empty())
{
shortest = queue.top();
if(!shortest.visited)
{
link = shortest.first_edge;
G.nodes[shortest.i].visited = true;
while(link->next)
{
link = link->next;
if(shortest.value + link->weight < G.nodes[link->end_vertex].value)
{
G.nodes[link->end_vertex].value = shortest.value + link->weight;
G.nodes[link->end_vertex].path = shortest.path;
G.nodes[link->end_vertex].path.push_back(shortest.i);
}
queue.push(G.nodes[link->end_vertex]);
}
}
queue.pop();
}
cout << G.nodes[goal].value << endl;
}