首页 > 编程语言 >路径规划的最优算法实现

路径规划的最优算法实现

时间:2023-02-11 15:56:24浏览次数:38  
标签:int void 路径 算法 inline 最优 dis

  1. 项目背景与简介

在生活当中,我们经常面对了许多路径规划问题,通常会考虑下列几种因素:红绿灯多少,道路长短,车流量多少。一般都是应用程序在帮我们做出判断和选择,而我们减少了对这一类问题的思考。本篇文章讲述了四种可以使用的不同的方法,每一种方式各有优劣,都值得大家来学习探讨。

接下来是本篇论文的一些约定:

如果下文档中没有做特殊提示,默认所有下标从\(1\)开始,并且默认\(n,m\)同阶

因为在生活当中,路径的长度大多为正数,没有特殊说明,不考虑长度为负数的情况

\(s\)为起点,\(t\)为终点

\(dis[i]\)表示熊起点到编号为\(i\)的点的最短路径长度

\(inf\)表示一个极大值

2.项目用到的相关知识

  • 链式前向星

一种检索出与当前点直接连边的点的方式,如果不会可以参考一下理解

for(register int i=head[x];i;i=e[i].nxt){
   //表示存在一条从x到e[i].to的边,边权为e[i].d
}
  • 一些循环语句基础

  • 一些\(stl\)(函数库知识)

  • 计算机一秒钟大概运行\(10^8\)次简单运算

3 编程思路及具体实现

3.1 编程思路

为了更好理解接下来要讲的知识,我们给出一个形式化的题意共大家参考。

在一张\(n\)个点\(m\)条边有向/无向图中,求点\(a\)到点\(b\)之间的最短路径。

上面就是有关这一类问题的最简化形式,接下来考虑通过几种不同的视角解决这个问题。

算法一:暴力。

题目要我们找出最短路径,我们就把所有的路径找出来,然后判断哪一个是最短的即可,时间复杂度巨大,在生活当中有时会浪费掉大量的枚举时间,经常不值得使用。但是这种方法可以把所有的路径找出来,在有时候我们判断优劣的方式更加复杂的时候,这种方式可能会有奇效。

因为在不同的题目当中,暴力所涉及到的代码类型会各不相同,这里只给出一个大概的框架参考

inline void check(){ //对比当前路径和最优路径直接哪个更加优秀
	...
}
inline void dfs(...){ //寻找一条路径,记录当前所需要存储的状态
	if(...){ //如果当前已经找到了一条路径
		check(); //判断是否最优
		return;
	}
	... //继续寻找一条路径
}

算法二:\(floyd\)。

这是一个可以求出全源最短路径的算法(求出任意两点之间的最短路)算法

我们考虑每次枚举一个中转点,然后暴力判断所有经过中转点的转移状态

如果我们设\(f[i][j]\)表示从点到点的最短路径,我们就可以列出如下转移

\(f[i][j]=min(f[i][j],f[i][k]+f[k][j])\)

这个算法的正确性不言而喻,时间复杂度为\(\Theta(n^3)\)

实现的过程当中,要注意,先枚举\(k\),再枚举\(i,j\),这里提供了一份参考代码

fr(k,1,n) fr(i,1,n) fr(j,1,n) f[i][j]=min(f[i][j],f[i][k]+f[k][j])

算法三:\(spfa\)

这个算法再现在一般不常用,但是在某些特定的情况下依然可以用到,并且表现十分优异

这类最短路不能处理出现出现负环的情况,接下来会讲述这种情况如何特判

我们首先找到起点,然后将其加入队列,进行如下操作

  1. 取出并弹出队首,我们设其为\(x\)

  2. 找出所有\(x\)可以到的边,如果最短路答案可以更新就更新,在靠右更新的条件下插入队列

  3. 如果当前队列为空,就结束,否则重复操作\(12\)

在上述流程当中,如果一个点进队了\(n\)次,说明原图存在负环

参考代码如下:

inline void spfa(){
    fr(i,1,n) dis[i]=inf;
    dis[s]=0;
    queue<int> q;
    q.push(s);vis[s]=1;
    while(!q.empty()){
        int y;
        y=q.front();
        q.pop();
        vis[y]=0;
        for(register int i=head[y];i;i=e[i].nxt){
            int to;
            to=e[i].to;
            if(dis[to]<=dis[y]+e[i].d) continue;
            dis[to]=dis[y]+e[i].d;
            if(!vis[to]) vis[to]=1,q.push(to);
        }
    }
}

这个算法时间复杂度是\(\Theta(km)\),其中\(k\)是每个点的进队次数,在随机数据下表现优异,一般可以当作一个常数,但是存在一些数据可以卡到最坏时间\(\Theta(nm)\)

算法四:\(dijskra\)(+堆优化)

这个算法在信息学竞赛中较为常用,时间复杂度较为稳定且实现效率高

我们考虑优化算法三

我们发现,有很多没有用的状态被我们加入了队列当中,所以我们考虑把队列替换成优先队列\((priority\_queue)\),然后每个点最多只可能入队一次,这个证明留给读者自行思考

但是要注意,这个算法不能判断负环,所以如果题目当中没有保证不出现负环,那么要不就使用算法三,要不然就使用一些处理技巧把负环去掉,至于使用什么类型的处理技巧要根据不同的题目来实现

时间复杂度为\(\Theta(n\ log\ n)\)

代码实现如下:

inline void Dij(){
	for(register int i=1;i<=n;++i)
		Dis[i]=inf;
	Dis[k]=0;
	ddd tmp;
	tmp.dis=0;
	tmp.pos=k;
	q.push(tmp);
	while(!q.empty())
		{
			ddd Tmp;
			Tmp=q.top();
			q.pop();
			if(vis[Tmp.pos])
				continue;
			vis[Tmp.pos]=1;
			for(register int i=head[Tmp.pos];i;i=e[i].nxt)
				{
					if(Dis[e[i].to]>Tmp.dis+e[i].d)
						{
							Dis[e[i].to]=Tmp.dis+e[i].d;
							ddd TTmp;
							TTmp.dis=Dis[e[i].to];
							TTmp.pos=e[i].to;
							q.push(TTmp);
						}
				}
		}
}

上面的四种方法,是能求出最短路的最常用的四种方法

3.2 具体实现

在上面讲述思路的过程当中,我们已经把粗略的代码放入到了讲解当中,接下来我们看一道例题,来更好的了解这个算法的思路

例题:

小明是一位美食家,由于尝尽了各地的美食,导致他的体重飞速增长,从而使得走路会给他带来沮丧值

小明现在手上有一个地图,其中标记出来了\(n\)个城市,编号为\(1-n\),并且有\(m\)条连接\(n\)个城市的路径,每条路径是一个单向路,表示为:存在一条从\(u_i\)到\(v_i\)长度为\(w_i\)个单位的路径

他一开始在起点\(s\)

因为他没有更多的交通共工具,所以他只能用腿走路,规定每秒可以走一个单位长度的路径

每停留一秒钟的时间,小明就会积攒一点的沮丧值

问小明到每个城市的最小沮丧值为多少

给定\(n,m,s\)和每一条路径

  • \(input:\)
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
  • \(output:\)
0 2 4 3

\(n,m \leq 10^5\)

解法:

不难在题目中抽离出来一个最短路模型

我们发现,在这道题目当中,只需要求一个点到其他点的最短路,并不需要求任意两点之间的最短路,并且我们通过对比时间复杂度,可以发现使用\(dijkstra\)最优

然后使用\(dijkstra\)算法即可通过此题

代码实现如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
	int x=0;
	bool f=0;
	char c=getchar();
	while (!isdigit(c))
		  f|=(c=='-'),c=getchar();
	while (isdigit(c))
		  x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return f?-x:x;
}
void write(int x) {
    if(x<0) {
        putchar('-');
        x=-x;
    }
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline void writepl(int x){write(x);putchar(' ');}
inline void writeln(int x){write(x);putchar('\n');}
const int Maxn=1e6+10,inf=1e13;
int n,m;
int k;
struct node{
	int nxt;
	int to;
	int d;
}e[Maxn];
struct ddd{
	int dis;
	int pos;
	bool operator < (const ddd &x) const {
		return dis>x.dis;
	}
};
int head[Maxn];
int cnt;
priority_queue<ddd> q;
int Dis[Maxn];
bool vis[Maxn];
inline void add(int u,int v,int w){
	cnt++;
	e[cnt].nxt=head[u];
	head[u]=cnt;
	e[cnt].to=v;
	e[cnt].d=w;
}
inline void Dij(){
	for(register int i=1;i<=n;++i)
		Dis[i]=inf;
	Dis[k]=0;
	ddd tmp;
	tmp.dis=0;
	tmp.pos=k;
	q.push(tmp);
	while(!q.empty())
		{
			ddd Tmp;
			Tmp=q.top();
			q.pop();
			if(vis[Tmp.pos])
				continue;
			vis[Tmp.pos]=1;
			for(register int i=head[Tmp.pos];i;i=e[i].nxt)
				{
					if(Dis[e[i].to]>Tmp.dis+e[i].d)
						{
							Dis[e[i].to]=Tmp.dis+e[i].d;
							ddd TTmp;
							TTmp.dis=Dis[e[i].to];
							TTmp.pos=e[i].to;
							q.push(TTmp);
						}
				}
		}
}
signed main(){
	//请输入小明拿到的地图的:点数,边数,起点,请保证,起点的标号小屋点数
	cout<<"请输入小明拿到的地图的:点数,边数,起点"<<endl;
	n=read();
	m=read();
	k=read();
	//请分别m条边每一条边的具体情况:入点,出点,边权(长度)
	cout<<"请分别m条边每一条边的具体情况(每一条边一行,一行三个数):入点,出点,边权(长度)"<<endl;
	for(register int i=1;i<=m;++i)
		{
			int x,y,z;
			x=read();
			y=read();
			z=read();
			add(x,y,z);
		}
	Dij();
	//小明到达每一个城市分别的沮丧值
	cout<<"小明到达每一个城市分别的沮丧值"<<endl;
	for(register int i=1;i<=n;++i)
		{
			printf("小明到达编号为%lld的城市会有%lld的沮丧值\n",i,Dis[i]);
			//write(Dis[i]);
			//putchar(' ');
		}
	return 0;
}

接下来我们给出具体的三组事例供大家理解

标签:int,void,路径,算法,inline,最优,dis
From: https://www.cnblogs.com/AntelopeWang/p/17111827.html

相关文章