求最小生成樹和最短路徑的總結
1.求最小生成樹有兩種方法:
①克魯斯卡爾演算法:這個演算法是以邊為單位(包括邊的所有的資訊:兩個端點+權值)進行儲存的,然後將邊按照權值的從小到大的順序進行排序,然後將第一條邊連線起來,第二條邊連線起來,就這樣一直迴圈,直到所有的邊都被連線起來為止,在這期間,你需要判斷那兩個點是否已經連線過了,如果連線過了,就不需要連線了(連線過的意思是:只要從一個端點能間接地到達另一個端點就算是連線過),如果沒有連線過,就將其連線上!因為這是按照權值從小到達的順序連線的,所以先連線的權值肯定小,最終求出來一定是最小生成樹!(主要以並查集為工具)細節:1.克魯斯卡爾演算法是用結構體來儲存邊的資訊的!而普里母演算法是用鄰接矩陣來儲存邊的資訊的!2.克魯斯卡爾是以邊為單位進行增邊,而普里母是以點為單位進行增邊!
程式碼實現:
#include <stdio.h> #include <queue> #include <algorithm> using namespace std; int n,m; int pre[110]; struct node { int u,v,w; }p[10000]; int cmp(node a,node b) { return a.w<b.w; } void init() { for(int i=1;i<=m;i++) pre[i]=i; } int find(int x) { int r=x; while(r!=pre[r]) { r=pre[r]; } int i,j; i=x; while(i!=r) { j=pre[i]; pre[i]=r; i=j; } return r; } int join(int x,int y) { int fx=find(x); int fy=find(y); if(fx!=fy) { pre[fx]=fy; return 1; } return 0; } int main() { int sum,t; while(scanf("%d%d",&n,&m)&&n) { sum=0;t=0; init(); for(int i=0;i<n;i++) { scanf("%d%d%d",&p[i].u,&p[i].v,&p[i].w); } sort(p,p+n,cmp); for(int i=0;i<n;i++) { if(join(p[i].u,p[i].v)) { sum+=p[i].w; } } printf("%d\n",sum); } return 0; }
②普里母演算法:這個演算法是通過鄰接矩陣來儲存邊的資訊的(第一個下標代表左端點,第二個下標代表右端點,儲存的值代表這條邊對應的權值),然後首先以某一個點為起點(一般以1為起點),先將這個點加進集合裡面,然後將其他的點到這個集合的距離賦值給dis陣列(dis陣列用來儲存所有的點到集合的距離的最小的值,進集合的點的dis值為這個點與集合裡面的其他的點的最短的距離,也就是這個最小生成樹的一個枝條的長度),然後通過迴圈找集合外面的點到集合裡面的點的最小值,然後儲存下標將其放進集合裡面(也就是vis[k]=1),然後更新其他的點到集合的點(也就是對比之前的最短的距離與到新近的這個點的距離進行比較,找最小的距離儲存到dis數組裡面!),然後重新找集合外面的點到集合的距離的最小值的點,將其放進集合,然後繼續更新最小的距離,直到所有的點都放進集合為止(也就是讓迴圈n-1次停止迴圈)!
程式碼實現:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
int n,m;
int d[105][105];
int dis[105];
int vis[105];
void init()
{
int a,b,c;
memset(vis,0,sizeof(vis));
memset(d,INF,sizeof(d));
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
d[a][b]=d[b][a]=c;
}
vis[1]=1;
for(int i=1;i<=n;i++)
{
dis[i]=d[1][i];
}
dis[1]=0;//這個1號點一定要初始化為0,因為剛開始賦值為INF,然後直接放進集合內,所以他到集合的距離為0!
}//這一點必須記住!
void prim()
{
int min,k,t=0;
for(int i=1;i<n;i++)
{
min=INF;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&(dis[j]<min))
{
min=dis[j];
k=j;
}
}
if(min==INF)//當所有的點到集合的距離都為INF,則說明這個點與集合之間沒有路,直接跳出,輸出?
{
break;
}
vis[k]=1;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&(dis[j]>d[k][j]))
{
dis[j]=d[k][j];
}
}
}
int sum=0;
for(int i=1;i<=n;i++)
sum+=dis[i];
printf("%d\n",sum);
}
int main()
{
while(scanf("%d%d",&m,&n)&&m)
{
init();
prim();
}
return 0;
}
2.求最短路徑的三種方法:
①迪傑斯特拉演算法:(這個也是用鄰接矩陣儲存邊的長度,以點為單位進行更新)這個演算法的思想與求最小生成樹的演算法的思想幾乎一樣,就是這個在求最短路徑的時候將起點固定了所以你必須是以給定的點為起點,然後在更新的時候,你不是將到集合的距離更新,而是將到起點的距離進行更新,但是因為你的dis陣列中儲存的是到起點的距離,所以你只需要將每次的dis數組裡面的值與進集合裡面的點的dis值加上進集合裡面的點與外面的點之間的距離進行比較,然後取較小的值,就是我們更新的值,每次都將dis的值的最小的值放進集合取更新其他的值,就這樣迴圈n-1次直到將所有的點都放進集合為止!用這種方法計算最終dis裡面的值是所有的點到起點的距離!
【不適用計算有負權邊的圖,時間複雜度低】
注意:
1.有重邊的要特殊處理(map[i][j]>c;map[i][j]=c);
2.map初始化時要記i==j時map[i][j]=0;否則map[i][j]=INF;
模板:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
int d[205][205];
int dis[205];
int vis[205];
int n,m,x,y;
void init()
{
int a,b,c;
memset(d,INF,sizeof(d));
memset(vis,0,sizeof(vis));
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&c);
if(d[a][b]>c)//這一點一定要注意!萬一遇到多條a到b的路的話,要取較小的值!
{
d[a][b]=d[b][a]=c;
}
}
scanf("%d%d",&x,&y);
for(int i=0;i<n;i++)
{
dis[i]=d[x][i];
}
dis[x]=0;
vis[x]=1;
}
void dijkstra()
{
int min,k;
for(int i=0;i<n;i++)
{
min=INF;
for(int j=0;j<n;j++)
{
if(!vis[j]&&dis[j]<min)
{
min=dis[j];
k=j;
}
}
if(min==INF)
break;
vis[k]=1;
for(int j=0;j<n;j++)
{
if(!vis[j]&&dis[j]>dis[k]+d[k][j])
{
dis[j]=dis[k]+d[k][j];
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
dijkstra();
if(dis[y]==INF)
printf("-1\n");
else
printf("%d\n",dis[y]);
}
return 0;
}
②SPFA演算法:(建立鄰接表,以點為單位進行加邊)首先是給定起點,將起點放進佇列,然後起點出佇列,然後在起點的周圍找與起點相連的點,放進佇列裡面(因為剛開始的時候除了起點的dis值外其他的點的dis值都是INF,所以肯定每個與起點相連的點到起點的值一定大於起點的dis值加上他們之間的邊長),然後再將進佇列的點出佇列然後在他們周圍找經過它們距離起點的距離比不經過它們距離起點的距離看看哪個大,如果經過它們的距離小,則將最小的距離進行更新,並且將與它們相連的點放進佇列,然後繼續出佇列更新與它們相連的點,直至所有的點都出佇列為止!(也就是進佇列的點的周圍的點都滿足到原點的距離是最小的了,然後就沒有點再進隊列了,只有出佇列的點,最終進佇列的點都出來了,所有的點的dis值也就是到原點的最小的距離了!)
【能夠計算有負權邊的圖,時間複雜度不確定,能夠判斷是否有負環】
程式碼(模板):
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#define INF 0x3f3f3f3f//這裡面不能用0xfffffff,因為用這個不能用memset來賦值!
using namespace std;
int n,m;
struct Edge
{
int from, to, val, next;
}edge[10005];
int edgenum;
int dis[105];
int vis[105];
int head[10005];
void init()
{
edgenum=0;
memset(head,-1,sizeof(head));
}
void addEdge(int u,int v,int w)//建立鄰接表!
{
Edge E={u,v,w,head[u]};//u為起點,v為終點,w為權值,head為同起點的上一條邊的編號
edge[edgenum]=E;
head[u]=edgenum++;
}
void getmap()
{
int a,b,c;
for(int i=0;i<m;i++)//以一個點為起點建立鄰接表!
{
scanf("%d%d%d",&a,&b,&c);
addEdge(a,b,c);
addEdge(b,a,c);
}
memset(vis,0,sizeof(vis));
memset(dis,INF,sizeof(dis));
}
void SPFA()
{
queue<int>q;
q.push(1);
dis[1]=0;
vis[1]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next)//鄰接表!(以一個點為中心來遍歷相鄰的點!)
{
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].val)//更新
{
dis[v]=dis[u]+edge[i].val;
if(vis[v]==0)
{
vis[v]=1;//賦值號!
q.push(v);//因為有可能更新的值對其他的點的最短的距離會造成影響,所以要將其放進佇列,然後以這個點為起點再將其他的點的最短距離進行更新!
}
}
}
}
printf("%d\n",dis[n]);
}
int main()
{
while(scanf("%d%d",&n,&m)&&(n||m))
{
init();
getmap();
SPFA();
}
}
③弗洛伊德演算法:是dp演算法的一種,它是利用其他的點作為斷點來更新任意兩點之間的距離,最終求的就是任意兩點之間的距離,邊的權值可正可負,但是時間複雜度較高,只能處理資料量將少的題,一般最好不用!
優缺點:
Floyd演算法適用於APSP(All Pairs Shortest Paths,多源最短路徑),是一種動態規劃演算法,稠密圖效果最佳,邊權可正可負。此演算法簡單有效,由於三重迴圈結構緊湊,對於稠密圖,效率要高於執行|V|次Dijkstra演算法,也要高於執行V次SPFA演算法。 程式碼:#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
int n,m;
int map[205][205];
int x,y;
void init()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(i==j)
{
map[i][j]=0;
}
else
{
map[i][j]=INF;
}
}
}
int a,b,c;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
if(map[a][b]>c)
{
map[a][b]=map[b][a]=c;
}
}
scanf("%d%d",&x,&y);
}
void floyd()
{
for(int k=0;k<n;k++)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(map[i][j]>map[i][k]+map[k][j])
{
map[i][j]=map[i][k]+map[k][j];
}
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
floyd();
if(map[x][y]==INF)
printf("-1\n");
else
printf("%d\n",map[x][y]);
}
return 0;
}