1.Floyd
2.Dijkstra
3.SPFA(队列优化Bellman算法)
4.Best-First-Search——A算法
5.启发式A*
6.启发式的A*的平局打破
7.目前自学存在的急需解决的问题
8.最短路径算法的优劣比较
9.各个算法的路径记录的策略
Floyed算法是需要不断的通过第三方节点来松弛目标两个节点之间的距离,通过遍历图中所有的顶点,从而实现全局最短路径的求解
所以这里,我们的两点之间的边权值是要不断的改变的,所以我们果断采用邻接矩阵来进行图的存储,这样会更加利于操作
我们通过求解最优子路径来求得全局最优路径,在这里,两个点之间的最优路径要么就是两点之间直接的连边,要么就是通过其他若干
的节点来进行松弛,所以,这里面我们一各个点为基准,构建三个循环,最外面的循环遍历所有的第三方节点,里面的循环控制两个目标
节点,在这里,可能有的人会问了,这样的话,只是以一个节点作为中间节点来考虑的,但是实际上,有可能最短路径包含不止一个中间节点
没错,在这里,我们要这么考虑,每次一个中间节点考虑完之后,邻接矩阵中的所有的边的权重都是考虑了这个已经考虑过得第三方节点
优化后的结果,所以我们下次再用别的第三方节点的时候就必然会将之前的所有的考虑过得第三方节点都纳入考虑过的优化范围之内,所以
最后的结果就是,我们任意两点之间的最短路径都是考虑了所有的第三方节点来进行优化的
个人感觉一点:Floyd的本质很可能就是动态规划
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(map[i][j]>map[i][k]+map[k][j]) map[i][j]=map[i][k]+map[k][j]; //考虑第三方节点k的优化
}
}
}
Dijstra的核心是不断的维护一个dis数组,最后得到的dis数组中的左右的权重就是源点到图中所有的节点的最短路径的长度,所以在这里
数据结构我们是不必过分的强求的,邻接矩阵,邻接表,链式前向星,边集数组都是可以的,这里我们用数组模拟链表来进行数据结构的讲解
其他的数据结构在理解了核心的之后都是轻而易举
a.录入图的信息完成初始化
b.找到在dis数组中权重最小的节点p(目前距离源节点最近的节点)
c.利用p的所有的出边优化源节点到p出边的临近节点的边权值
d.图中除了源节点以外的n-1个点都已经优化过,继续e,否则返回b
e.输出dis数组的权重
Dijstra算法其实很好理解,我们每次利用距离原点最近的节点作为第三方节点来优化源节点和第三方节点的出边临近节点,当所有的
节点全部考虑完了以后,我们得到的必然就是单源节点到其余节点的最短路径
先对朴素的Dijstra来说,我们需要用book数组记录那些节点我们已经访问过,在遍历求解距离单源点最近的节点的时候我们可以不访问
那些考虑过的节点
#include"iostream"
#include"cstdio"
#include"cstdlib"
#define inf 99999999
#define Nedge 5000
#define Npoint 1000
using namespace std;
int u[Nedge];
int v[Nedge];
int w[Nedge];
int first[Npoint];
int nextk[Nedge];
//上面是链式前向星的数据结构
int book[Npoint];
int npoint,nedge;
int dis[Npoint];
int main()
{
cin>>npoint>>nedge;
memset(book,0,sizeof(book));
memset(first,-1,sizeof(first));
memset(nextk,0,sizeof(nextk));
for(int i=1;i<=nedge;i++)
{
int a,b,c;
cin>>u[i]>>v[i]>>w[i];
nextk[i]=first[u[i]];
first[u[i]]=i;
}
book[1]=1;
int k=first[1]; //这里讲 1 当做源节点,下面的代码是对dis进行初始化
for(int i=1;i<=npoint;i++) dis[i]=inf;
while(k!=-1)
{
dis[v[k]]=w[k];
k=nextk[k];
}
dis[1]=0; //自己到自己的距离肯定是0
for(int i=1;i<=npoint-1;i++)
{
int minedge=inf;
int minpoint;
for(int i=1;i<=npoint;i++) //找到最近的节点
{
if(book[i]==0&&dis[i]dis[minpoint]+w[k]) dis[v[k]]=dis[minpoint]+w[k];
k=nextk[k];
}
}
for(int i=1;i<=npoint;i++) cout<
在这里我们要注意因为每次选最近的点都要进行遍历操作,但是我们可以优化一下,对,我们可以用堆,根据dis中的权重为判断依据,我 们来构建最小堆,可以大大提高Dijstra的速度
#include"iostream"
#include"cstdio"
#include"cstdlib"
#define inf 99999999
#define NP 1000
#define NE 2000
using namespace std;
int u[NE];
int v[NE];
int w[NE];
int first[NP];
int nextk[NE];
int dis[NP];
int heap[NP];
int pos[NP]; //pos记录i号节点在堆中的位置,在变松弛之后方便向上调整
int heapnumber=0;
int n,m;
void swap(int x,int y)
{
int t=heap[x];
heap[x]=heap[y];
heap[y]=t;
t=pos[heap[x]]; //同步更新
pos[heap[x]]=pos[heap[y]];
pos[heap[y]]=t;
}
void siftdown(int i)
{
int t,flag=0;
while(i*2<=heapnumber&&flag==0)
{
if(dis[heap[i]]>dis[heap[i*2]]) t=i*2;
else t=i;
if(i*2+1<=heapnumber&&dis[heap[i*2+1]]>n>>m;
heapnumber=n;
for(int i=1;i<=n;i++)
{
first[i]=-1;
nextk[i]=0;
heap[i]=pos[i]=i;
dis[i]=inf;
}
dis[1]=0; //以 1 为源点
for(int i=1;i<=m;i++)
{
cin>>u[i]>>v[i]>>w[i];
nextk[i]=first[u[i]];
first[u[i]]=i;
}
int k=first[1];
while(k!=-1) //初始化dis数组
{
dis[v[k]]=w[k];
k=nextk[k];
}
for(int i=n/2;i>=1;i--) siftdown(i); //初始化堆
pop();
for(int i=1;i<=n-1;i++)
{
int minpoint=pop();
k=first[minpoint];
while(k!=-1)
{
if(book[v[k]]==0&&dis[v[k]]>dis[minpoint]+w[k])
{
dis[v[k]]=dis[minpoint]+w[k];
siftup(pos[v[k]]);
}
k=nextk[k];
}
}
for(int i=1;i<=n;i++) cout<
点击打开链接
我们首先来分析下含负权边的无向图:
1.先看图
我们求A点到C点的最短距离,很明显答案为1.
2.我们用dij来跑下,看过程:
3.看完了dij过程可能仍有人不是很明白为什么,没关系,待会儿会详细解释,现在我们看下带负权边的有向图:
4.如图,我们还是求A到C的最短距离,很明显,答案还是15.我们还是用dij来跑下:
PS(有的人在倒数第二步没有判断点是否标记,导致求出来的结果是1,然而这时错误的,下面我将说明)
6.我们来看看原因:
我们先来看看dij的由来,dij求最短路的算法是由贪心得来的,也就是说长路径的松弛正确的前提是用来松弛它的短路径是最短的,也就是说在之后是不会变的,这在非负权值的情况下是对的,然而遇到负权值便错了,因为当加入了负权值边后便可能使之前的短边变得更短,就如图中一样,我们先访问了C点,则AC的距离在之后的距离应该是不变的,这在都是非负权值时是正确的,因为每条边都是非负的,当通过其他点来中转时,所经过的路径和必然不小于AC的距离,然而加入了负权边后,使得AC的距离变得比初始更小,这便使得前提错误,前提都错了,dij算法便不成立,结果便错误,这也是为什么有那么多人糊涂的原因,也是我专门举这个例子的原因
#include"iostream"
#include"cstdio"
#include"cstdlib"
#define inf 99999999
#define NP 1000
#define NE 2000
using namespace std;
int queue[NP*2];
int head,tail;
int dis[NP];
int book[NP];
int n,m;
int u[NE];
int v[NE];
int w[NE];
int first[NP];
int nextk[NE];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
dis[i]=inf;
first[i]=-1;
nextk[i]=0;
book[i]=0;
}
memset(queue,0,sizeof(queue));
head=tail=1;
dis[1]=0; //假设 1 为源点
for(int i=1;i<=m;i++)
{
cin>>u[i]>>v[i]>>w[i];
nextk[i]=first[u[i]];
first[u[i]]=i;
}
queue[1]=1;
tail++;
book[1]=1; //小心这里dis一定不可以先初始化,因为一旦初始化将源节点的出边进行添加的话,源节点的出边的弧头书不会入队列的,算法就 //出现问题了
while(head!=tail)
{
int k=first[queue[head]];
while(k!=-1)
{
if(dis[v[k]]>dis[u[k]]+w[k])
{
dis[v[k]]=dis[u[k]]+w[k];
if(book[v[k]]==0)
{
book[v[k]]=1;
queue[tail]=v[k];
tail++;
}
}
k=nextk[k];
}
book[queue[head]]=0; //这一步是非常有必要的
head++;
}
for(int i=1;i<=n;i++) cout<
1
2 3
4 5 6
#include"iostream"
#include"cstdio"
#include"cstdlib"
#include"cmath"
#define N 1000
using namespace std;
typedef struct node
{
int c;
int x,y;
double prev; //优先顺序
int px,py; //记录前驱,输出路径的时候需要
}point;
int book[100][100]; //记录是否在close数组里面
point map[100][100];
point pre[N]; //堆
int numpre=0;
point close[N];
int numclo=0;
int nextk[4][2]={{1,0},{0,1},{-1,0},{0,-1}}; //四个方向的扩展
int n,m;
int sx,sy;
int ex,ey;
void swap(int x,int y)
{
point t=pre[x];
pre[x]=pre[y];
pre[y]=t;
}
point count(int x,int y)
{
point w;
w.x=x;w.y=y;
w.prev=sqrt(pow(w.x-ex,2)+pow(w.y-ey,2));
return w;
}
void siftup(int i)
{
int t,flag=0;
while(i!=1&&flag==0)
{
if(pre[i].prevpre[i*2].prev) t=i*2;
else t=i;
if(i*2+1<=numpre&&pre[i*2+1].prev>n>>m;
memset(map,0,sizeof(map));
memset(pre,0,sizeof(pre));
memset(close,0,sizeof(close));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>map[i][j].c;
}
}
cin>>sx>>sy>>ex>>ey;
map[sx][sy]=count(sx,sy);map[sx][sy].px=map[sx][sy].py=0;
pre[1]=count(sx,sy);pre[1].px=pre[1].py=0;
numpre++;
while(numpre!=0)
{
point w=pop();
for(int i=0;i<4;i++)
{
int dx=w.x+nextk[i][0];
int dy=w.y+nextk[i][1];
if(book[dx][dy]==0)
{
map[dx][dy].x=dx;map[dx][dy].y=dy;
map[dx][dy].px=w.x;
map[dx][dy].py=w.y;
}
if(dx==ex&&dy==ey)
{
numpre=0;
break;
}
if(map[dx][dy].c==1||dx<1||dx>n||dy<1||dy>m||book[dx][dy]==1) continue;
if(judgeopen(dx,dy));
else if(judgeclose(dx,dy,w.x,w.y));
else push(dx,dy,w.x,w.y);
}
}
point stack[N];
memset(stack,0,sizeof(stack));
int numberofstack=0;
stack[1]=map[ex][ey];
numberofstack=1;
while(!(stack[numberofstack].px==0&&stack[numberofstack].py==0))
{
stack[numberofstack+1]=map[stack[numberofstack].px][stack[numberofstack].py];
numberofstack++;
}
for(int i=numberofstack;i>=1;i--) cout<<'('<
6)比较与Dijstra:
相对于Dijstra而言,BFS更具有目的性,也就是说,我们在搜索的时候根据优先队列会优先选择要扩展的点,这在图搜索中十分有用
的,Dijstra属于盲目搜索,因为没有启发,所以搜索的时候我们事项四周进行的,没有目的性的扩展队列,所以说在图很大的时候,我
们用启发式的搜索会更快一点
5+6.启发式A*
1)启发式的A*算法简介:
启发式的A*算法实在BFS(最佳优先搜索)的基础上增添了所谓的耗散函数,通过耗散函数和误差估计函数之和,从而决定我们优先开
发的顺序
2)启发式A*算法和BFS基本上原理:
这里的原理是差不多的,鄙人也总结不出来好的意见,这里援引大牛的博客就好,平局打破的思路是非常的优秀的
启发式A*以及平局打破的策略(我们打破平局的原因是,如果我们考虑平局的话,有可能我们会将所有的最短路径都遍历一遍,但是实 际上我们只需要找到一个最短路径就可以了,所以通过各种策略减少遍历的个数——本人比较倾向于计算向量内积的策略)
7.目前的问题: