Dijstkra算法

问题描述

  在一幅有向图中有 N N N个顶点和 M M M条连接任意两点的边,每条边都是有向带权的且权值非负。求从一点出发到各点的最短路径长度及最短路径。

输入格式

  第一行依次给出 N N N(顶点的个数)、 M M M(有向边的个数)和 S S S(起始点)。接下来 M M M行依次给出各个有向边的起点编号、终点编号和权重。顶点编号从 0 0 0 N − 1 N-1 N1

输出格式

  第一行给出 N N N个整数,按照各个顶点的编号依次给出从起始点到该点的最短路径的长度,中间以空格分隔,末尾没有多余空格。接下来 N N N行按照各个顶点的编号依次给出从起始点到该点的最短路径,路径中的各点以“->”作为分隔。

输入样例

6 8 0
0 1 1
0 3 4
0 4 4
1 3 2
2 5 1
3 2 2
3 4 3
4 5 3

输出样例

0 1 5 3 4 6
0
0->1
0->1->3->2
0->1->3
0->4
0->1->3->2->5

解题思路

  若从 A A A C C C的最短路径中要经过 B B B点,那么从 A A A B B B的这段路径就是从 A A A B B B的最短路径,这也就满足了最优子结构的性质。我们使用一个长度数组来存储起始点到各点的距离。每次从其中选取为访问过的、距离起始点最近的点作为一个跳板,通过此跳板查看是否可以使起始点到达其它点的距离更近,然后更新长度数组。每次使用完跳板后都对其做标记,直至所有可到达的点都被做上标记。

测试代码

无路径记录

#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int main(){
	struct node{///定义邻接表中的节点
	    size_t terminus;
	    size_t length;
	};
    size_t number,road,start;
    cin >> number >> road >> start;
    vector<vector<node> > adjacency(number);///建立邻接表
    for(size_t i=0;i<road;i++){
        size_t left,right,length;
        cin >> left >> right >> length;
        adjacency[left].push_back({right,length});///向邻接表中放入有向边
    }
    vector<bool> mark(number,false);///标记已经访问过的点
    vector<size_t> table(number,UINT_MAX);///记录出发点到各点的距离初始化为无穷大
    table[start] = 0;///出发点到出发点距离为零
    size_t next = start;///初始化下一次访问的点为出发点
    size_t length = 0;///初始化到下一次访问的点的距离为零
    do{
        for(node temp:adjacency[next]){///枚举当前访问点有向连接的各点
            table[temp.terminus] = min(length+temp.length,table[temp.terminus]);///通过该点到其它点距离是否可以更近
        }
        mark[next] = true;///标记该点已经访问过
        length = UINT_MAX;///初始化到下一次访问点的距离为无穷大
        for(size_t i=0;i<number;i++){
            if(!mark[i]&&table[i]<length){///选取未访问过的、到该点最小距离的点
                length = table[i];///记录到该点的距离
                next = i;///记录该点的标号
            }
        }
    }while(length<UINT_MAX);///已经访问过该点所在连通图的所有点
    cout << table[0];
    for(size_t i=1;i<table.size();i++){
        cout << ' ' << table[i];
    }
    cout << endl;
    return 0;
}

有路径记录

#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
#include <climits>
using namespace std;
int main(){
    struct node{///定义邻接表中的节点
        size_t terminus;
        size_t length;
    };
    size_t number,road,start;
    cin >> number >> road >> start;
    vector<vector<node> > adjacency(number);///建立邻接表
    for(size_t i=0;i<road;i++){
        size_t left,right,length;
        cin >> left >> right >> length;
        adjacency[left].push_back({right,length});///向邻接表中放入有向边
    }
    vector<bool> mark(number,false);///标记已经访问过的点
    vector<size_t> table(number,UINT_MAX);///记录出发点到各点的距离初始化为无穷大
    table[start] = 0;///出发点到出发点距离为零
    vector<size_t> last(number);///记录达到某点的上一点
    for(size_t i=0;i<last.size();i++){
        last[i] = i;///初始化各点的上一点都是自己
    }
    size_t next = start;///初始化下一次访问的点为出发点
    size_t length = 0;///初始化到下一次访问的点的距离为零
    do{
        for(node temp:adjacency[next]){///枚举next点有向连接的temp
            if(length+temp.length<table[temp.terminus]){///通过next点到temp.id点是否可以更近
                table[temp.terminus] = length+temp.length;///更新到temp.id点的距离
                last[temp.terminus] = next;///记录是通过next点到达temp.id的
            }
        }
        mark[next] = true;///标记该点已经访问过
        length = UINT_MAX;///初始化到下一次访问点的距离为无穷大
        for(size_t i=0;i<number;i++){
            if(!mark[i]&&table[i]<length){///选取未访问过的、到该点最小距离的点
                length = table[i];///记录到该点的距离
                next = i;///记录该点的标号
            }
        }
    }while(length<UINT_MAX);///已经访问过该点所在连通图的所有点
    cout << table[0];
    for(size_t i=1;i<table.size();i++){
        cout << ' ' << table[i];
    }
    cout << endl;
    stack<size_t> path;///建立栈用于记录路径
    for(size_t i=0;i<number;i++){
        path.push(i);
        while(path.top()!=last[path.top()]){///起始点的上一点一定是自己,找到起始点则终止
            path.push(last[path.top()]);///通过各点的上一点找到整条路经
        }
        while(path.size()>1){
            cout << path.top() << "->";
            path.pop();
        }
        cout << path.top() << endl;
        path.pop();
    }
    return 0;
}

使用优先队列优化

#include <iostream>
#include <vector>
#include <climits>
#include <queue>
using namespace std;
int main(){
	struct node{///定义邻接表中的节点
	    size_t terminus;
	    size_t length;
	};
    size_t number,road,start;
    cin >> number >> road >> start;
    vector<vector<node> > adjacency(number);///建立邻接表
    for(size_t i=0;i<road;i++){
        size_t left,right,length;
        cin >> left >> right >> length;
        adjacency[left].push_back({right,length});///向邻接表中放入有向边
    }
    vector<bool> mark(number,false);///标记已经访问过的点
    vector<size_t> table(number,UINT_MAX);///记录出发点到各点的距离初始化为无穷大
    table[start] = 0;///出发点到出发点距离为零
    struct point{
        size_t id;
        size_t length;
        bool operator<(const point &another)const{
            return length > another.length;///构建小顶堆
        }
    };
    priority_queue<point> group;///使用优先队列来产生下次要访问的点
    group.push({start,table[start]});
    while(!group.empty()){
        point next = group.top();
        group.pop();
        if(mark[next.id]){///选取未访问过的、距离起始点距离最小的点
            continue;
        }
        for(node temp:adjacency[next.id]){///枚举当前访问点有向连接的各点
            if(next.length+temp.length<table[temp.terminus]){
                table[temp.terminus] = next.length+temp.length;
                group.push({temp.terminus,table[temp.terminus]});///向队列中放入更新后的距离
            }
        }
        mark[next.id] = true;///标记该点已经访问过
    }
    cout << table[0];
    for(size_t i=1;i<table.size();i++){
        cout << ' ' << table[i];
    }
    cout << endl;
    return 0;
}
/** \brief
 *
 * \param const vector<vector<pair<int, int> > > &adj /// 邻接表,adj[sta][].first为点sta到点adj[sta][].second的距离
 * \param const int sta /// 起始点
 * \param vector<int> &tab /// tab[tem] 为sta到tem的距离
 * \return void
 *
 */
void dijkstra(const vector<vector<pair<int, int> > > &adj, const int sta, vector<int> &tab){
    tab.resize(adj.size(), INT_MAX); /// 初始化
    tab[sta] = 0;
    priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > que; /// 小顶堆
    que.push({tab[sta], sta});
    vector<bool> mar(adj.size(), false); /// 标记某点已访问
    size_t cou = 0; /// 已访问点计数
    while(!que.empty()){
        pair<int, int> mid = que.top(); /// 获取未访问点中距离最小的点
        que.pop();
        if(mar[mid.second]) continue;
        for(pair<int, int> edg: adj[mid.second]){ /// 尝试松弛所有与该点连接的点
            int len = mid.first + edg.first;
            if(len < tab[edg.second]){
                que.push({len, edg.second}); /// 将松弛成功的点放入堆中
                tab[edg.second] = len;
            }
        }
        mar[mid.second] = true;
        if(++cou == adj.size()) break;
    }
}
/** \brief
 *
 * \param const vector<vector<pair<int, int> > > &adj /// 邻接表,adj[sta][].first为点sta到点adj[sta][].second的距离
 * \param const int sta /// 起始点
 * \param vector<pair<int, vector<int> > > &tab /// tab[tem].first 为sta到tem的距离,tab[tem].second 为到达tem的路径中tem的上一个点(可能有多条路径,故该参数为一个点集)
 * \return void
 *
 */
void dijkstra(const vector<vector<pair<int, int> > > &adj, const int sta, vector<pair<int, vector<int> > > &tab){
    tab.resize(adj.size(), {INT_MAX, {}}); /// 初始化
    tab[sta].first = 0;
    priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > que; /// 小顶堆
    que.push({tab[sta].first, sta});
    vector<bool> mar(adj.size(), false); /// 标记某点已访问
    size_t cou = 0; /// 已访问点计数
    while(!que.empty()){
        pair<int, int> mid = que.top(); /// 获取未访问点中距离最小的点
        que.pop();
        if(mar[mid.second]) continue;
        for(pair<int, int> edg: adj[mid.second]){ /// 尝试松弛所有与该点连接的点
            int len = mid.first + edg.first;
            if(len < tab[edg.second].first){
                que.push({len, edg.second}); /// 将松弛成功的点放入堆中
                tab[edg.second].first = len;
                tab[edg.second].second.clear();
                tab[edg.second].second.push_back(mid.second);
            }else if(len == tab[edg.second].first){
                tab[edg.second].second.push_back(mid.second);
            }
        }
        mar[mid.second] = true;
        if(++cou == adj.size()) break;
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值