1.有向无环图的单源点最短路
其实跟之前说的最长路是一样的
思路:广搜(拓扑排序)+ DP
如下图所示:
2.一般图的单源点最短路
Dijkstra(迪杰斯特拉)算法
算法思路:每次找离起点最近的那个点,确认它的最短路径值(这个点的最短路必须已经确认了),拿这个点去更新与它相邻的点,不断重复这个操作,从离起点的距离一点一点扩展开,有种贪心+bfs的感觉
举个栗子
说说用Dijkstra求最短路的过程
一开始将所有点到原点的距离设置为正无穷,从1号点出发,更新2号,4号,6号点到原点的距离,dis[2]=2,dis[4]=1,dis[6]=3,4号是到1号最近的点,4号去更新2号的值,更新不了,11>2,然后4号更新7号的值,dis[7]=3,此时2离起点最近,2号能更新到的点是3号和5号,此时需要考虑的是dis[6]=3,dis[7]=3,di[5]=7,dis[3]=8,6号离起点最近,然后7号也离起点最近,再用7号点去更新与它相邻的点5号和8号,dis[5]=6,dis[8]=11,5号离原点最近,用5号更新3号和8号的值,3号更新不了,11>8,更新8号dis[8]=6+4=10,得到最短路的数值10
适用于有向图和无向图,也可带环,不适用于有负权边的图
算法重复从结点集合v-s中选择最短路径最小的点结点u加入到集合s,然后对u的出边进行松弛操作,当把一个结点选进集合s后,就意味着已经找到了从源点到这个点的最短路径,但若存在负权边,就与这个前提矛盾,可能会出现的出的距离加上负权后比已经的到的最短路径还短的情况
比如说这个图,用Dijkstra它会认为1到2的最短路径是3,但其实是2,因为bfs最大的特点就是短视,它只能看到与自己相邻的点的情况,对于远方,它就懵了,如果有一边负的,它不知道多走一边会减小它的值
代码实现:
#include //O(n方)
using namespace std;
int mp[100][100];
int inf = 99999999;
int dis[100];
int book[100]={0};
int main() {
int n, m;
cin >> n >> m; //顶点数,边数
int a, b, c;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j)
mp[i][j] = 0;
else
mp[i][j] = inf; //初始化
}
}
for (int i = 1; i <= m; i++) {
cin >> a >> b >> c;
mp[a][b] = c; //建图
}
for (int i = 1; i <= n; i++) {
dis[i] = mp[1][i];
}
book[1] = 1;
//Dijkstra算法的核心部分
int u;
for (int i = 1; i <= n - 1; i++) {//找离原点最近的点
int minn = inf;
for (int j = 1; j <= n; j++) {
if ( book[j] == 0&&dis[j] < minn ) {
minn = dis[j];
u = j;
}
}
book[u] = 1;
for (int v = 1; v <= n; v++) {
if (mp[u][v] < inf) {
if (dis[v] > dis[u] + mp[u][v])
dis[v] = dis[u] + mp[u][v];
}
}
}
cout<<dis[n]<<endl;
}
这种朴素算法的时间复杂度是n的平方,而一般题目给的数据都是差不多le5,这时候肯定会爆,
所以我们用堆优化来降低时间复杂度
时间复杂度降到nlogn+m
堆优化了每次找离起点最近的点的时间复杂度。
邻接表优化了对于每一个基点,更新它的所有邻边的时间复杂度(上面那个就是1扫到n,很花时间)
链式前向星+优先队列
priority_queue默认的是大根堆
priority_queue支持小根堆的一种方法
priority_queue<int,vector<int>,greater<int>> q;
可以看到,我们把距离放在pair对的第一个,把对应的点放在pair对的第二个,这是因为小根堆的比较对象是pair对的第一个元素
而我们正是要对距离排序,把离起点最近的距离放在堆顶
算法思路:
1.链式前向星存边
2.采用优先队列和c++中的二元组pair,它相当于一个包含两个变量的结构体,所以这里也可以用结构体实现,使用重载运算符(结构体的内嵌比较函数),但是我不太会,所以就没用
3.每次离原点最近的点都放在堆顶,并且用vis数组记录访问过的点,防止来回兜圈
4.弹出堆顶,搜索堆顶的所有连边,如果从起点到v的距离大于从从起点到u的距离+从u到v的距离,那么就更新最小距离,把它对应的点和距离加入队列
5.遍历,输出值即可
代码实现:
#include
#define inf 0x3f3f3f3f
using namespace std;
const int maxn = 2e5;
struct node
{
int v,next,w;
}edge[maxn];
int cnt;
bool vis[maxn];
int dis[maxn],head[maxn];
void add(int u,int v,int w)//起点,终点,距离,链式前向星建图
{
edge[cnt].w=w;
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void dijkstra(int n)
{
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
memset(dis,inf,sizeof dis);
q.push({0,1});
dis[1]=0;
while(!q.empty())
{
pair<int,int> temp = q.top();//记录堆顶,即堆内最小的边并将其弹出
q.pop();
int u = temp.second;//点
if(vis[u]) continue;//如果被访问过,就跳过
vis[u]=true;//标记
for(int i = head[u];i!=-1;i=edge[i].next)//搜索堆顶的所有连边
{
int v = edge[i].v;
if(dis[v]>dis[u]+edge[i].w)//松弛操作
{
dis[v]=dis[u]+edge[i].w;
q.push({dis[v],v});//把新遍历的点加到堆中
}
}
}
//if(dis[n]==inf) cout<<-1<
//else cout<
for(int i=1;i<=n;i++)
cout<<dis[i]<<endl;
}
int main()
{
int n,m,u,v,w;
cin>>n>>m;
memset(head,-1,sizeof(head));
for(int i = 0;i<m;i++)
{
cin>>u>>v>>w;
add(u,v,w);
}
dijkstra(n);
return 0;
}
/*
测试数据
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4*/