最短路 dijkstra(迪杰斯特拉) 算法模板 原理 正确性和非负证明 代码实现 获取最短路径 堆优化

超详细小白也能看懂的dijkstra教学视频
以下内容均为视频的配套

文章目录

  • 理论
    • 介绍
    • 步骤
  • 证明
    • 已选取节点不改性
    • 正确性
    • 负权失效
  • 代码
    • 朴素实现与路径
  • 堆优化
    • 原理
    • 代码

理论

介绍

dijkstra是求单源最短路的一个算法 一般来说只用来处理非负权图

步骤

具体的数学步骤不是我的风格
个人总结出来的dijkstra就只有两个步骤:

  • 选取一个节点 该节点满足:
    • 距离最短
    • 没有被选取过
  • 对于选取的节点 对它所有的邻接节点进行松弛操作

松弛操作大概就是能够选到一个节点C作为中间点 使得原路径AB变为ACB后总距离更短 视频中有画图形象解释

证明

已选取节点不改性

其实这个很容易
对于一直在更新迭代的距离数组dis 它的值只会增加而不会减少
因为边的权值都是非负的 每次更新距离只会在已选取节点的距离上增加 而不会减少

正确性

采用直观的归纳证明 非数学证明

  1. 首先 显然除了源点A外选取的第一个点B的最短距离肯定是正确的

反证: 假设选取点B距离不是最短 那么必定存在一条路使得ACB比AB距离要短 但是这个违背了我们的贪心步骤 假如存在这样的C点 我们选取到的第一个点应该是C而不是B

  1. 对于已经选取过的点 我们可以把他看作为一个大的源点A 那么新考虑的点符合1的情况 归纳得证

下面说一下为什么可以把它们看成一个大的源点A
根据算法步骤2 我们对每一个选取过的节点的邻接点都尝试了松弛操作
那么意思就是不存在有大源点A向外部的点的连边丢失
如果大源点A内有多条边指向同一个外部节点X 其实最终也只会保留最短的边 因为被松弛替代了

负权失效

在选取节点非负性中 显然我们的算法无法知道有那么个节点
它会加上权值后比原来的更小 这是我们每次贪心最小考虑不到的

代码

朴素实现与路径

int n;	//节点数
vector<vector<int>> G;	//有向图中邻接矩阵储存点与点的权值	无边时为无穷大
vector<int> path(n);	//保存最短路中点的前导节点
vector<int> dijkstra()	//0号节点作为起点
{
    vector<int> dis(n,0x3f3f3f3f);	//初始化为无穷大
    vector<bool> visited(n,false);	//初始化访问标记
 	dis[0]=0;	//0号点自身
    for(int i=0;i<n-1;i++){	//处理全部点 最后的点不用处理
        int node=-1;	//哪个点当前与起点最近
        for(int j=0;j<n;j++)
            if(!visited[j]&&(node==-1||dis[node]>dis[j]))
                node=j;	//寻找未处理过的点中距离最短的点
        for(int j=0;j<n;j++)	//用node的点去更新点
            //dis[j]=min(dis[j],dis[node]+G[node][j]);
            if(dis[node]+G[node][j]<dis[j]){
                dis[j]=dis[node]+G[node][j];
                path[j]=node;
            }
        visited[node]=true;	//标记为已访问
    }
    return dis;
}
void get_path(int x,vector<int> &Path)
{
    if(!x){
        Path.push_back(0);
        return;
    }
    if(path[x]!=-1)get_path(path[x],Path);
    Path.push_back(x);
}

堆优化

原理

  • 利用最小堆把步骤1优化了 选取最小距离节点不用遍历全部了
  • 利用邻接表把步骤2优化了 可以直接选取到邻接点了

代码

const int n,m;	//n个节点 m条边
vector<int> edge(m+5,0);	//边
vector<int> weight(m+5,0);	//权重
vector<int> Next(m+5,0);	//下一条兄弟边
vector<int> head(n+5,-1);	//节点的链表
vector<int> dijkstra()	//0号节点作为起点
{
    vector<int> dis(n,0x3f3f3f3f);	//初始化为无穷大
    vector<bool> visited(n,false);	//初始化访问标记
    dis[0]=0;
    priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> heap;
    heap.push({0,0});	//first是距离 second是节点编号
    while(heap.size())
    {
        int node=heap.top().second;	//取出堆顶的点
        heap.pop();
        if(visited[node])continue;	//如果已经更新过就不用了
        for(int i=head[node];~i;i=Next[i]){
            int now_node=edge[i],w=weight[i];
            if(dis[now_node]>dis[node]+w){	//可以更新
                dis[now_node]=dis[node]+w;
                heap.push({dis[now_node],now_node});
            }
        }
        visited[node]=true;
    }
    return dis;
}

你可能感兴趣的:(算法)