#include<iostream>
using namespace std;
int main(){
cout<<"真的是很不正经的指南,希望能理解";
}
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cout<<"形成了肌肉记忆"<<endl;
//不要问我头文件为什么那么多没用的
}
被最短路和最小生成树折磨疯了的我……已经形成肌肉记忆了要开始敲板子了,幸亏理智尚存这就是为什么头文件很多没用的而且我还可以敲更多没用的, 好啦进入正题
最小生成树
克鲁斯卡尔(Kruskal)和普里姆(prim)算法
只讲原理不讲代码实现,代码实现网上有很多随便一份都比我写得好,当然也有比较水的(比如我),多看几个就好啦~所以想要看代码实现的,抱歉右上角红叉叉……
- Kruskal:利用贪心每次寻求边权最小的边加入到边集合中,在对应的将点放在点集合中(集合只是一个概念,抽象化的东西,不要深究)
那么问题就来了,所有满足边权小的边全部都要吗?当然不了,如果该边的两个顶点都在点集合中当然要舍弃了,因为虽然该边是最小的,但是之前还有比它边权小的边所以并不影响结果啊。而且仔细想一想,最小生成树不就是说把一个图截取部分边变成一棵树吗?树是不允许有环的存在,如果我们还要这条边的话不就是又变成了图?
实现思路可以利用并查集去找父结点(祖先结点)去判断两个点是否已经相连;
简单说一下并查集(具体的自行百度
把有关联的点连在一起——并
不断地找父亲结点(直到找到某一个要寻找的祖先结点)——查
不断地把点放入集合中——集
最后会形成一棵树,这棵树会形成什么样的树取决于数据和操作,操作有很多优化,比如压缩路径啊,当然了这些属于自行百度的,加油;
- prim:如果是Kruskal是对边的操作,prim就要加上对点的操作了,确定一个点开始找与其相连的最小的边(随便一个点,因为最小生成树要求每一个点都要遍历到,所以也就是说任意一个点都可以满足情况)然后放入边集合和点集合中,在找与点集合中所有点相连的最小的一条边(如果有多个任意一条)可以想象的到,Kruskal是由若干个分散的树慢慢合为一个树,但是prim就是从一开始就是一棵树,不断地扩大树。实现上和迪杰斯特拉(最短路)差不多,就是比迪杰斯特拉少了一个“三边判断”也是同Dijkstra一样可以用优先队列(堆)去优化。
其实也是用到了贪心,寻求该点最小边
先确定一个点,用一个数组(任何线性表都可以,只要你顺手并且能用明白)记录最短距离,在最短距离中找到一个最小值,加入边集合,得到的点加入点集合(做一个标记,表示已经加入集合中)再便利与该点相连的所有点的边,更新最短距离数组,重复上述过程;
可以得出 prim复杂度是O(n2)利用堆优化O(nlogn)而克鲁斯卡尔的时间复杂度为O(eloge)也就是说取决于边的多少,所以根据不同情况可以选择不同算法
最短路
- 佛洛依德(Floyd)
三重for循环,不断地利用中间点去更新与其相连的两个点的距离
虽然时间复杂度高达O(n3)但是有效的解决了每一个点到每一个点的最短路径啊
- 迪杰斯特拉(Dijkstra)
找到一个源点(起点)然后用一个数组记录最小路径,然后找到最小的最小路径,记录该点,然后再判断与其相连的两边之和是否大于另外两点就距离,更新最小路径数据,重复上述过程
时间复杂的O(n2)利用堆优化O(nlogn),只能找到源点到其他点的最短路,并且无法处理负边权
- 贝尔曼福德(bellman-ford)
重点:不断地进行松弛操作
(松弛:让数据变得更小,两边之和与第三边的关系)
初始化,每一个点和源点的距离为inf(无穷大)源点到源点距离为0;遍历每一个点进行松弛操作;如果次数大于点个数的话就说明存在负权值;
时间复杂度O(E + nlogn) ,可不可以降下来呢?因为有的松弛操作没必要进行啊
- SPFA 利用队列去优化bellman-ford,每次松弛的点是由队列中元素决定的,一个元素可以多次入队,入对的次数决定了是否存在负边。
时间复杂度:O(nE);