启动:
2022.2.16总结
系统学习最短路算法之无负权回路单源最短路算法:
有负权回路(有回路,且回路长度总和为负数(回路之间各个点之间边的和是一个负数,那么回路转下去为负无穷))最短路不一定存在:
总体来说解决无负权回路单源最短路算法常用基础算法有1.dijkstra()2.spfa()两种算法
其中dijkstra使用堆优化版本
spfa是贝尔曼福特算法队列优化的结果
接下来来搞这俩算法!
堆优化版本dijkstra()–>java语言版
时间复杂度O(mlogn) (n是点个数,m是边个数)
铺垫知识:
知识点(1):邻接表
e[],ne[],h[],idx,w[];
static void add(int a,int b,int c){
e[idx]=b;
ne[idx]=h[a];
w[idx]=c;
h[a]=idx++;
}
//知识点随后补充到博客
知识点(2):小根堆
static class PII{
int num;//节点编号
int distance;//节点和目标节点间的距离
PII(int num,int distance){
this.num=num;
this.distance=distance;
}
}
PriorityQueue <PII> aq=new PriorityQueue<>(Comparator.comparing(PII->PII.distance));//小根堆,类排序
算法过程:
整体思路:
在优先队列中存放的队头元素一定是当前状态下距离源点最近的且未被确定的点!
由此我们通过提取队头元素,然后通过它更新与它直接相连的节点距离,随后将其加入队列中(由于是小根堆,不论队列里加入多少节点,队头节点永远满足当前未被确定的状态下距离源点最近的节点!)
随后逐步更新直到队列为空!
最后判断目标节点距离源点的距离是否为最开始初始化的无穷大,若为无穷大则表明无法更新到目标节点(没有一条路通往目标节点),若是其他数值(表示其和源点最近的距离),则直接返回即可!
算法流程:
- 初始化处理,邻接表头数组h(初始化为-1 ),距离数组dist(初始化为无穷大)
- 将源点的序号和距离一起(通过新建类实现)加入优先队列中
- 在队列不为空的前提下,抛出表头元素,并将其状态标记为true(表示已经使用过了)
- 用表头元素更新与它直接相连的点,并将其加入优先队列中
- 结束之后判断是否能够到达目标节点,若可以直接返回,不可以返回题目给定内容
鸡汤来喽(代码部分)
//之前记得定义全局变量......
public static int dijkstra(){
PriorityQueue<PII> aq=new PriorityQueue<>(Comparator.comparing(PII->PII.distance));//建立优先队列
dist[1]=0;//源点为1号节点,距离为0,其余dist为0x3f3f3f3f表示无穷大
aq.add(new PII(1,0));//将新节点加入队列中
while(!aq.isempty()){
PII temp=aq.poll();
int num=aq.num;
int distance=aq.distance;
if(st[num]==true)continue;//如果用这个点更新过其他节点,那么就直接略过!
st[num]=true;//标记当前使用的节点,之后不再使用
for(int i=h[num];i!=-1;i=ne[i]){//用当前节点,更新其他与其直接相连的节点
int j=e[i];
if(dist[j]>dist[num]+w[i]){
dist[j]=dist[num]+w[i];
aq.add(new PII(j,dist[j]));//将刚刚更新过的节点加入优先队列中
}
}
}
if(dist[n]==0x3f3f3f3f)return -1;//目标节点若为初始化的无穷大,则表示无法到达
return dist[n];
}
public static void main(String[] args) {
//这里面已经将表建好,并且初始化了h数组和dist数组
}
队列优化版本spfa()–>java语言版
时间复杂度:O(m) 最差O(nm) (n是点个数,m是边个数)
注意:spfa算法很像dijkstra算法= =,但是又有区别,dijkstra不可以判断是否存在负环,但spfa可以!
铺垫知识:
嘿嘿嘿嘿,因为两个很相像,所以上面铺垫了,下面就不说了!
打这个板块是为了看起来整齐点!
算法过程:
整体思路:
大概思路和dijkstra相似但是又大有不同,这里队列里存放的不再是1.距离最近且2.未被使用这两个条件同时存在的点了,
而是只保留一个条件:未被使用的点!
从源点出发,将与其直接相连的点依次更新,并加入队列中(只有发生更新才会加入队列,防止陷入死循环,记得细细品),之后将刚刚用来更新其他节点的点从队列中抛出,并将其状态改为false。
之后一直判断到结束即可!是不是比dijkstra看起来要简单?嘿嘿嘿!
算法流程
- 初始化处理,邻接表头数组h(初始化为-1 ),距离数组dist(初始化为无穷大)
- 将源点的序号加入队列中,并将其标记为true
- 在队列不为空的前提下,抛出表头元素,并将其状态标记为false
- 用表头元素更新与它直接相连的点,若发生跟新则将其加入队列中,并将状态改为true
- 结束之后判断是否能够到达目标节点,若可以直接返回,不可以返回题目给定内容
鸡汤又来喽(代码部分)
//之前记得定义全局变量......
public static void spfa(){
dist[1]=0;
Queue<Integer> q=new LinkedList<>();
q.add(1);//将源点加入队列
st[1]=true;//将状态改为使用过
while(!q.isempty()){
int t=q.poll();
st[t]=false;//从队列中抛出后,将状态改为未使用过
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
if(!st[j]){//判断更新过后的节点是否在队列中,若不存在则加入队列,并将其状态改为true
q.add(j);
st[j]=true;
}
}
}
}
}
public static void main(String[] args) {
//这里面已经将表建好,并且初始化了h数组和dist数组
}
总结:
-
dijkstra与spfa中st[]数组的作用是不一样的,dijkstra中st数组具有单调性,而spfa中st数组不具备单调性
-
记得初始化h数组和dist数组,有时候写题写顺手了,好像很自然就忘记初始化了
-
spfa从队列中使用了当前的点,会把该点pop掉,状态数组st[i] = false(说明堆中不存在了) ,更新临边之后,把临边放入队列中, 并且设置状态数组为true,表示放入队列中 。如果当前的点距离变小,可能会再次进入队列,因此可以检验负环: