dijkstra 알고리즘
방향성 가중치 그래프 의 단일 소스 최단 경로는 모든 점에서 특정 정점 i까지의 최단 경로를 찾는 데 적합합니다.
적용 범위 : 모든 에지 가중치가 양수인 그래프. 그리고 루프의 존재를 허용하십시오.
로 나누어 두 종류의 순진 다 익스트라 알고리즘 과 힙 최적화 익스트라 알고리즘 .
Dijkstra 알고리즘의 아이디어 :
정점을 두 가지 범주로 나누기 : 최단 경로의 정점이 계산되었습니다.컬렉션그리고 최단 경로의 정점은 계산되지 않았습니다.
탐욕스러운 아이디어를 사용하여 i에 가장 가까운 정점 j가 집합 s의 정점에서 발견되지 않을 때마다 s에 추가 된 다음 j에서 정점 i까지 도달 할 수있는 점에서 거리가 업데이트됩니다. 최소값을 취하여 n 번 반복하면 N 개의 꼭지점이 모두 세트에 추가됩니다.
순진한 알고리즘과 힙 최적화 알고리즘의 알고리즘 차이는 i에 가장 가까운 정점 j가 집합 s의 정점에서 발견되지 않는다는 것입니다.
순진한 알고리즘은 인접 행렬 저장을 사용하는 조밀 한 그래프에 적합 하며 시간 복잡도는 O (n 2 )입니다.
g[i][j]=x
, 이는 거리 i-> j가 x임을 의미합니다.
힙 최적화 알고리즘은 인접 테이블 저장소를 사용하는 희소 그래프에 적합 하며 시간 복잡도는 O (mlogn)입니다.
n 포인트 수 m 변 수
분석 : dijkstra 알고리즘에 대한 헤비 사이드 및 자기 루프의 영향
고리.
자체 루프 : 자신에서 자신으로의 상황. 루프
자체는 확실히 자체적으로 업데이트되지 않지만 다른 지점까지의 거리를 업데이트 할 때 이미 세트에 합류했기 때문에 다른 사용자에 의해서만 업데이트됩니다. 업데이트 여부에 관계없이 최단 거리의 포인트는 유효하지 않습니다.
루프는 자기 루프의 일반적인 경우입니다. 그래프에 루프가 있으면 Dijkstra의 아이디어에 따르면 루프를 통과하지 않을 것입니다. 루프의 마지막 노드가 루프를 통과한다는 것은 이전 노드가 세트에 추가되었습니다., 더 이상 고려되는 범주에 포함되지 않으며 거리를 업데이트하지 않습니다. 세트에없는 케이스 만 고려되기 때문입니다.
무거운 모서리 는 다루기 쉬우 며 두 점 사이에 여러 모서리가있을 때는 가장 짧은 모서리 만 사용하십시오.
제목 설명
n 개의 점과 m 개의 간선이 있는 유 방향 그래프가 주어지면 그래프에 여러 간선 과 자체 루프 가있을 수 있으며 모든 간선 가중치는 양수입니다.
지점 1에서 지점 n까지의 최단 거리를 찾으십시오. 지점 1에서 지점 n까지 걸을 수 없으면 -1을 출력하십시오.
입력 형식 첫
번째 줄에는 정수 n과 m이 포함됩니다.
다음 m 개 행 각각에는 3 개의 정수 x, y, z가 포함되어 있으며, 이는 점 x에서 점 y로 향하는 가장자리가 있고 변의 길이가 z임을 나타냅니다.
출력 형식
정수를 출력합니다.지점 1에서 지점 n까지의 최단 거리。
경로가 없으면 -1이 출력됩니다.
입력 샘플 :
3 3
1 2 2
3 1
1 3 4
샘플 출력 :
삼
Naive Dijkstra 알고리즘
데이터 범위는
1≤n≤500,
1≤m≤10 5 이며
그림에 포함 된 변의 길이는 10,000을 초과하지 않습니다.
분석 :
n 개의 꼭지점, 최대 n × (n-1) 개의 방향성 그래프) <무거운 가장자리 및 자체 루프를 고려하지 않음> , n 2 는 2.5e5와 같고, m은 1e5와 같고, m과 n 2는 1입니다. 조밀 한 그래프 인 level 인접 매트릭스 스토리지를 채택하십시오.
#include <iostream>
#include <cstring>
using namespace std;
const int N=510,INF=0x3f3f3f3f;
int g[N][N];
int dis[N];
bool vis[N]; //判断该结点是否已加入统计完最短路径的集合s,初始时为false,均未加入
int n,m;//n个点 m条边
void dijkstra(int s)//处理 1号结点->所有结点 的最短路径
{
memset(dis,0x3f,sizeof dis);
dis[s]=0; //s到自身距离为0
//最多循环n次,每次选出一个距离1号结点最近的结点,可求出每个结点到s结点的距离
//第一次选出的一定是s结点自己
int num=n;
while (num--) {
int t=-1; //最终选出的t号结点,先初始化为小于1的结点(因为正常的结点是从1号开始)
for (int i=1;i<=n;i++) {
if (!vis[i] && (t==-1 || dis[i]<dis[t])) t=i;
}
if (t==n) break;
这题特定的结束条件:我们只需求1->n结点的最短路径。
此时到了n号结点,但是仍不能确定dis[t]的值是不是无穷大,设想一下:n个顶点,0条边的情况。
vis[t]=true;
//接下来更新 s->其它结点 通过t结点的最短距离
for (int i=1;i<=n;i++) {
if (!vis[i]) {
只需更新不在集合中的结点,但是if条件可省略
dis[i]=min(dis[i],dis[t]+g[t][i]);
} 若i是已经记录过最短路径的点,因为t后于i加入集合,所以dis[i]<=dis[t]必然成立(贪心),所以这里不需要!vis[j]的判断
}
}
}
int main()
{
scanf("%d%d",&n,&m);
//初始化邻接矩阵,规定自身到自身距离为0,但是求最短路时自环的边无效,可以不用初始化。
memset(g,0x3f,sizeof g);
//存储边
int a,b,w;
while (m--) {
scanf("%d%d%d",&a,&b,&w);
g[a][b]=min(g[a][b],w);//处理重边的情况,取最小值的边,同时忽略自环
}
dijkstra(1); //求1号点到所有点的最短距离。
dis[n]==INF?cout<<"-1":cout<<dis[n]; //输出1号顶点到n号顶点的最短距离
return 0;
}
dis 배열은 s 노드에서 모든 노드까지의 최단 경로의 합을 저장하며, 욕심의 아이디어를 바탕으로 매번 s 노드에 가장 가까운 노드를 선택합니다.
따라서 while (num-) num 사이클에서는 매번 선택되는 dis [] 배열의 값이 차례로 증가합니다.
따라서 31 번째 줄부터 34 번째 줄까지 if (!vis[i]) { dis[i]=min(dis[i],dis[t]+g[t][i]);}
if 판단을 추가 할 수 있는지 여부를 결정합니다.
노드 i가 집합 s에 추가 된 노드이면 dis [i] <= dis [t]가 있어야하고 그래프의 간선 가중치는 모두 양수이므로 dis [i ]의 영향을받지 않습니다.
힙 최적화 dijkstra 알고리즘
힙 최적화 알고리즘은 가장 작은 힙을 사용하여 위의 21 22 23 라인을 최적화하고 결정하기 위해 n 사이클을 거치지 않고 매번 가장 짧은 거리의 정점을 직접 가져 오는 것입니다.
힙에 기록해야하는 정보는 노드 및 거리별로 정렬 된 다음 거리별로 정렬됩니다. 따라서 pair<int,int>
첫 번째는 거리를 저장하고 두 번째는 정점 번호를 저장합니다. 제거 후 거리 업데이트가 필요하며, 업데이트 된 거리 이후에는 dis 배열을 수정해야 하지만 또 다른 문제가있다 . 만약 노드가 이미 힙에 존재한다면 힙 수정을 다시해야한다는 것이다. 두 개의 키워드. 힙 수정 stl 컨테이너의 힙은 구현할 수 없으며, 힙을 손으로 작성하여 구현할 수 있지만 더 복잡합니다.
따라서 수정하지 않고 힙에 다시 삽입합니다.이 접근 방식은 힙을 쓸 필요가 없지만 힙의 요소가 중복된다는 것입니다. 정점은 같지만 거리가 다른 여러 요소가있을 수 있습니다. 원래 힙은 최대 N 개의 요소 (최대 N 개의 정점이 있기 때문에)가있을 수 있으며 더 많은 요소가 현재있을 수 있지만 효과가 없습니다. 각 정점은 최단 거리에 따라 거리를 한 번만 업데이트합니다. 정점이 vis 배열을 기반으로하는지 판단 할 수 있습니다. 중복을 피하기 위해 알려진 최단 경로 세트에 이미 추가되지 않았습니다.
참고할 또 다른 점 :
순진한 접근 방식에서는 노드 i에서 다른 모든 노드까지의 최단 거리를 최대 n 번 반복하여 결정할 수 있습니다.
힙 최적화 이론은 동일해야하지만 수정 작업을 삽입 작업으로 대체했습니다. 즉, 가장 짧은 거리 만 효과적 일지라도 노드가 여러 번 반복 될 수 있으며 다음을 통해 필터링됩니다. vis 배열. 도달 할 수없는 정점 INF가 있으면 모든 정점 뒤에 배치됩니다.
그래서힙 최적화를위한 두 가지 종료 조건이 있습니다. 대기열이 비어 있지 않습니다 || vis 배열의 실제 개수가 n 개 미만입니다.。
데이터 범위 :
1≤n, m≤1.5 × 10 5 ,
그림의 측면 길이는 0 이상 10,000 이하입니다.
m과 n은 레벨이며 인접 테이블 스토리지, 힙 최적화를 사용하는 희소 그래프입니다.
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int,int> PII; //first表示距离,second表示节点编号,按距离升序排列,每次选出最小的
priority_queue< PII,vector<PII>,greater<PII> >heap;
const int N=2e5,INF=0x3f3f3f3f;
int h[N],e[N],w[N],ne[N],idx;
int dis[N];
bool vis[N];
int n,m;
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dijkstra(int s)
{
memset(dis,0x3f,sizeof dis);
dis[s]=0;
heap.push({
dis[s],s});
int num=n; n在后面还要用到,不能修改,因此用num替代
while(heap.size() && num) {
//当队列不空时 或者 成功计数小于n次
PII t=heap.top();
heap.pop();
//每次取出距离s最近的结点,然后开始更新距离
int v=t.second,d=t.first; //s->v的最短距离为d
if (vis[v]) continue; //已经更新过,重复更新的情况跳过,否则
vis[v]=true,num--;//还剩下n个点未统计
for (int i=h[v];i!=-1;i=ne[i]) {
int j=e[i];
if (!vis[j] && dis[j]>d+w[i]) {
//同理,if条件可省略
dis[j]=d+w[i];
heap.push({
dis[j],j});
}
}
}
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
int a,b,c;
while (m--) {
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
dijkstra(1);
dis[n]==INF?puts("-1"):printf("%d",dis[n]);
return 0;
}
25 행에서는 시작점 s 만 힙에 넣고 나머지 정점은 넣은 다음 업데이트해야하지만 stl의 힙은 키워드로 업데이트 할 수 없지만 insert 업데이트하는 대신 나머지 점은 그렇지 않습니다. 삽입되고 업데이트가 도착하면 삽입됩니다.
27 행의 while 판정에 대해서는 두 가지 판정 조건 while(heap.size() && num)
이 있는데, 이는 중복 요소의 출현으로 인해 n주기를 종료 할 수 없기 때문입니다 .n주기의 힙에있는 꼭지점이 다음과 같은 지점 일 수 있습니다. 최단 거리가 계산되었습니다.이주기는 유효하지 않으므로 n 개의 유효한주기 후에 만 작동합니다 . 동시에 조건 heap.size()
은 유효한 n 개주기 전에 힙이 null이되는 것을 방지하는 것입니다. 연결되지 않은 그래프가있는 상황을 상상해보십시오. : 정점 n 개, 가장자리가 0 인 경우 두 번째 루프 동안 힙이 비어 있지만 여전히 루프 PII t=heap.top();
가 필요하며 문제가 있으며 TLE가 발생합니다.
업데이트 할 수있는 손으로 쓴 힙인 경우 n 번 반복하면 충분하며 처음에 모든 포인트를 힙에 넣을 수 있습니다.