如果你有一份北京地图,想从中关村走到三元桥,那么怎样能找出实现这一目的的最短路径呢?一种可能的方法就是将这两点之间所有的路线都找出来,然后求出每条路线的距离,找出最短的路线。但是仔细想想我们就会发现这种办法几乎是不可行的,因为这样的路线太多了,而且有些路线是完全不值得考虑的,比如你从中关村到昌平再到三元桥。那这样的问题有没有更科学的解决办法呢?答案是肯定的。
我们把从中关村到三元桥之间所有路线抽象成一个加权有向图,所有路段的交点就是
图的顶点,那么这个问题就可以抽象成在加权有向图G中,求出起始点中关村,到终点
三元桥之间的最短路径。这就是我们将要介绍的最短路径问题。最短路径问题有多种变体,本文只讨论有向无环图上的单源最短路径问题。
求最短路径实际上就是求两个顶点之间权值和最小的那条路径。在前面的找路问题中,我们的权值就是距离,所以距离和最短的就是最短路径,这是一个很形象的例子。当然,这个权值也可是其它如费用、时间等的单位。记住这只是一个抽象的概念。
我们用一张图来表示这个例子:
在上图所示的带权有向无环图中,共有10个顶点,我们可以将其理解成路口,有19条带权边,我们可以理解成各段路及距离。现在假设起点是节点0,终点是节点9。我们要找出这两点之间的最短路径应该怎么办?
根据贪心算法的思想,假设我们从起点开始,取每个与起点最近的顶点来看,那结果是0–4–7–8–6–9。距离和是21显然这样得到的路径不是最优的,这个路径还不如0–3–2–9(距离和19)事实上简单的贪心算法思想往往能得到一个不算最差的解。
dijkstra算法也是贪心算法思想,但不能简单的直接每个顶点取最小权值的边。它的思想是:起点到终点的权值和最小,那么起点到最短路径的终点间的任一顶点的权值和也是最小的。这就有点加入动态规划的子问题最优解的思想了。那么我们只要找出这条最短路径上的每个顶点就可以了。
核心思想就是从起点开始不断往外查找,直到查找完所有顶点。初始时,我们只知道起点,以及与起点直接相连的边,我们不断的根据这个边的权值访问找到的最小权值和的边,并在程序运行时不断更新这个边的末端顶点到起点的距离,保证它一定是已知的最短距离。这样当我们访问完所有顶点,自然就得到了最短路径。
无论如何,我们首先要用代码来表示上图,很明显地二维数组是一个表示各边权值的好方法。
代码如下(示例):
#include
#include
#include
using namespace std;
class Graph{
private:
int vertex; //顶点数
int** matrix; //有向图关系矩阵
bool* visited; //存储是否已访问
int* dist; //存储到起点的权值和
int* pre; //存储上一个节点
public:
const int maxium = 10000; //最大值,表示不存在的边
Graph(const int edges, const int nodes, int arr[][3]){
vertex = nodes;
visited = new bool[vertex];
dist = new int[vertex];
pre = new int[vertex];
matrix = new int* [vertex]; //生成有向图关系矩阵
for (int i=0; i<vertex; ++i){
pre[i] = -1;
dist[i] = maxium;
visited[i] = false;
matrix[i] = new int[9];
for (int j=0; j<vertex; j++){
matrix[i][j] = maxium;
}
}
for (int i=0; i<edges; ++i){ //生成有向图关系,maxium为不连接
matrix[arr[i][0]][arr[i][1]] = arr[i][2];
}
}
~Graph(){
delete[] visited;
delete[] matrix;
delete[] pre;
delete[] dist;
}
看上去和以前关于图论的文章矩阵表示法的代码很像,嗯~ 事实上我就是复制的。所以充满了个人风格。这里我们定义了 maxium=10000
来表示无穷大的权值,也就是没路。int vertex;
表示顶点数。 int** matrix;
有来存储有向图的各边及权值,同样是以下标表示边。 bool* visited;
用来存储顶点是否已访问。 int* dist;
用来存储顶点到起点的权值和。int* pre;
存储从起点到本顶点的最短路径中的上一个顶点,最终可以根据这个数组得到任两个顶点间的最短路径。
代码如下(示例):
void dijkstra(int s, int end){
dist[s] = 0;
visited[s] = true;
for (int i=0; i<vertex; i++){ //与起点相连的节点到起点的距离
if (matrix[s][i] < maxium){
dist[i] = matrix[s][i];
pre[i] = s;
}
}
int curr = s;
for (int i=0; i<vertex; ++i){ //开始寻找
int tmp = maxium;
for (int j=0; j<vertex; j++){ //找出离起点最小权值的点,命名为curr
if (!visited[j] && dist[j]<tmp){
tmp = dist[j];
curr = j;
}
}
visited[curr] = true; //开始访问前面找到的离起点最近点curr
for (int k=0; k<vertex; ++k){
int new_dist = maxium; //更新权值
if (!visited[k] && matrix[curr][k] < maxium){
new_dist = dist[curr] + matrix[curr][k];
if (new_dist < dist[k]){
dist[k] = new_dist;
pre[k] = curr; //记录当前顶点的前驱
}
}
}
}
show(s, end);
}
void show(int start, int end){ //显示路径
stack<int> out;
int n = end;
while (n != start){ //将各顶点前驱压入栈
out.push(n);
n = pre[n];
}
out.push(start);
vector<int> path; //倒入向量,使顺序正常
while (out.size()){
path.push_back(out.top());
out.pop();
}
for (int i=0; i<path.size(); ++i){
cout << path[i] << " ";
}
cout << endl;
}
};
代码中Dijkstra函数就是实现算法的函数。函数有两个参数s和end,分别表示起始节点和结束节点。函数将所有节点从起始节点的距离初始化为无穷大,除了起始节点,它被初始化为0。然后它找到距离起始节点最近的节点并更新其邻居的距离。这个过程重复进行,直到所有节点都被访问过。matrix 变量表示图的邻接矩阵,其中每个元素表示两个节点之间的边的权重。dist 数组存储每个节点到起始节点的距离,而 pre存储最短路径中每个节点的前一个节点。
show 函数用于打印从起始节点到结束节点的最短路径。
本文实现了任两点间的有向无环图的最短路径计算。Dijkstra算法是一种用于在图中查找两个节点之间的最短路径的算法。该算法使用贪心策略,每次找到距离起始节点最近的节点并更新其邻居的距离。这个过程重复进行,直到所有节点都被访问过。
该算法的时间复杂度为O(V^2),其中V是节点数。如果使用堆优化,时间复杂度可以降低到O(ElogV),其中E是边数。
在实现中,我们使用邻接矩阵来表示图,其中每个元素表示两个节点之间的边的权重。以下是测试代码:
int main(){
int arr[][3] = {{0,1,8},{0,3,16,},{0,4,7},{1,3,9},{1,5,5},{2,9,2},
{3,2,1},{3,6,10},{3,8,12},{4,7,5},{4,3,9},{4,8,7},{5,3,2},
{5,2,11},{6,2,13},{6,9,2},{7,6,8},{8,7,1},{8,6,6}};
Graph t(19, 10, arr);
t.dijkstra(0, 9);
return 0;
}
最终结果是:0 1 5 3 2 9