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; }
<think>好的,我现在需要帮助用户寻找关于Dijkstra算法练习题和教程。首先,我得回想一下用户提供的引用内容,看看里面有没有相关的信息。引用[1]提到了Dijkstra算法的基本思想、朴素版和堆优化版的实现步骤,以及两个例题Dijkstra求最短路I和II,这可能就是用户需要的练习题。引用[2]则是一个基础详解附有练习题,这应该是教程类的内容。引用[3]讨论了堆优化的时间复杂度和使用优先队列的方法,这可能对理解算法优化有帮助。引用[4]和[5]虽然提到了最短路和匈牙利算法,但可能与用户当前需求关联不大。 接下来,我需要整理这些信息,明确用户的需求。用户想要练习题和教程,所以应该优先提取引用中提到的例题详解部分。引用[1]中的两个例题可以作为练习题推荐,同时引用[2]的基础详解可以作为教程资源。另外,引用[3]中的堆优化内容可以作为进阶教程的一部分。需要确保推荐的练习题有具体的实现思路和代码示例,这样用户才能有效练习。 然后,考虑如何组织回答结构。按照用户的要求,回答需要结构清晰,分步骤解决问题。可以先介绍Dijkstra算法的基本概念,然后提供教程资源,接着推荐练习题,并给出每个练习题的实现思路或代码示例。同时,注意引用标识的正确添加,比如在提到例题时引用[^1],在基础教程时引用[^2],堆优化部分引用[^3]。 还需要检查是否有遗漏的信息。比如引用[4]中的分析虽然是指另一道题,但可以转化为Dijkstra练习题,例如求最远距离的最短路径,这可能是一个变种题目,可以加入练习题列表。此外,用户可能需要不同难度级别的题目,所以需要包括基础题和优化题。 最后,确保回答符合格式要求,使用中文,正确应用LaTeX公式(如时间复杂度$O(n^2)$和$O(m \log n)$),代码块使用正确语法,并自然添加引用标识。同时,生成相关问题,帮助用户进一步学习,比如询问时间复杂度的比较或负权边处理的问题。</think>### Dijkstra算法练习题与教程推荐 #### 一、基础概念与教程 Dijkstra算法用于求解**带非负权边的单源最短路径**问题[^2]。其核心思想是逐步扩展**已知最短路径的顶点集合**,通过贪心策略选择当前距离起点最近的顶点。时间复杂度根据实现方式不同分为: - **朴素版**:$O(n^2)$,适合稠密图 - **堆优化版**:$O(m \log n)$,适合稀疏图[^3] **推荐教程**: 1. [引用1]中的“搜索与图论-Dijkstra算法”详细讲解了算法思想、实现步骤及代码注解,并包含两个例题Dijkstra求最短路I和II)的完整分析。 2. [引用2]提供基础详解,包含练习题和实际应用场景。 --- #### 二、经典练习题与实现思路 ##### 1. 例题1:Dijkstra求最短路I(朴素版) **题目描述**:给定稠密图(邻接矩阵),求起点到终点的最短距离。 ```python # 伪代码示例(邻接矩阵) 初始化距离数组dist为无穷大,dist[起点] = 0 for i in 0..n-1: 找到未访问的dist最小的顶点u 标记u为已访问 遍历所有顶点v,若dist[v] > dist[u] + g[u][v],则更新dist[v] ``` **实现要点**:适合节点数较少(如$n \leq 1000$)的情况,直接遍历找最小值。 --- ##### 2. 例题2:Dijkstra求最短路II(堆优化版) **题目描述**:给定稀疏图(邻接表),求起点到终点的最短距离。 ```python # 伪代码示例(优先队列) 使用优先队列存储节点距离对(dist[u], u) 初始化dist[起点] = 0,其余为无穷大 将起点加入队列 while 队列非空: 取出队列中dist最小的节点u if u已访问:continue 标记u为已访问 遍历u的所有邻接边u→v,权重w: if dist[v] > dist[u] + w: dist[v] = dist[u] + w 将(dist[v], v)加入队列 ``` **实现要点**:使用优先队列(如Python的`heapq`)优化查找最小值的效率。 --- ##### 3. 变种题:指挥部到最远哨所的距离 **题目描述**:给定无向图,求起点到所有节点的最短距离中的最大值[^4]。 ```python # 核心代码逻辑 dijkstra() # 计算所有节点的最短距离 max_dist = max(dist[1..n]) print(max_dist) ``` --- #### 三、推荐练习平台 1. **LeetCode**:题目743(网络延迟时间)、1514(概率最大路径) 2. **AcWing**:题库中包含多道Dijkstra模板题及变种(如[引用1]中的例题) 3. **洛谷**:P4779(单源最短路径标准题) ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值