最短路径算法之Djikstra算法 优先队列 邻接表优化 C++

最短路径算法之Djikstra算法

    • 原理及思路
      • 基础版
      • 优化版
    • 实现
      • 测试数据
      • 基础无优化版
        • 输出
      • 使用优先队列优化版
        • 输出
    • 总结

原理及思路

基础版

  • 利用广度优先搜索BFS, 遍历整个图
  • 使用两个集合, vis 负责顶点访问标志, dis 负责实时更新顶点
  • 开始时 dis 只有源顶点, 且源顶点到每个顶点的距离为无穷大(infinity), 接着把所有与目前顶点连通的顶点, 纳入到dis中, 更新到新顶点的最短距离, 更新 vis 访问标志
  • 重复第二个动作, 直到所有顶点纳入到 dis 中

优化版

  1. 背景: 基础版由于双层循环, 时间复杂度达到O(n^2), 当 n 过大时, 时间会爆掉
  2. 原理和思路:
    • 利用vector创建邻接表, 替换二维数组, 节省空间
    • 把最短路径排序更新, 使用C++自带STL——priority_queue 优先队列, 来保存新加入顶点的权值, 完成自动排序, 减少运行时间

实现

测试数据

4 5
1 2 3
1 3 5
1 4 7
2 4 2
3 4 1

基础无优化版

#include
#include
using namespace std;

const int INF = 10000;
int edge[50][50];
int dis[50];//d表示源节点到该节点的最小距离
bool vis[50];//v标记访问过的节点
int n, m;//n代表点数 m代表边数
int main()
{
	freopen("in.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    int min;
    int x, y, d;
    memset(edge, INF, sizeof(edge)); //使用一个值初始化无限大 
    memset(dis, INF, sizeof(dis));
    memset(vis, false, sizeof(vis));
    for(int i = 0; i < m;i++) {
        scanf("%d%d%d", &x, &y, &d);
        edge[x][y]=d;
        edge[y][x]=d;
    }
    dis[1]=0; //使用1作为源顶点 距离置0 
    //类似冒泡 
    for(int i = 1, k; i <= n; i++) {
    	min = INF; 
        for(int j = 1; j <= n; j++) {
        	if(!vis[j] && dis[j] < min) {
                min = dis[j];
                k = j;
            }
		}
		vis[k] = true;
		//松弛操作调整 
        for(int j = 1; j <= n; j++) {
        	if(!vis[j] && edge[k][j] != INF && dis[j] > dis[k] + edge[k][j]) {
        		dis[j] = dis[k] + edge[k][j];
			}               
		}
    }
    //最终输出从源节点到其他每个节点的最小距离
    for(int i = 1; i <= n; i++)
        printf("%d->%d: %d\n", 1, i, dis[i]); 
    return 0;
}

输出

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

使用优先队列优化版

#include
#include
#include
#include
using namespace std;

//定义邻接表 
struct edge {
	int v, d; //顶点 权值 
	edge(int v_, int d_): //初始化 
		v(v_), d(d_) { }
};
//优先队列生成最短路径结构体 
struct queue_element {
    int v;
    int d;
    queue_element(int v_, int dis_value_):
        v(v_), d(dis_value_) { }
    bool operator <(const queue_element &other) const {
        return d > other.d;	//重载操作符, 距离大的优先级靠后 
    }
};
int dis[50];
bool vis[50];
vector<edge> edges[50];
int n, m;

void Djikstra(int v)  {
	memset(dis, 0x3f, sizeof(dis));	//各类初始化 
    memset(vis, false, sizeof(vis));
    
    priority_queue<queue_element> q;
    q.push(queue_element(v, 0));//首先把源顶点压入优先队列 
	 
    while (!q.empty()) {
    	queue_element t = q.top();
    	q.pop();
    	if (vis[t.v]) {	 
    		continue;	//如果已访问过则扔掉 继续下一个
		}
		
		vis[t.v] = true;//更新访问标志 
		dis[t.v] = t.d; //保存路径 
		
		//松弛操作 更新最短路径 
		vector<edge>::iterator it;
		for (it = edges[t.v].begin(); it != edges[t.v].end(); ++it) {
	        q.push(queue_element(it->v, t.d + it->d)); //压入顶点距离 自动排序 
	    }
	}
	
	//最终输出从源节点到其他每个节点的最小距离
    for(int i = 1; i <= n; i++){
    	if (i != v) {
    		printf("%d->%d:%d\n",v, i, dis[i]);
		}
	}     
	cout << endl;
}

int main()
{
	freopen("in.txt", "r", stdin) ;
    scanf("%d%d",&n,&m);
    int x, v, d;
    //建立邻接表 
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d",&x,&v,&d);
        edges[x].push_back(edge(v, d));
        edges[v].push_back(edge(x, d));
    }
    //每个节点都当做一次源节点 
	for (int i = 1; i <= n; i++) {
		Djikstra(i);
	}
	
    return 0;
}

输出

1->2:3
1->3:5
1->4:5

2->1:3
2->3:3
2->4:2

3->1:5
3->2:3
3->4:1

4->1:5
4->2:2
4->3:1

总结

迪杰斯特拉在求最短路径时, 经常使用到, 但是比赛时会害怕爆时间, 只能来学这看不懂 装逼 的算法, 我一个比赛转C++小白, 直接从一个优化过的算法, 学到了vector创建邻接表, 操作符重载, 优先队列, 迭代器, 结构体重载初始化. 脑子废了

不懂或者有错误请及时提问哦~
共勉

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