最短路径(dijstra算法,链式前向星,堆优化)

【模板】单源最短路径(弱化版)

对于这题我们使用邻接矩阵的话会导致弓箭复杂度会大大提升,所以我们就需要学习一种新的数据结构,名叫链式前向星,在链式前向星中,我们需要定义一个结构体数组,其中有成员to,w,next;

struct EGDE
{
	int to;//终点,仅仅是指两个点之间
	int w;//权值,路径长度
	int next;//指向前一个点
}edge[maxn];
int first[maxn];

接下来构造存放边的函数,姑且叫做cinn函数

请大家将first数组想象成一个一个鞭子的把,next想象成鞭子的每一节,那么这个数组就可以按照鞭子的样子将所有边的信息存起来了。其实这个就是邻接表的升级版

void cinn(int u,int w,int v)//w代表着权值,u代表着first数组的下标,v代表着终点
{
	cnt++;//代表着下标,这个下标指的是边的下标
	edge[cnt].to=v;
	edge[cnt].w=w;
	edge[cnt].next=first[u];
	first[u]=cnt;	
}

接下来就是在主函数的输入的问题

	cin>>n>>m;//n代表着节点数,m代表着边数
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;//u代表着fisrt数组的下标,其中存放的值就是相当于这个鞭子的把,同时也是代表着起点
		cinn(u,v,w);//v代表着终点,w代表着权值
		cinn(v,u,w);//如果是有向边则只需要一个这样的函数,如果是无向边那么就需要写两个这样的函数
	}

如何遍历这个鞭子呢,我们设计一个vis函数用于遍历,优势链式储存所以就需要将把拿到手,那么这个把就是出差呢在first数组里面,我们只需要将我们需要查找的链子的下标输进去即可

void vis(u)
{
	for(int i=first[u];i!=0;i=edge[i].next)//i的变化里面的i就是相当于first[u]
	{
		printf("%d--%d:%d\n",u,edge[i].to,edge[i].w);
	}
}

前向链式星结构的最终代码

#include<iostream>
#define N 10000
using namespace std;
int cnt = 0;
struct EGDE
{
	int to;//终点,仅仅是指两个点之间
	int w;//权值,路径长度
	int next;//指向前一个点
}edge[N];
int first[N];
int n, m;
void cinn(int u, int v, int w)//w代表着权值,u代表着first数组的下标,v代表着终点
{
	cnt++;//代表着下标,这个下标指的是边的下标
	edge[cnt].to = v;
	edge[cnt].w = w;
	edge[cnt].next = first[u];
	first[u] = cnt;
}
void vis(int u)
{
	for (int i = first[u]; i != 0; i = edge[i].next)//i的变化里面的i就是相当于first[u]
	{
		printf("%d--%d:%d\n", u, edge[i].to, edge[i].w);
	}
}
int main()
{
	cin >> n >> m;//n代表着节点数,m代表着边数
	int s;
	cin>>s;
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		cin >> u >> v >> w;//u代表着fisrt数组的下标,其中存放的值就是相当于这个鞭子的把,同时也是代表着起点
		cinn(u, v, w);//v代表着终点,w代表着权值
	}
	vis(s);
	return 0;
}

最后我们按照dijkstra算法的思路走一遍代码就可以出来了

#include<iostream>
#define N 1000000
using namespace std;
int cnt = 0;
long long ans[N];
bool book[N];
int first[100000];
int n,m,s;
struct EGDE
{
	int to;//终点,仅仅是指两个点之间
	int w;//权值,路径长度
	int next;//指向前一个点
}edge[N];
void cinm(int u, int v, int w)//w代表着权值,u代表着first数组的下标,v代表着终点
{
	//代表着下标,这个下标指的是边的下标
	edge[++cnt].to = v;
	edge[cnt].w = w;
	edge[cnt].next = first[u];
	first[u] = cnt;
}
int main()
{
	cin >> n >> m>>s;//n代表着节点数,m代表着边数
	for(int i=1;i<=m;i++)//将每一条边都设置为是最大值
	{
		ans[i]=2147483647;
	}
	ans[s]=0;//由于是从s开始所以要将着一条边设置为0方便结束
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		cin >> u >> v >> w;//u代表着fisrt数组的下标,其中存放的值就是相当于这个鞭子的把,同时也是代表着起点
		cinm(u, v, w);//v代表着终点,w代表着权值
	}
	int tmp=s;
	while(book[tmp]==0)
	{
		long long minn=2147483647;
		book[tmp]=1;//将已经收录过的边进行标记
		//Dijkstra算法的核心部分
		for(int i=first[tmp];i!=0;i=edge[i].next)//这个就相当于遍历已经理解起来的点
		{
			if(!book[edge[i].to]&&ans[edge[i].to]>ans[tmp]+edge[i].w)//如果没有被标记且直接的两点距离大于通过某点的距离
			{
				ans[edge[i].to]=ans[tmp]+edge[i].w;//ans数组存放的是s点到ans下标编号的这个点的最短距离,其实这里优点像动态规划
			}
		}
		for(int i=1;i<=n;i++)//重新遍历全部点,找到没有被标记且最小的边
		{
			if(book[i]==0&&ans[i]<minn)
			{
				minn=ans[i];//这个是为找到目前情况的最小值(每次寻找都有可能找到不同的最小边)
				tmp=i;//这个算法下一次就从这个最小边开始(由于这个是最小便已经确定,所以可以依赖这个节点进行下移的寻找)
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		cout<<ans[i]<<" ";
	}
	return 0;
}

dijkstra算法的核心两步:第一找出里目的地的最近的点(没有被收录的点之中),并将该点收录,下一次循环就从这个点开始。

第二判断这个点的距离是比从前面那个点加上前面那个点到这个点的距离谁大谁小,将小的距离重新赋给该值(动态规划的思路)。

【模板】单源最短路径(标准版)

这一题还得是用堆优化

1.这里要用到一个优先队列(stl),其本质就是用一个数组模拟的一个完全二叉树。
2.功能:拿出优先级最大的元素,这个优先级可以自己定义。
3.这个包括在头文件#include<queue>之中。
4.定义方式:priority_queue<int> que   尖括号说明里面存放的数是整型(这样定义就是大顶堆 值越大优先级越高)
5.关于优先队列的几种操作:1.que.size()  得到这个队列的元素数量
                                              2.que.push(x)  插入
                                              3.que.pop()  删除优先级最高的元素(弹出堆顶元素)
                                              4.que.top()访问优先级最高的元素(访问堆顶元素)
                                              5.que.empty()判断堆是否为空
插入删除的时间复杂空间度都是为对数级,访问堆顶元素的时间复杂度为常数级别。

接下俩是堆优先队列的一些基础操作
 

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
int main()
{
	priority_queue<int> que;
	que.push(7);
	que.push(1);
	que.push(12);
	printf("nmber:");
	cout<<que.size()<<endl;
	while(!que.empty())
	{
		cout<<que.top()<<endl;
		que.pop();
	}
	cout<<endl;
	return 0;
}

输出数据如下(这样就可以使用堆排序)

在优先队列中其实有三个参量,第一是选择的类型,第二就是我们可以选择的容器,我们课以放一个vector<int>来表示一维数组,第三个参数就是我们的自定义如less<int>就表示大顶,greater<int>就表示小顶对(我没想到这个竟然是反着来的)

priority_queue<int,vector<int>,greater<int>>//从小到大

 对于我们自定义

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
struct node
{
	int x,y;
	bool operator< (const node &b) const{//运算符重新定义,注意这个运算符只能定义小于号
		return this->x>b.x;//从大到小
	}
};

int main()
{
	priority_queue<node> que;
	que.push((node){5,2});
	que.push((node){2,4});
	while(!que.empty())
	{
		cout<<que.top().x<<endl;
		que.pop();
	}
	return 0;
}

输出结果:

在我们用基础的模板时,都会用到for循环来找到最小值(也就是打擂台的方法),但是这样就会导致时间超限,而堆每次的堆顶都是小顶堆,这样就免去了找到最小值的步骤从而减少对时间的开销。

那么有关最短路径问题我们以后就需要知道以下三点:1.对于最短路径问题我们需要使用dijstra算法    2.在dijstra算法中我们需要用到小顶堆来存放每一次的最小值。   3.我们需要使用链式前向星来存放边的信息。
  对于dijstra算法核心步骤:1.遍历与目标点相连的所有点,将其举止都更新。2.找出最小值并将其标记为以收入状态,直到所有点都已经全部标记。

代码如下:

#include<iostream>
#include<queue>
using namespace std;
const int maxN = 100010;
const int maxM = 500010;
int n, m, s, cnt;
struct node
{
	int id;
	int dis;
	bool operator< (const node& x) const {
		return x.dis < dis;
	}
};
priority_queue<node> q;
struct EDGE
{
	int to;
	int w;
	int next;
}edge[maxM];
int head[maxM];
bool vis[maxM];
int ans[maxM];
void add(int u, int v, int w)
{
	cnt++;
	edge[cnt].to = v;
	edge[cnt].w = w;
	edge[cnt].next = head[u];
	head[u] = cnt;
}
int main()
{
	cin >> n >> m >> s;
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		cin >> u >> v >> w;
		add(u, v, w);
	}
	for (int i = 1; i <= maxM; i++)
	{
		ans[i] = 0x7fffffff;
	}
	
	ans[s]=0;
	q.push(node{s,0});
	while(!q.empty())
	{
		node tmp=q.top();
		q.pop();
		int k=tmp.id;
		if(vis[k])
		continue;
		vis[k]=true;
		for(int i=head[k];i!=0;i=edge[i].next)
		{
			int to=edge[i].to;
			if(!vis[to]&&ans[to]>ans[k]+edge[i].w)
			{
				ans[to]=ans[k]+edge[i].w;
				q.push(node{to,ans[to]});
			}
		}
	}
	
	for (int i = 1; i <= n; i++)
	{
		cout << ans[i] << " ";
	}
	return 0;
}

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在使用Dijkstra算法求解最短路径时,可以使用链式前向星(Linked List Representation)来表示图的数据结构,以提高算法的效率。 链式前向星是一种用于存储稀疏图的数据结构,它将每个节点的邻接边以链表的形式存储。具体实现步骤如下: 1. 创建一个结构体或类来表示图的边,包括目标节点和边的权重等信息。 2. 创建一个数组,数组的每个元素表示一个节点,每个节点包含一个指向邻接边链表的指针。 3. 遍历图的所有边,对于每条边(u, v)和权重w,创建一个新的边节点,并将其插入到节点u的邻接边链表中。 4. 在Dijkstra算法中,需要使用一个优先队列(最小)来选择最短路径的节点。队列中的元素包括节点索引和到达该节点的距离。 5. 初始化距离数组和标记数组,距离数组记录起始节点到各个节点的最短距离,标记数组用于标记已经找到最短路径的节点。 6. 将起始节点加入优先队列,并将距离数组中起始节点的距离设为0。 7. 重复以下步骤,直到优先队列为空: a. 从优先队列中取出距离最小的节点u。 b. 遍历节点u的邻接边链表,对于每个邻接节点v,如果通过u到达v的距离更短,则更新距离数组中节点v的距离,并将节点v加入优先队列。 8. 当所有节点都被标记后,最短路径的结果就可以通过距离数组得到。 使用链式前向星可以减少遍历边的次数,提高Dijkstra算法的效率。同时,链式前向星还可以支持动态图的操作,如添加和删除边。但是,它需要额外的空间来存储链表和边节点,因此适用于稀疏图而非稠密图。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白色的风扇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值