講了半天好像也許maybe聽懂了一點,先寫下來233
先整理整理怎么存(開始繞)
最簡單的是鄰接矩陣存,但是開到10000*10000就MLE了,所以我們用鏈式前向星存(據說是叫這個名字吧)
這是個什么鬼玩意呢?
我們在記錄時,以輸入的順序記錄。
我們記錄一條邊,就記下它的終點(to),權值(就是邊長)(dis),以及和這條邊的起點相同,編號稍微小一點的邊的編號(next)(開始繞)
這里我們記錄當前每個點的所有出邊(就是起點是這個點的邊)中編號最大的那一條邊(因為上面的next是編號稍微小的邊)
當然也可以依據自己的習慣存儲邊
先上段代碼
int head[nmax],n,m,s;//head[i] 是 以 點 i 為 起 點 , 所 有 出 邊 中 編 號 最 大 的 一 個 priority_queue<pair<int,int> > q; void add(int fr,int _to,int _dis) { cnt++; eage[cnt].to=_to; eage[cnt].dis=_dis; eage[cnt].next=head[fr];//fr 為 from 的 簡 寫 , 這 里 的 以 點 i 為 起 點 的 邊 多 了 一 條, //所 以 上 一 個 以 點 i 為 起 點 的 編 號 最 大 的 邊 就 是 這 里 的 以 i 為 起 點 編 號 最 大 的 邊 的 上 一 條 邊 head[fr]=cnt; //更 新 head[i] }Edge [50001]; const int inf=2147483647; int main() { scanf("%d%d%d",&n,&m,&o_node); dis[o_node]=0; for(int i=1;i<=m;i++) {int from,to,dis; cin>>from>>to>>dis; add(from,to,dis); }
這一坨是存圖
拿張圖舉個例子
假設我們輸入邊的數據如下(三個數n,m,s,n為起點,m為終點,s為邊長)
1 2 2
2 3 2
1 3 5
2 4 1
3 4 2
1 4 4
那代碼中的存儲如下
Edge[1].to=2,Edge[1].dis=2,Edge[1].next=0,head[1]=1(這里指沒有上一條邊),head[1]=1(這里head[i]記錄的是以i為起點,當前最大編號出邊的編號)
Edge[2].to=3,Edge[2].dis=2,Edge[2].next=0,head[2]=2
Edge[3].to=3,Edge[3].dis=5,Edge[3].next=1,head[1]=3
.....................................
講完存圖,再來說這個算法是怎么實現的
要求最短路徑,這里有點類似貪心。
首先選擇一個距離起點最近的直達點b,記錄當前點與b的距離,再由b進行相同的擴展,來更新起點與其它點的距離
這樣更新了一圈后就是最短距離,
再舉個栗子
沒錯還是剛才那張圖,這里標出了每條邊的權值
按照dijkstra算法,我們首先找到距離①最近的直達點②,由②更新出①到④的最短路為3,①到③的最短路為4,
那么程序怎么實現呢?
看注釋吧
(代碼from gh,注釋自己加的)
#include <iostream> #include <cstdio> #include <queue> using namespace std; const int INF = 2147483647; struct edge { int to, dis_, next; } Edge[500001]; struct node { int to, dis; inline friend bool operator<(const node &a, const node &b) { return a.dis < b.dis;//構造函數,將優先隊列按照權值從小到大排序 } }; int head[500001], dis[10001]; bool vst[10001]; int nodenum, edgenum, origin_node, cnt = 1; priority_queue<node> q;//優先隊列 inline void add_edge(int from, int to, int value) { Edge[cnt].to = to; Edge[cnt].dis_ = value; Edge[cnt].next = head[from]; head[from] = cnt++; } inline void dijkstra() { for (register int i = 1; i < origin_node; i++) { dis[i] = INF;//全部初始化為一個很大的數 } dis[origin_node]=0; for (register int i = origin_node + 1; i <= nodenum; i++) { dis[i] = INF; } q.push((node){origin_node, 0}); while (!q.empty())//隊不空(這里是當廣搜來做的) { int x = q.top().to; q.pop(); if (vst[x])//如果訪問過,就跳過 continue; vst[x] = 1; for (register int i = head[x]; i; i = Edge[i].next)//從以x為起點的最后一條邊開始,一直遍歷完這個點的所有邊 { dis[Edge[i].to] = min(dis[Edge[i].to], dis[x] + Edge[i].dis_);//比較原來的大小和以x點為中轉后的大小(取小的) q.push((node){Edge[i].to, -dis[Edge[i].to]});//入隊 } } } template <typename T_> inline T_ getnum() { T_ res = 0; bool flag = false; char ch = getchar(); while (!isdigit(ch)) { flag = flag ? flag : ch == '-'; ch = getchar(); } while (isdigit(ch)) { res = (res << 3) + (res << 1) + ch - '0'; ch = getchar(); } return flag?-res:res; } template<typename T_> inline void putnum(T_ num) { if (num<0) { putchar('-'); num=-num; } if (num>9)putnum(num/10); putchar('0'+num%10); } int main() { nodenum = getnum<int>(), dgenum = getnum<int>(),origin_node = getnum<int>(); for (register int i = 1; i <= edgenum; i++) { register int f, t, v; f = getnum<int>(), t = getnum<int>(), v = getnum<int>(); add_edge(f, t, v); } dijkstra(); for (register int i=1;i<=nodenum;putchar(' '),i++) { putnum<int>(dis[i]); } return 0; }
順便附上一道dijkstra的題
這個好像就是個模板哈
(代碼from題解)
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<string> #include<cstdlib> #include<queue> #include<set> #include<vector> #define INF 0x3f3f3f3f #define PI acos(-1.0) #define N 3001 #define MOD 123 #define E 1e-6 using namespace std; struct node{ int pre; int next; int w; }a[N*10]; int n,m; int cnt; int head[N],vis[N],f[N]; void add(int x,int y,int w) { cnt++; a[cnt].pre=y; a[cnt].next=head[x]; a[cnt].w=w; head[x]=cnt; cnt++; a[cnt].pre=x; a[cnt].next=head[y]; a[cnt].w=w; head[y]=cnt; }//存圖 int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int x,y,w; cin>>x>>y>>w; add(x,y,w); } memset(f,INF,sizeof(f)); f[1]=0; vis[1]=1; int x=head[1];//手動模擬第一次出隊 while(x!=0) { int y=a[x].pre; if(f[y]>a[x].w) f[y]=a[x].w; x=a[x].next; } int cnt=0; while(cnt<n)//遍歷所有的點 { cnt++; int k; int minn=INF; for(int i=1;i<=n;i++) if(vis[i]==0&&f[i]<minn) { minn=f[i]; k=i; }//先把能賦值的距離賦值上 vis[k]=1; int x=head[k];//手動模擬for循環 while(x!=0)//這里木有隊列,所以要while循環一次處理完 { int y=a[x].pre; int w=a[x].w; if(vis[y]==0&&f[y]>f[k]+w) f[y]=f[k]+w; x=a[x].next; } } if(f[n]==INF) cout<<"-1"<<endl; else cout<<f[n]<<endl; return 0; }
堆優化
我們上面說到dij是先挑距離起點最近的一個點b搞,然后再找距離b最近的點搞,那么每次判斷距離就有點麻煩。我們換成每次挑距離起點最近的點搞,這樣我們可以用堆(priority_queue)來維護距離起點最近的那個點,時間復雜度O(nmlogn)
代碼:
#include<bits/stdc++.h> #define pa pair<int,int> using namespace std; inline int read() { char ch=getchar(); int x=0;bool f=0; while(ch<'0'||ch>'9') { if(ch=='-')f=1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); } return f?-x:x; } int n,m,dis[100009],cnt,head[100009],s; struct Ed{ int to,dis,nxt; }edge[200009]; inline void add(int fr,int to,int dis) { cnt++; edge[cnt].to=to; edge[cnt].dis=dis; edge[cnt].nxt=head[fr]; head[fr]=cnt; } priority_queue<pa,vector<pa>,greater<pa> > q;//大根堆轉小根堆 bool vis[100009]; inline void dij(int s) { for(int i=1;i<=n;i++) dis[i]=2147483645; dis[s]=0; q.push(make_pair(0,s)); while(!q.empty()) { int now=q.top().second; q.pop(); if(vis[now])continue; vis[now]=1; for(int e=head[now];e;e=edge[e].nxt) { int v=edge[e].to; if(dis[now]+edge[e].dis<dis[v]) { dis[v]=dis[now]+edge[e].dis; q.push(make_pair(dis[v],v)); } } } } int main() { n=read();m=read();s=read(); for(int i=1;i<=m;i++) { int u=read(),v=read(),w=read(); add(u,v,w); } dij(s); for(int i=1;i<=n;i++) printf("%d ",dis[i]); }