この問題を解決するには、DFS + BFSを使用することができますが、私は唯一のダイクストラのアルゴリズムを知っているので、私は、ありません。
この問題を解決するには、ダイクストラ法の核となるアイデアを理解する必要があり、私はこのようにそれを理解する:
Iたとえば、グラフ:
このグラフのように、1から6までの最短経路を見つけます。もちろん、この点が2次元配列に格納されることはめったにないため、
ダイクストラは考えました。最初にdis [を使用します。 ] 1が到達できるすべてのポイントを格納する配列。次に、他のポイントをINFとして設定します(通常はINFコマンドで0x3f3f3f3fで十分です)。
次に、既存の到達可能ポイントを転送ステーションとして使用して、他の到達可能ポイントを見つけることができます。次に、毎回dis array値を更新します。
例:2から開始し、2が到達できるすべてのポイントを列挙します(もちろん、1が見つかったため、配列book [1] = 1を使用してマークを付ける必要があります。 ;)になった
後、2とすることができる達しINF(わずか4および3があり、その後、1から4までの距離を比較する)と1から2まで4(1 + 8)までの距離が少ないINFよりので、DIS。 [4]は9に更新され
、ヒープ3についても同じことが言えます。その後、繰り返し歩くことができなかったため、以前はトランジットポイントであったたびにマークが付けられました。
だから問題は、このアイデアについてどう考えるかということです。
まず、最初のポイントを除くすべてのポイントが転送ステーションとして使用されることを明確にする必要があります。したがって、大きなforループは1〜n-1ポイントです。注:ここでの大きなポイントは、各ポイントを示すためにのみ使用されます。転送ステーションとして使用する必要があります。内部のコンテンツとの論理的な接続は必要ありません。
したがって、最も単純なダイクストラアルゴリズムが出てきます(このアルゴリズムは配列が大きすぎる場合を考慮しないため)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
int Map[1000][1000],n,m;//Map用来记录a点到b点的路径值,n表示有多少个点,m表示给出多少条边
int dis[1000],book[1000];//dis是用来表示 start点到其他点的距离,book用来标记 循环到这个点的是否被标记过
void dijkstra(int start){//最简单的dijkstra算法(采用矩阵形式)
//首先求出这个点到其他点的距离用一个for
int Min=INF;
for(int i=1;i<=n;i++){//初始化从start这点开始的所有能与start点可达的点的距离
dis[i]=Map[start][i];//注意这里dis和Map只是一个逻辑关系相联系
}
//这里需要枚举n-1个点,因为开始点不需要算
//所以用一个大的for来循环
book[start]=1;//标记开始点已经作为中转站过了
for(int i=1;i<=n-1;i++){
//找出第一个到start的最短的点
//并且标记已经选过了
Min=INF;
int u;//记录到start最短的点
for(int j=1;j<=n;j++){
if(book[j]==0&&dis[j]<Min){//去搜寻能与start有直接边的点的最小距离//这里最好头脑里面想着无向图
Min=dis[j];
u=j;
}
}
book[u]=1;//表示u这个点已经作为中转站了
//然后找到以u点为中转站的其他点到start的距离和经过u这个点的距离,并比较大小
for(int e=1;e<=n;e++){//这里e是end的简写,方便理解表示以u作为中转站所能到达的另一个点
if(Map[u][e]<INF){
if(dis[e]>dis[u]+Map[u][e]){//这就是上面分析的比较
dis[e]=dis[u]+Map[u][e];
}
}
}
}
}
void init(int n){
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++){
if(i==j) Map[i][j]=0;//自己到自己就是0
else Map[i][j]=INF;//其他的设为INF
}
}
}
void read(int m)//这里用来输入,因为函数分开写更好理解它的功能
{
int a,b,c;
for(int i=1;i<=m;i++){
cin>>a>>b>>c;
Map[a][b]=c;//令为无向图
Map[b][a]=c;
}
}
int main(){
cin>>n>>m;
init(n);//这里虽然n,m为全局变量,但是这样写参数传递更加好理解;
read(m);
dijkstra(1);//从第一个点开始(当然这里也可以从别的点开始)
for(int i=2;i<=n;i++){//输出到其他点的最短距离
cout<<"1到"<<i<<"的最短距离为:"<<dis[i]<<"\n";
}
cout<<endl;
return 0;
}
上の図によると、結果は次のようになります。
上記は最も単純なダイクストラアルゴリズムであり、AC問題には十分とは言えません。それでは、どのように最適化するのでしょうか。
ここでは優先度付きキュー+構造+隣接リスト+ダイクストラを使用します。考えるのは非常に複雑ですが、本当に理解していると非常に便利です。この問題のACコードは以下のとおりですが、ダイクストラ+ヒープだと思います。最適化を使用すると、アルゴリズムの効率が確実に向上します。
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
ll n,m;
struct End_Dis{
ll to,d,c;//to表示到的点,d表示到这个兵兵(也可以理解为距离问题),c用来记录是不是有这个兵兵在这条路上
End_Dis(ll a,ll b,ll cc=0):to(a),d(b),c(cc){}
bool operator < (const End_Dis tt) const{
return d>tt.d;//这个比较特殊,因为优先队列是由大到小,所以我写了一个由大到小的,然后这样会让优先队列由小到大排列(特别的地方)
}
};
vector<End_Dis> a[100000*2];
ll book1[200010];//用来标记这个点扫过没有
ll Dijkstra(ll start){
ll dis[200010];
for(ll i=1;i<=n;i++) dis[i]=INF;//先初始化为INF
book1[1]=0;//表示这个点走过了
dis[1]=0;//自己到自己距离为0;
priority_queue<End_Dis> q;//建立一个优先队列
q.push(End_Dis(1,0));//把第一个点放进去
while(!q.empty()) {//循环为非空
End_Dis t=q.top();
q.pop();
if(book1[t.to]) continue;//表示这个点使用过没有
for(ll i=0;i<a[t.to].size();i++){//以这个点位中转站,枚举其他点更新1----其他点的最短距离
End_Dis x=a[t.to][i];//取出对象
if((t.c!=x.d)+t.d<dis[x.to]) {//1到中转站h的距离+前面是否出现过的值是否小于 1---x的距离
dis[x.to]=(t.c!=x.d)+t.d;
q.push(End_Dis(x.to,dis[x.to],x.d));//找到了之后然后让中转站后面的点进队
}
}
}
return dis[n];
}
void read(ll m){
ll aa,bb,cc;
for(ll i=1;i<=m;i++){
scanf("%lld %lld %lld",&aa,&bb,&cc);
a[aa].push_back(End_Dis(bb,cc));
a[bb].push_back(End_Dis(aa,cc));
}
}
void init(ll n){
for(ll i=1;i<=n;i++){
a[i].clear();
}
}
int main(){
while(~scanf("%lld %lld",&n,&m)){
init(n);
read(m);
int ans=Dijkstra(1);
if(ans==INF)puts("-1");
else printf("%lld\n",ans);
}
return 0;
}
もちろん、この質問は私に悟りを与えました。私はダイクストラアルゴリズムを理解していましたが、それは非常に浅いです。このヒープ最適化は、前に示したグラフの問題を解決するために使用できると思い
ます。コードを変更するだけです。
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
ll n,m;
struct End_Dis{
ll to,d;//to表示到的点,d表示为前一个点到to这个点的距离
End_Dis(ll a,ll b):to(a),d(b){}///这里构造函数有便于加点
bool operator < (const End_Dis tt) const{//这里在使用优先队列的时候很有用
return d>tt.d;//这个比较特殊,因为优先队列是由大到小,所以我写了一个由大到小的,然后这样会让优先队列由小到大排列(特别的地方)
}
};
vector<End_Dis> a[100000*2];//用邻接表来存储一个点能到的其他点比如:a[aa][i].to表示aa这个点到to这个点
ll book1[200010];//用来标记这个点扫过没有//用来标记这个点是否作为过中转站
ll Dijkstra(ll start){
ll dis[200010];//表示距离
for(ll i=1;i<=n;i++) dis[i]=INF;//先初始化为INF
book1[start]=0;//表示这个点走过了//这里需要注意了 因为这里是和最简单的Dijkstra算法不同点
dis[start]=0;//自己到自己距离为0;
priority_queue<End_Dis> q;//建立一个优先队列
q.push(End_Dis(start,0));//把第一个点放进去
while(!q.empty()) {//循环为非空
End_Dis t=q.top();
q.pop();
if(book1[t.to]) continue;//表示这个点使用过没有
for(ll i=0;i<a[t.to].size();i++){//以这个点位中转站,枚举其他点更新1----其他点的最短距离
End_Dis x=a[t.to][i];//取出t.to这个点所能到达的点的对象
if(dis[x.to]>dis[t.to]+x.d){//start到x.to点的距离 和 start到t.to的距离与t.to到x.d的和的比较
//更新这个点的距离
dis[x.to]=dis[t.to]+x.d;
q.push(End_Dis(x.to,dis[x.to]));//把这个点加入优先队列中
}
}
}
return dis[n];
}
void read(ll m){//因为为无向图
ll aa,bb,cc;
for(ll i=1;i<=m;i++){
scanf("%lld %lld %lld",&aa,&bb,&cc);
a[aa].push_back(End_Dis(bb,cc));
a[bb].push_back(End_Dis(aa,cc));
}
}
void init(ll n){//每次清空
for(ll i=1;i<=n;i++){
a[i].clear();
}
}
int main(){
while(~scanf("%lld %lld",&n,&m)){
init(n);
read(m);
int ans=Dijkstra(1);
if(ans==INF)puts("-1");//如果不能到n点那么久输出-1
else printf("到n点的最短路径为: %lld\n",ans);
}
return 0;
}
これが最良のダイクストラアルゴリズムバージョンだと思います(ここでは構造体+優先度キュー+巧妙なダイクストラアルゴリズムが使用されているため、テンプレートを直接持ってきて呼び出し、最終的に分類することができます(保存パスの問題があるようです(を参照)後で私)それを追加))))