无向图 | 单源最短路径 | Dijkstra算法 | 最小堆 |
算法描述
求上图从Start到Destination的最短路径
INF = 1e10 # 无限大
node_number = 16 # node的数目
route_length = [ # node节点间的距离矩阵
[INF, 4, INF, 2, INF, INF, 3, INF, INF, INF, INF, INF, INF, INF, INF, INF], # 节点0
[ 4, INF, 2, INF, 7, 3, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF], # 节点1
[INF, 2, INF, 5, INF, 2, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF], # 节点2
[ 2, INF, 5, INF, INF, INF, INF, 8, INF, INF, INF, INF, INF, INF, INF, INF], # 节点3
[INF, 7, INF, INF, INF, 5, INF, INF, INF, INF, 6, INF, 8, INF, 9, INF], # 节点4
[INF, 3, 2, INF, 5, INF, INF, INF, 3, INF, 9, INF, INF, INF, INF, INF], # 节点5
[ 3, INF, INF, INF, INF, INF, INF, 5, 4, 7, INF, INF, INF, INF, INF, INF], # 节点6
[INF, INF, INF, 8, INF, INF, 5, INF, INF, 1, INF, 8, INF, INF, INF, INF], # 节点7
[INF, INF, INF, INF, INF, 3, 4, INF, INF, INF, 4, INF, INF, INF, INF, INF], # 节点8
[INF, INF, INF, INF, INF, INF, 7, 1, INF, INF, 4, INF, INF, INF, INF, INF], # 节点9
[INF, INF, INF, INF, 6, 9, INF, INF, 4, 4, INF, 4, INF, 7, INF, INF], # 节点10
[INF, INF, INF, INF, INF, INF, INF, 8, INF, INF, 4, INF, INF, 2, INF, INF], # 节点11
[INF, INF, INF, INF, 8, INF, INF, INF, INF, INF, INF, INF, INF, 8, INF, 9], # 节点12
[INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 7, 2, 8, INF, 4, 8], # 节点13
[INF, INF, INF, INF, 9, INF, INF, INF, INF, INF, INF, INF, INF, 4, INF, 3], # 节点14
[INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 9, 8, 3, INF]] # 节点15
cost = [INF] * node_number # 从起点到各个节点间的距离初始化为INF
pre_node= [0] * node_number # 记录索引对应节点的上一个节点(方便打印路径)
cost[0]=0 # cost of start to start is 0. After that, cost is: [0,INF,INF,...INF]
L = [i for i in range(node_number)] # list of node.
while L:
minimum=INF
min_cost_node=0
for i in L: # find the index of the node which has the minimum cost in list 'cost'.
if minimum>cost[i] and i!=-1: # i=-1 means that node 'i' is deleted in the previous loop.
minimum=cost[i]
min_cost_node=i
if min_cost_node==15: # 15 is the end node we want to go.
break
L[min_cost_node]=-1 # delete the node in list 'L'
for index,val in enumerate(route_length[min_cost_node]): # route_length[min_cost_node] is one row of the matrix.
'''
For example,when min_cost_node is node '0',
then the row is [INF, 4, INF, 2, INF, INF, 3, INF, INF, INF, INF, INF, INF, INF, INF, INF]. # ノード0
in this 'for' loop, (index,val) can be (0,INF),(1,4),(2,INF),(3,2)...(15,INF).
'''
if val<INF: # val
if cost[index]>cost[min_cost_node]+val:
'''
Update the list 'cost'.Because we find a path
which go through node 'min_cost_node' costs fewer then the path found previously.
'''
cost[index] = cost[min_cost_node] + val
'''
Because this path has a road from node 'min_cost_node' to node 'index',
we record this road.Just let node 'min_cost_node' be the previous node of node 'index'.
'''
pre_node[index]=min_cost_node
# path is the result we find.
path=[]
vertex=15
while vertex!=0:
path.append(vertex)
vertex=pre_node[vertex]
path.append(0)
print('The minimum cost path is: ')
for i in range (len(path)-1,0,-1):
print(path[i],'-->',end='')
print(path[0])
print('The cost is: ',cost[15])
C++解法
/*
计算从节点's'开始的最短路径
结果存储在数组D中
*/
void Dijkstra(graph* G, int* D, int s) {
int i, v, w;
for (i = 0; i < G->n(); ++i) {
v = minVertex(G, D);
if (D[v] == INF)
return; //说明节点v是孤立点,不可到达
G->setMark(v, VISITED);
for (w = G->first(v); w < G->n(); w = G->next(v, w))
if (D[w] > D[v] + G->weight(v, w)) {
D[w] = D[v] + G->weight(v, w);
/*顶点w的上一个顶点是v*/
path[w] = v;
}
}
}
主函数所在文件
#include
#include
#include "graphm.h"
#include
const int INF = 1e8;
const int VISITED = 1;
const int N = 16;
int path[1000];
int D[N];
/*找到当前花费最小的顶点*/
int minVertex(graph* G, int* D) {
int i, v = -1;
/*从头开始,找到一个没有被访问过的顶点,赋值给v*/
for(i=0;i<G->n();++i)
if (G->getMark(i) == UNVISITED) {
v = i; break; }
/*与上面的顶点比较花费大小,找到花费最小的未被访问的顶点*/
for (i++; i < G->n(); i++)
if (G->getMark(i) == UNVISITED && (D[i] < D[v]))
v = i;
return v;
}
/*
计算从节点's'开始的最短路径
结果存储在数组D中
*/
void Dijkstra(graph* G, int* D, int s) {
int i, v, w;
for (i = 0; i < G->n(); ++i) {
v = minVertex(G, D);
if (D[v] == INF)
return; //说明节点v是孤立点,不可到达
G->setMark(v, VISITED);
for (w = G->first(v); w < G->n(); w = G->next(v, w))
if (D[w] > D[v] + G->weight(v, w)) {
D[w] = D[v] + G->weight(v, w);
/*顶点w的上一个顶点是v*/
path[w] = v;
}
}
}
int main() {
int matrix[N][N] = {
// node节点间的距离矩阵
{
INF, 4, INF, 2, INF, INF, 3, INF, INF, INF, INF, INF, INF, INF, INF, INF} , // 节点0
{
4, INF, 2, INF, 7, 3, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF}, // 节点1
{
INF, 2, INF, 5, INF, 2, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF}, // 节点2
{
2, INF, 5, INF, INF, INF, INF, 8, INF, INF, INF, INF, INF, INF, INF, INF}, // 节点3
{
INF, 7, INF, INF, INF, 5, INF, INF, INF, INF, 6, INF, 8, INF, 9, INF}, // 节点4
{
INF, 3, 2, INF, 5, INF, INF, INF, 3, INF, 9, INF, INF, INF, INF, INF}, // 节点5
{
3, INF, INF, INF, INF, INF, INF, 5, 4, 7, INF, INF, INF, INF, INF, INF}, // 节点6
{
INF, INF, INF, 8, INF, INF, 5, INF, INF, 1, INF, 8, INF, INF, INF, INF}, // 节点7
{
INF, INF, INF, INF, INF, 3, 4, INF, INF, INF, 4, INF, INF, INF, INF, INF}, // 节点8
{
INF, INF, INF, INF, INF, INF, 7, 1, INF, INF, 4, INF, INF, INF, INF, INF}, // 节点9
{
INF, INF, INF, INF, 6, 9, INF, INF, 4, 4, INF, 4, INF, 7, INF, INF}, // 节点10
{
INF, INF, INF, INF, INF, INF, INF, 8, INF, INF, 4, INF, INF, 2, INF, INF}, // 节点11
{
INF, INF, INF, INF, 8, INF, INF, INF, INF, INF, INF, INF, INF, 8, INF, 9}, // 节点12
{
INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 7, 2, 8, INF, 4, 8}, // 节点13
{
INF, INF, INF, INF, 9, INF, INF, INF, INF, INF, INF, INF, INF, 4, INF, 3}, // 节点14
{
INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 9, 8, 3, INF}// 节点15
};
memset(path, -1, sizeof(path));
graph* G = new graphm(N);
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
G->setEdge(i, j, matrix[i][j]);
for (int i = 0; i < N; ++i)
D[i] = INF;
D[0] = 0; //顶点和顶点自己之间的距离为0
Dijkstra(G, D, 0);
std::cout << "从起点到终点的最短路径是: ";
int prev = N - 1;
std::stack<int>pth;
while (path[prev] != -1) {
pth.push(path[prev]);
prev = path[prev];
}
while (!pth.empty()) {
std::cout << pth.top() << " --> ";
pth.pop();
}
std::cout <<N-1<< std::endl;
std::cout <<"总距离为: "<< D[N - 1] << std::endl;
}
文件2:graph.h(图的抽象类)
#pragma once
/*程序代码源于《数据结构与算法C++描述第三版》*/
/*
图的抽象类,这里假设图被创建后,图的顶点数目不变
*/
class graph
{
private:
void operator=(const graph&) {
} //保护图,防止图被显示赋值
graph(const graph&) {
} //复制构造函数也设为私有
public:
graph() {
} //默认构造函数
virtual ~graph() {
} //析构函数
//初始化有n个节点的图
virtual void Init(int n) = 0;
//返回图的顶点数目和边的数目
virtual int n() = 0;
virtual int e() = 0;
//返回顶点'v'的第一个相邻顶点
virtual int first(int v) = 0;
//返回顶点v的下一个相邻顶点(在顶点'w'之后)
virtual int next(int v, int w) = 0;
//设置边的权重,i,j是顶点,wgt是边的权重
virtual void setEdge(int i, int j, int wgt)=0;
//删除边,i,j是顶点
virtual void delEdge(int i, int j) = 0;
//判断边是否在图中,i,j是顶点,如果边(i,j)权重不为0返回true
virtual bool isEdge(int i, int j) = 0;
//返回边的权重,i,j是顶点,
virtual int weight(int i, int j) = 0;
//获取或者设置顶点'v'的标志值,方便确认这个顶点之前是否被我们访问过
//val是要设置的值
virtual int getMark(int v) = 0;
virtual void setMark(int v, int val) = 0;
};
文件3:graphm.h(图的相邻矩阵实现)
#pragma once
#include"graph.h"
const int UNVISITED = 0;
class graphm: public graph
{
private:
int numVertex, numEdge; //顶点和边的数目
int** matrix; //相邻矩阵
int* mark; //标记数组
public:
graphm(int numVert) {
//构造函数
Init(numVert);
}
~graphm() //析构函数
{
delete[]mark;
for (int i = 0; i < numVertex; ++i)
delete[]matrix[i];
delete []matrix;
}
//初始化有n个顶点的图
void Init(int n) {
int i;
numVertex = n;
numEdge = 0;
mark = new int[n];
//初始化标记数组
for (i = 0; i < numVertex; ++i)
mark[i] = UNVISITED;
matrix = new int* [numVertex];
for (i = 0; i < numVertex; ++i)
matrix[i] = new int[numVertex];
for (i = 0; i < numVertex; ++i)
for (int j = 0; j < numVertex; ++j)
matrix[i][j] = 0; //0代表边不存在
}
//顶点数目
int n() {
return numVertex; }
//边的数目
int e() {
return numEdge; }
//返回顶点'v'的第一个相邻顶点
int first(int v) {
for (int i = 0; i < numVertex; ++i)
if (matrix[v][i] != 0)return i;
return numVertex; //代表这个点是孤立点
}
//返回顶点v的下一个相邻顶点(在顶点'w'之后)
int next(int v, int w) {
for (int i = w + 1; i < numVertex; ++i)
if (matrix[v][i] != 0)return i;
return numVertex; //没有下一个顶点
}
//设置边的权重,i,j是顶点,wgt是边的权重
void setEdge(int i, int j, int wgt) {
if (wgt <= 0)return;
if (matrix[i][j] == 0)++numEdge;
matrix[i][j] = wgt;
}
//删除边,i,j是顶点
void delEdge(int i, int j) {
if (matrix[i][j] != 0)--numEdge;
matrix[i][j] = 0;
}
//判断边是否在图中,i,j是顶点,如果边(i,j)权重不为0返回true
bool isEdge(int i, int j) {
return matrix[i][j] != 0;
}
//返回边的权重,i,j是顶点,
int weight(int i, int j) {
return matrix[i][j]; }
//获取或者设置顶点'v'的标志值,方便确认这个顶点之前是否被我们访问过
//val是要设置的值
int getMark(int v) {
return mark[v]; }
void setMark(int v, int val) {
mark[v] = val; }
};
上面C++寻找minVertex的过程可以优化,下面代码运行没有问题,输出结果和上面的一样。时间复杂度是 O ( ∣ E ∣ l o g 2 ∣ E ∣ ) O(|E|log_2|E|) O(∣E∣log2∣E∣)
/*程序代码源于《数据结构与算法C++描述第三版》*/
class DijkElem {
public:
int vertex, distance;
DijkElem() {
vertex = -1; distance = -1; }
DijkElem(int v, int d) {
vertex = v; distance = d; }
};
class DDComp {
public:
static bool prior(DijkElem a, DijkElem b) {
return (a.distance < b.distance);
}
};
/*用优先队列实现Dijkstra算法*/
void Dijkstra2(graph* G, int* D, int s) {
/*v是当前的minVertex*/
int i, v, w;
DijkElem temp;
DijkElem *E=new DijkElem[G->e()];
temp.distance = 0;
temp.vertex = s;
E[0] = temp;
heap<DijkElem, DDComp>H(E, 1, G->e());
for (int i = 0; i < G->n(); ++i) {
do {
if (H.size() == 0)return;
temp = H.removefirst();
v = temp.vertex;
} while (G->getMark(v) == VISITED);
G->setMark(v, VISITED);
if (D[v] == INF)return; //说明节点v是孤立点,不可到达
for(w=G->first(v);w<G->n();w=G->next(v,w))
if (D[w] > D[v] + G->weight(v, w)) {
D[w] = D[v] + G->weight(v, w);
path[w] = v;
temp.distance = D[w];
temp.vertex = w;
/*在最小堆中插入新的节点(距离)*/
H.insert(temp);
}
}
delete[]E;
}
上面的heap数据结构可以用C++ STL里面的priority_queue,也可以自己写一个最小堆,我用的是之前照着书上写的最小堆代码:
heap.h
#pragma once
#include
#include
using namespace std;
template <typename E, typename Comp>
class heap
{
private:
E* Heap; //Pointer to the heap array
int maxsize; //Maximum size of the heap
int n; //Number of elements now in the heap
//Helper function to put element in its correct place,and that moves the father node down
void siftdown(int pos);
public:
heap(E* h, int num, int max);
int size()const;
bool isLeaf(int pos)const;
int leftchild(int pos)const;
int rightchild(int pos)const;
int parent(int pos)const;
void buildHeap();
void insert(const E& it);
E removefirst();
E remove(int pos);
};
//Swap two elements in the container
template<typename E>
void swap(E* array, int pos1, int pos2) {
E temp = array[pos1];
array[pos1] = array[pos2];
array[pos2] = temp;
}
//Heap class
// E is the element in heap
//Comp is a class used to compare two elements
//Helper function to put element in its correct place,and that moves the father node down
template <typename E, typename Comp>
void heap<E, Comp>::siftdown(int pos) {
while (!isLeaf(pos)) {
//Stop if pos is a leaf
int j = leftchild(pos);
int rc = rightchild(pos);
if ((rc < n) && Comp::prior(Heap[rc], Heap[j]))
j = rc; //Set j to greater child's value
if (Comp::prior(Heap[pos], Heap[j]))
return; //Done
swap(Heap, pos, j);
pos = j; //Move down
}
}
/*
template
E& heap::operator[](int i) {
assert(i < maxsize); //Interrupt if i>=maxsize
return E[i];
}
*/
template <typename E, typename Comp>
heap<E, Comp>::heap(E* h, int num, int max) {
Heap = h;
n = num;
maxsize = max;
buildHeap();
}
//Return current heap size
template <typename E, typename Comp>
int heap<E, Comp>::size()const {
return n;
}
//True if pos is a leaf
template <typename E, typename Comp>
bool heap<E, Comp>::isLeaf(int pos)const {
return ((pos >= n / 2) && (pos < n));
}
//Return leftchild position
template <typename E, typename Comp>
int heap<E, Comp>::leftchild(int pos)const {
return 2 * pos + 1;
}
//Return rightchild position
template <typename E, typename Comp>
int heap<E, Comp>::rightchild(int pos)const {
return 2 * pos + 2;
}
//Return parent position
template <typename E, typename Comp>
int heap<E, Comp>::parent(int pos)const {
return (pos - 1) / 2;
}
//Heapify contents of Heap
template <typename E, typename Comp>
void heap<E, Comp>::buildHeap() {
for (int i = n / 2 - 1; i >= 0; --i) {
siftdown(i);
}
}
//Insert "it" into the heap
template <typename E, typename Comp>
void heap<E, Comp>::insert(const E& it) {
assert(n < maxsize); //Terminate if heap is full
int curr = n++;
Heap[curr] = it; //Start at end of heap
//Move up until curr's parent > curr
while ((curr != 0) && (Comp::prior(Heap[curr], Heap[parent(curr)]))) {
swap(Heap, curr, parent(curr));
curr = parent(curr);
}
}
template <typename E, typename Comp>
E heap<E, Comp>::removefirst() {
assert(n > 0); //Interrupt if heap is empty
swap(Heap, 0, --n); //Swap first with last value.
if (n != 0)siftdown(0); //Siftdown new root val
return Heap[n]; //Return deleted value
}
template <typename E, typename Comp>
E heap<E, Comp>::remove(int pos) {
assert((pos >= 0) && (pos < n)); //Intertupt if it is in bad position
if (pos == n - 1)n--; //Last element,no work to do
else {
swap(Heap, pos, --n); //Swap with last value
while ((pos != 0) && (Comp::prior(Heap[pos], Heap[parent(pos)]))) {
swap(Heap, pos, parent(pos)); //Push up large key
pos = parent(pos);
}
if (n != 0)siftdown(pos); //Push down small key
}
return Heap[n];
}