Dijkstra算法基础详解,附有练习题

介绍

Dijkstra算法是一种用于求解单源最短路径问题的经典算法。它可以在给定有向加权图(无向是特殊的有向)中,从一个起点到其他所有节点的最短路径。

作用:

在给定的加权图中,求一个点到其余点的最短路径

基本思想:

  • 初始化一个距离数组dist[],表示起点到各个节点的当前最短距离,初始时起点的最短距离为0,其余节点的最短距离为无穷大。
  • 创建一个集合S,用于存放已经确定最短路径的节点,初始时集合S为空。
  • 重复以下步骤,直到集合S包含了所有节点:
    • 在未确定最短路径的节点中,选择一个节点v,选择的是dist[v]值最小,将该节点加入集合S。
    • 更新节点v的所有邻接节点u的最短距离,如果通过节点v到达节点u的距离比当前最短路径更短,则更新最短距离dist[u]为新的最短距离。
    • 表示为: dist[u] = min(dist[u],dist[v]+edge[v][u])

Dijkstra算法的关键是如何选择节点v,这里使用了贪心策略,每次选择距离起点最近的未确定最短路径的节点,确保每次加入的节点都是当前最短距离确定的。

朴素模板如下:

int dijkstra(int b)//求b到各个点的距离
{
	memset(dist, 0x3f, sizeof dist);
	dist[b] = 0;

	for (int i = 0; i < n - 1; i++)
	{
		int t = -1;     // 在还未确定最短路的点中,寻找距离最小的点
		for (int j = 1; j <= n; j++)
			if (!st[j] && (t == -1 || dist[t] > dist[j]))
				t = j;

		//从t这个点更新到其他点的距离。
		//虽然是遍历了每个点,但并不代表t可以到任意点,
		//因为会把到不了的边的距离设为inf,就避免了考虑t点可以到哪些点的问题
		for (int j = 1; j <= n; j++)
			dist[j] = min(dist[j], dist[t] + v[t][j]);

		st[t] = true;
	}

	if (dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
}

堆优化版

可以看到,朴素版有一个遍历找dist最小的操作,如果用小根堆维护dist的话,就可以一下子找到最小了,无需遍历

插入优先队列的知识点

可以使用标准库中的priority_queue来实现小根堆和大根堆。priority_queue默认实现的是大根堆,如果需要实现小根堆,可以通过自定义比较器来改变默认行为。

priority_queue<int, vector<int>, greater<int>> minHeap;

int dijkstra(int b)//求b到各个点的距离
{
	memset(dist, 0x3f, sizeof dist);
	dist[b] = 0;

	
    priority_queue<node, vector<node>, cmp> q;
	q.push({ b,0 });
	
	while (q.size()) {
		node t = q.top();
		q.pop();
		int ver = t.v, distance = t.w;
		//此时dist[ver] == distance,是最小的
		if (st[ver]) continue;

		st[ver] = true;
		for (int i = 0; i < edge[ver].size();i++) {
			//更新ver可以到达的点
			if (dist[i] > edge[ver][i] + distance) {
				dist[i] = edge[ver][i] + distance;
				q.push({ i,dist[i] });
			}
		}

	}




	if (dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
}

邻接表写法

Dijkstra求最短路 II

const int N = 150010;
typedef pair<int,int> PII;

int h[N],w[N],ne[N],e[N],idx;
void add(int u,int v,int we){
    w[idx] = we,ne[idx] = h[u],e[idx] = v;
    h[u] = idx++;
}
int dist[N];bool st[N];
int n,m;
int dijkstra(int b){
    memset(dist,0x3f,sizeof dist);
    dist[b] = 0;
    
    //PII的第一个元素是点x到b点的距离,第二个元素是点x
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,b});
    while(heap.size()){
        auto t = heap.top();
        heap.pop();
        
        int ver = t.second,distance = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        //访问ver能到达的点,更新ver到这些点的距离
        for(int i = h[ver];i!=-1;i = ne[i]){
            //切记了,i只是编号!
            int j = e[i];//j才是ver能到达的点
            if(dist[j] > distance + w[i]){
                dist[j] = distance + w[i];
                heap.push({dist[j],j});
            }
            
        }
        
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    else return dist[n];
    
}
void solve() {
	cin>>n>>m;
	memset(h,-1,sizeof h);
	//memset(st,false,sizeof st);
	//idx = 1;
	while(m--){
	    int a,b,c;cin>>a>>b>>c;
	    add(a,b,c);
	}
	int t = dijkstra(1);
	cout<<t<<endl;
	
}

Dijkstra序列

输入样例:
5 7
1 2 2
1 5 1
2 3 1
2 4 1
2 5 2
3 5 1
3 4 1
4
5 1 3 4 2
5 3 1 2 4
2 3 4 5 1
3 2 1 5 4
输出样例:
Yes
Yes
Yes
No

分析:还是要先弄目标dijkstra序列的含义是什么,这个困扰我了很久。

我们知道,在构建dist数组时,每次是选 还没有选过当前dist最小 的点,在这个点的基础上进行更新。然后要选n次,我们这n次选的点的序列就称作dijkstra序列(第一个点就是起点)。

例如:5,1,3,4,2。代表点5是起点,然后选点1,点3,点4,点2。

设每次选的点为dist[i],那么dist[i]一定小于当前所有还没选过的点。以这个为判断条件,如果不满足,则不是一个dijkstra序列。

#include<assert.h>
#include<cstdio>
#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>

#define inf 0x3f3f3f3f
#define int long long
const int N =1010;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;

//long long MAX(long long a, long long b) { return a < b ? b : a; }

int n, m;


int v[N][N];//存每条边,v[i][j]代表i到j的距离。如果i到j没有直接的路径,那么设为inf
int dist[N];//存i号点到各个点的最短距离
int st[N];//存到达该点该点是否已经确定最短路径
int q[N];

int dijkstra() {//从b开始,到各个点的距离
	memset(dist, inf, sizeof(dist));
	memset(st, 0, sizeof(st));
	dist[q[0]] = 0;//到达自己的距离是0

	
	for (int i = 0; i < n; i++) {

		int cur = q[i];
		for (int j = 1; j <= n; j++) {
			if (!st[j] && dist[j]<dist[cur]) {
				return false;
			}
		}

		//从cur这个点更新到其他点的距离。
		//虽然是遍历了每个点,但并不代表cur可以到任意点,
		//因为会把到不了的边的距离设为inf,就避免了考虑cur点可以到哪些点的问题
		for (int j = 1; j <= n; j++) {
			dist[j] = min(dist[j], dist[cur] + v[cur][j]);
		}

		st[cur] = 1;
	}

	return 1;
}
signed main() {
	cin >> n >> m;
	memset(v, inf, sizeof(v));

	for (int i = 0; i < m; i++) {
		int a, b, d; cin >> a >> b >> d;

		//因为是无向图
		v[a][b] = d;
		v[b][a] = d;
	}
	int k; cin >> k;
	while (k--) {
		vector<int> t;
		t.push_back(0);
		for (int i = 0; i < n; i++) {
			cin >> q[i];
		}
		if (dijkstra()) cout << "Yes" << endl;
		else cout << "No" << endl;

	}

	return 0;
}

奶牛回家

输入样例:
5
A d 6
B d 3
C e 9
d Z 8
e Z 3
输出样例:
B 11

分析:简单题。命名为大写字母的牧场才有牛,可以逆向思考,从牛棚Z开始往这些牧场走,那条路最短。

易错点,两个牧场间可能有多条路,这些路长度可能不一样,注意判断。

#include<cstdio>
#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>

#define inf 0x3f3f3f3f
#define int long long
const int N =1010;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;

//long long MAX(long long a, long long b) { return a < b ? b : a; }

int n;

int v[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

int get(char x) {
	if (x >= 'A' && x <= 'Z') return x - 'A' + 1;
	return x - 'a' + 26 + 1;

}
void dijkstra() {
	memset(dist, inf, sizeof(dist));
	memset(st, 0, sizeof(st));
	dist[26] = 0;//Z到Z距离是0
	for (int i = 1; i <= 52; i++) {
		int cur = -1;
		for (int j = 1; j <= 52; j++) {
			if (!st[j] && (cur == -1 || dist[cur] > dist[j])) {
				cur = j;
			}
		}

		for (int j = 1; j <= 52; j++) {
			dist[j] = min(dist[j], dist[cur] + v[cur][j]);
		}
		st[cur] = 1;
	}

}
signed main() {
	cin >> n;
	memset(v, inf, sizeof(v));
	while (n--) {
		char a, b; int c;
		cin >> a >> b >> c;
		int d = get(a);
		int e = get(b);
		v[d][e] = v[e][d] = min(v[d][e], c);

	}
	dijkstra();
	int index = 0, len = inf;
	for (int i = 1; i <= 25; i++) {
		if (len > dist[i] && dist[i] <= 10000 * 1000) {
                           //如果大于这个距离就是没有到i的路,距离为inf
			len = dist[i];
			index = i;
		}
	}
	printf("%c %lld", index + 'A' - 1, len);

	return 0;
}

数据结构习题Dijkstra 1.3、将二叉树看作图,并对它作图的深度优先遍历,则与原二叉树的 结果是相同的。 A、前序遍历 B、中序遍历C、后序遍历D、层次序遍历 1.4、在关于树的以下4种叙述中,正确的是 。 A、用指针方式存储有n个结点的二叉树,至少要有n+1个指针。 B、m阶B-树中,具有k个子结点的结点,必有k-1个键值。 C、m阶B-树中,每个非叶子结点的子结点个数≥[m/2]。 D、平衡树一定是丰满树。 1.5、在最好和最坏情况下的时间复杂度均为O(nlog2n)且稳定的排序方法是 A、希尔排序 B、快速排序 C、堆排序 D、归并排序 二、解答题 2.1、对目标串 abaabcc和模式串aabc,描述根据KMP算法进行匹配的过程,包括失效函数计算。答:失效函数:-1, 0, -1, -1 目标串 abaabcc和模式串aabc的KMP算法进行匹配的过程 abaabcc aabc 首先,从目标位置0和模式位置0开始比较,至ab和aa,不等时目标位置为1, 模式位置为1。因0位置失效函数的值( f [posP-1] ) 为-1,从目标位 置1和模式位置0开始新一轮比较。因第一个字符( posP == 0 )就不等,目标 位置增1,目标位置2和模式位置0开始新一轮比较,直到模式位置比较至4, 匹配成功。 int fastFind ( char *s, char * pat ){ //带失效函数的KMP匹配算法 int posP = 0, posT = 0; int lengthP = strlen(pat), lengthT = strlen(s); while ( posP < lengthP && posT < lengthT ) if ( pat[posP] == s[posT] ) { posP++; posT++; //相等继续比较 } else if ( posP == 0 ) posT++; //不相等 else posP = f [posP-1]+1; if ( posP < lengthP ) return -1; else return posT - lengthP; }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值