ダイクストラアルゴリズム
有向加重グラフの単一ソース最短経路は、すべての点から特定の頂点iまでの最短経路を見つけるのに適しています。
適用範囲:すべてのエッジの重みが正の数であるグラフ。そして、ループの存在を許可します。ナイーブダイクストラアルゴリズムとヒープ最適化ダイクストラアルゴリズムの2種類に
分けられます。
ダイクストラアルゴリズムのアイデア:
頂点を2つのカテゴリに分割します:最短経路の頂点がカウントされていますコレクションそして、最短経路の頂点は数えられていません。
貪欲なアイデアを使用して、iに最も近い頂点jが集合sの頂点で見つからないたびに、それがsに追加され、jから頂点iまで到達できる点からの距離が更新されます。最小値を取るので、n回ループすると、N個の頂点がすべてセットsに追加されます。
ナイーブアルゴリズムとヒープ最適化アルゴリズムのアルゴリズムの違いは、iに最も近い頂点jがセットsの頂点の中に見つからないことです。
ナイーブアルゴリズムは、隣接行列ストレージを使用する密グラフに適しており、時間計算量はO(n 2)です。
g[i][j]=x
、これは、距離i-> jがxであることを意味します
ヒープ最適化アルゴリズムは、隣接テーブルストレージを使用するスパースグラフに適しており、時間計算量はO(mlogn)です。
n点数m辺数
分析:ダイクストラアルゴリズムに対するヘビーサイドとセルフループの影響
ループ。
自己ループ:自分から自分への状況。ループ
自体は絶対に自分自身に更新されるのではなく、他の人によって自分自身に更新されるだけです。別のポイントまでの距離が更新されると、自分がセットに追加され、更新されているかどうかに関係なく、最短距離のポイントは無効です。
ループは自己ループの一般的なケースです。ダイクストラの考えによれば、グラフにループがある場合、彼はループを通過しません。ループの最後のノードに到達すると、前のノードがセットに追加されました。、セットに含まれていないケースのみが考慮されるため、考慮されるカテゴリには含まれなくなり、距離は更新されません。
重いエッジは扱いやすいです。2点の間に複数のエッジがある場合は、最短のエッジを使用してください。
タイトル説明
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
2 3 1
1 3 4
サンプル出力:
3
ナイーブダイクストラアルゴリズム
データ範囲は
1≤n≤500、
1≤m≤10 5、
及び図に関与する辺の長さは、10,000を超えません。
分析:
n個の頂点、最大でn×(n-1)個の有向グラフ)<重いエッジと自己ループを考慮しない>、n 2は2.5e5に等しく、mは1e5に等しく、mとn2は1つです密グラフであるレベル。隣接行列ストレージを採用します。
#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;
}
ディスアレイは、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 ]の影響を受けません。
ヒープ最適化ダイクストラアルゴリズム
ヒープ最適化アルゴリズムは、最小のヒープを使用して上記の21 22 23行を最適化し、nサイクルを経て決定するのではなく、毎回最小の距離で頂点を直接フェッチすることです。
ヒープに記録する必要のある情報は、ノードと距離でソートされてから、距離でソートされます。したがってpair<int,int>
、最初の1つは距離を格納し、2番目の1つは頂点番号を格納します。取り出した後、距離を更新する必要があります。更新した距離の後、dis配列を変更する必要がありますが、別の問題があります。ノードがすでにヒープに存在する場合、ヒープを再度変更する必要があります。 2つのキーワード。ヒープの変更stlコンテナのヒープは実現できません。ヒープを手書きすることで実現できますが、より複雑になります。
したがって、変更してヒープに再度挿入することはありません。このアプローチでは、ヒープを書き込む必要はありませんが、ヒープ内の要素は冗長です。頂点が同じで距離が異なる複数の要素が存在する可能性があります。元のヒープ最大でN個の要素が存在する可能性があり(最大でN個の頂点があるため)、現在はさらに存在する可能性がありますが、これは効果がありません。各頂点は、最短距離に応じて1回だけ距離を更新します。頂点がvis配列に基づいているかどうかを判断できます。重複を避けるために、既知の最短パスのセットにはまだ追加されていません。
注意すべきもう1つのポイント:
素朴なアプローチでは、ノードiから他のすべてのノードまでの最短距離は、最大n回ループすることで決定できます。
ヒープ最適化の理論は同じである必要がありますが、変更操作を挿入操作に置き換えました。これは、ノードが数回繰り返される可能性があることを意味しますが、最短距離のみが有効であり、その後、ノードはフィルター処理されます。 vis配列。到達不能な頂点INFがある場合、それはすべての頂点の後ろに配置されます。
など、ヒープ最適化には2つの終了条件があります。キューが空ではありません|| 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のヒープはキーワードで更新できませんが、挿入します。更新する代わりに、残りの点は更新されません。入れられ、アップデートが到着したときに入れられます。
27行目のwhile判定については、2つの判定条件があります。while(heap.size() && num)
これは、冗長要素が出現し、nサイクルを終了できないためです。nサイクルのヒープ内の頂点が、次の点である可能性があります。最短距離が計算されました。このサイクルは無効であるため、n個の有効なサイクルの後でのみ機能します。同時に、heap.size()
有効なnサイクルの前にヒープがヌルにならないようにすることが条件です。接続されていないグラフがある状況を想像してください。 :n個の頂点、エッジが0の場合、ヒープは2番目のループ中に空になりますが、それでもループする必要PII t=heap.top();
があり、問題が発生し、TLEが発生します。
更新可能な手書きヒープの場合は、n回ループするだけで十分であり、最初にすべてのポイントをヒープに入れることができます。