前言
- 最近遇到求最短距离的题次数有点多,每次写都写不对,故总结一下,仅供参考。前言主要是对于自己遇到的疑问进行解释,希望有相同疑问的同学也能得到解答。
- 对于dijkstra运用哪一种数据结构(邻接矩阵 OR 邻接表)。应考虑节点数量大小,网上查找资料说到不超过100用邻接表。
- 对于大顶堆排序还是纯数组操作问题。大顶堆是优化后的方案。(具体可参考复杂度总结)
- 核心思想:贪心思想
- 如何记忆:链接: 看B站视频加深理解把~.
复杂度总结
邻接矩阵 O(n^2)
邻接表 O(n^2)
邻接表+binary heap O((n+m)logn)
邻接表+fibonacci heap O(m+nlogn)
邻阶矩阵 (适合稠密图,疏密图中空间浪费大,算法复杂度为O(n2))
leetcode743原题
要点注意
- 在找【未找到最短距离的目标点的最小值】的两种写法(一个只定义x ,另一个定义最小值min = inf(无穷大))
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
/***************dijkstra************************************************************/
/**邻接矩阵**/
//定义默认值
const int inf = 0x3f3f3f3f;
//创建邻阶矩阵vector容器嵌套初始化
vector<vector<int>> G(n,vector(n,inf));
//对已经初始化的邻接矩阵 对times 中的元素进行复制,这样可以避免times 数据被破坏
for(auto &t:times) {
int x = t[0] - 1;//标记点从零开始
int y = t[1] - 1;
G[x][y] = t[2];
}
/****最短距离数组与布尔数组****/
//创建 距离表dis
vector<int> dis(n,inf);
//自己与自己的距离为0
dis[k-1] = 0;
//创建 布尔表用于判断是否已经找到最短距离
vector<bool> isF(n,false);
//for(嵌套两个for)一个在还未找到最短距离的点中找最短,另一个for更新dis
/***写法1******/
for(int i = 0;i < n;i++) {
int x = -1;
for(int y = 0;y < n;y++) {
if(!isF[y]&&(x == -1||dis[y]<dis[x])){
x = y; //起始 x == -1 说明先认为【未找到最短距离的点的第一个为最短】,之后发现又比他还短的作为最短。
}
}
/************************************************************/
/****上面写法1的另一种写法*****/
/*与写法1的不同在于 该写法定义一个最小值进行if判断,对dis数组赋值,更新方式也不同
for(int i = 0;i < n;i++) {
int min = inf;
for(int y = 0;y < n;y++) {
if(!isF[y]&&(dis[y]<min){
min = dis[y];
x = y; //找到【未找到最短距离的目标点的最小值】
}
}
*/
/************************************************************/
//for循环结束后,当前x记录的目标点即为起始点到目标点的最短距离,将对应位置true
isF[x] = true;
//用for循环去更新最短距离
for(int y = 0 ; y < n;y++) {
dis[y] = min(dis[y],G[x][y]+dis[x]);//dis【y】:源点 到y点的(未证实)最短距离
}
}
/***************dijkstra************************************************************/
//题目就是要找最短中的最远的距离
int ans = *max_element(dis.begin(), dis.end());
return ans == inf ? -1 : ans;
}
};
邻接矩阵(优先级队列——又称堆排序)
- 这里开始有点没理解~明天来理解一下
if (!isFindShortest[aimPoint] && distanceS[aimPoint] > distanceS[NowStPoint] + cost) {
解:就是在还未找到源点到【目标点=aimPoint】的最短距离点集里面逐个判断更新dis【】数组。
//迪杰拉特斯算法
void djlts() {
//初始化起始村庄点到起始村庄点的消费为零
distanceS[startC] = 0;
//创建优先级队列适配vector容器
priority_queue<P, vector<P>, tmp2> pque;
pque.push(P(startC, 0));
while (!pque.empty()) {
P p = pque.top();
pque.pop();
//初始化起始村庄点
int NowStPoint = p.first;
if (isFindShortest[NowStPoint]) continue;//若已经找到最少消费 进入下一个循环
//当前节点虽然找到但未被使用,但要利用到这个节点的最小消费值, 所以在这里赋值为true
isFindShortest[NowStPoint] = true;
for (int i = 0; i < G[NowStPoint].size(); i++) {
int aimPoint = G[NowStPoint][i].first;
int cost = G[NowStPoint][i].second;
/****这下面有点没理解 mark一下!!!**/
//找到未被确定最小的消费中的最小值
if (!isFindShortest[aimPoint] && distanceS[aimPoint] > distanceS[NowStPoint] + cost) {
distanceS[aimPoint] = distanceS[NowStPoint] + cost;
//将所有的都插入到优先级队列,由
pque.push(P(aimPoint, distanceS[aimPoint]));
}
}
}
}
原文链接:https://blog.csdn.net/yyjshang/article/details/121076937
邻接表版(适合非稠密图)
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
#define maxn 110 //最大顶点个数
int n; //顶点个数
struct arcnode //边结点
{
int vertex; //与表头结点相邻的顶点编号
int weight; //连接两顶点的边的权值
arcnode * next; //指向下一相邻接点
arcnode() {}
arcnode(int v,int w):vertex(v),weight(w),next(NULL) {}
};
struct vernode //顶点结点,为每一条邻接表的表头结点
{
int vex; //当前定点编号
arcnode * firarc; //与该顶点相连的第一个顶点组成的边
}Ver[maxn];
void Init() //建立图的邻接表需要先初始化,建立顶点结点
{
for(int i = 1; i <= n; i++)
{
Ver[i].vex = i;
Ver[i].firarc = NULL;
}
}
void Insert(int a, int b, int w) //尾插法,插入以a为起点,b为终点,权为w的边,效率不如头插,但是可以去重边
{
arcnode * q = new arcnode(b, w);
if(Ver[a].firarc == NULL)
Ver[a].firarc = q;
else
{
arcnode * p = Ver[a].firarc;
if(p->vertex == b)
{
if(p->weight > w)
p->weight = w;
return ;
}
while(p->next != NULL)
{
if(p->next->vertex == b)
{
if(p->next->weight > w);
p->next->weight = w;
return ;
}
p = p->next;
}
p->next = q;
}
}
void Insert2(int a, int b, int w) //头插法,效率更高,但不能去重边
{
arcnode * q = new arcnode(b, w);
if(Ver[a].firarc == NULL)
Ver[a].firarc = q;
else
{
arcnode * p = Ver[a].firarc;
q->next = p;
Ver[a].firarc = q;
}
}
struct node //顶点节点,保存id和到源顶点的估算距离,优先队列需要的类型
{
int id; //源顶点id和估算距离
int w;//权值
friend bool operator<(node a, node b) //因要实现最小堆,按升序排列,因而需要重载运算符,重定义优先级,以小为先
{
return a.w > b.w;
}
};
#define INF 0xfffff //权值上限
int parent[maxn]; //每个顶点的父亲节点,可以用于还原最短路径树
bool visited[maxn]; //用于判断顶点是否已经在最短路径树中,或者说是否已找到最短路径
node d[maxn]; //源点到每个顶点估算距离,最后结果为源点到所有顶点的最短路。
priority_queue<node> q; //优先队列stl实现
/***************Dijkstra**************************/
void Dijkstra(int s) //Dijkstra算法,传入源顶点
{
for(int i = 1; i <= n; i++) //初始化
{
d[i].id = i;
d[i].w = INF; //估算距离置INF
parent[i] = -1; //每个顶点都无父亲节点
visited[i] = false; //都未找到最短路
}
d[s].w = 0; //源点到源点最短路权值为0
q.push(d[s]); //压入队列中
while(!q.empty()) //算法的核心,队列空说明完成了操作
{
node cd = q.top(); //取最小估算距离顶点
q.pop();
int u = cd.id;
if(visited[u]) //注意这一句的深意,避免很多不必要的操作
continue;
visited[u] = true;
arcnode * p = Ver[u].firarc;
//松弛操作
while(p != NULL) //找所有与他相邻的顶点,进行松弛操作,更新估算距离,压入队列。
{
int v = p->vertex;
if(!visited[v] && d[v].w > d[u].w+p->weight)
{
d[v].w = d[u].w+p->weight;
parent[v] = u;
q.push(d[v]);
}
p = p->next;
}
}
}
/***************Dijkstra**************************/
int main()
{
int m, a, b, c, st, ed;
printf("请输入顶点数和边数:\n");
scanf("%d%d", &n, &m);
printf("请输入边以及权值(a, b, c)\n");
Init(); //计算前必须初始化
while(m--)
{
scanf("%d%d%d", &a, &b, &c);
Insert2(a, b, c); //无向图注意存储两条边
Insert2(b, a, c);
}
printf("请输入起点和终点:\n");
scanf("%d%d", &st, &ed);
Dijkstra(st);
if(d[ed].w != INF)
printf("最短路径权值为:%d\n", d[ed].w);
else
printf("不存在从顶点%d到顶点%d的最短路径。\n", st, ed);
return 0;
}