【前向星】
不要把前向星想成什麼高深莫測的東西,它其實就是一種邊集數組。
前向星特別適合用來優化SPFA、DFS、BFS。
前向星構建過程如下:
先把圖中每條邊的起點按照從小到大的順序排序,如果起點一樣,那麼把終點按照從小到大的順序排序。此過程目的是確立圖中邊的位序。
構建數組head[i],記錄圖中以點i爲起點的所有出邊的第一個位序。
構建數組len[i],記錄圖中以點i爲起點的出邊數。
針對上圖,假設我們輸入邊的順序爲:
1 2
2 3
3 4
1 3
4 1
1 5
4 5
那麼依據上述規則排完序後就得到:
編號: 1 2 3 4 5 6 7
起點u: 1 1 1 2 3 4 4
終點v: 2 3 5 3 4 1 5
最終得到head[i]、len[i]值如下:
head[1]=1 len[1]=3
head[2]=4 len[2]=1
head[3]=5 len[3]=1
head[4]=6 len[4]=2
顯然,利用前向星會使用到排序操作,所以即便是使用效率較高的快速排序,前向星算法的時間複雜度也至少爲O(nlog(n))。
【鏈式前向星】
如果說鄰接表是不好寫但效率好,鄰接矩陣是好寫但效率低的話,前向星就是一個相對中庸的數據結構。前向星固然好寫,但效率並不高。而在優化爲鏈式前向星後,效率也得到了較大的提升。雖然說,世界上對鏈式前向星的使用並不是很廣泛,但在不願意寫複雜的鄰接表的情況下,鏈式前向星也是一個很優秀的數據結構。
——摘自《百度百科》
鏈式前向星其實就是靜態建立的鄰接表,時間效率爲O(m),空間效率爲O(m),遍歷效率也爲O(m)。
建立鏈式前向星,不需要對結點序號按大小進行排序。
以下面數據爲例,第一行表示5個頂點7條邊,接下來是7條邊的起點、終點和權值。
5 7
1 2 1
2 3 2
3 4 3
1 3 4
4 1 5
1 5 6
4 5 7
鏈式前向星存儲的是以1...n爲起點的邊的集合。在實現算法時,按邊的輸入順序從0開始對邊進行編號。這些編號,將幫助界定下文中陳述edge[i].next時的“上一條邊”,及陳述head[i]時的“最後一條邊”。
編碼之前約定兩個變量的含義:
edge[i].next,表示與邊i起點相同的上一條邊的編號。(由於慣例,此處next不改爲pre,雖有可能造成誤解)
head[i]數組,表示與點i起點相同的最後一條邊的編號。(只是方便記憶才這樣陳述。規範說法是以i爲起始點。)
head數組一般初始化爲-1。
加邊函數如下:
void add_edge(int u, int v, int w){ //加邊函數。u爲起點,v爲終點,w爲邊權
edge[cnt].to=v; //終點
edge[cnt].w=w; //權值
edge[cnt].next=head[u]; //以u爲起點的的最後一條邊的編號
head[u]=cnt++; //更新以u爲起點的上一條邊的編號
}
依據next,head數組的約定,並按邊的輸入順序從0開始對邊進行編號,然後按照上面的數據就可以寫出下面的過程:
對於1 2 1這條邊:edge[0].to=2; edge[0].next=-1; head[1]=0;
對於2 3 2這條邊:edge[1].to=3; edge[1].next=-1; head[2]=1;
對於3 4 3這條邊:edge[2].to=4; edge[2],next=-1; head[3]=2;
對於1 3 4這條邊:edge[3].to=3; edge[3].next=0; head[1]=3;
對於4 1 5這條邊:edge[4].to=1; edge[4].next=-1; head[4]=4;
對於1 5 6這條邊:edge[5].to=5; edge[5].next=3; head[1]=5;
對於4 5 7這條邊:edge[6].to=5; edge[6].next=4; head[4]=6;
遍歷函數如下:
for(int i=1; i<=n; i++){ //n個起點
cout<<i<<endl;
for(int j=head[i]; j!=-1; j=edge[j].next){ //遍歷以i爲起點的所有邊
cout<<i<<" "<<edge[j].to<<" "<<edge[j].w<<endl;
}
cout<<endl;
}
第一層for循環:依次遍歷以1...n爲起點的邊的集合。
第二層for循環:遍歷以i爲起點的所有邊,j首先等於head[i]。注意:head[i]表示與點i起點相同的最後一條邊的編號。
然後,通過edge[j].next來找與邊j起點相同的上一條邊的編號,終止條件爲edge[j].next=-1。
【鏈式前向星代碼】
完整代碼爲:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int head[maxn]; //head[i],表示與點i起點相同的最後一條邊的編號。
int n,m;
int cnt=0; //邊的編號,從0開始
struct node {
int to,w,next;//終點,邊權,與邊i起點相同的上一條邊的編號
} edge[maxn];
void add_edge(int u,int v,int w) { //加邊,u起點,v終點,w邊權
edge[cnt].to=v; //終點
edge[cnt].w=w; //權值
edge[cnt].next=head[u];//以u爲起點的的最後一條邊的編號
head[u]=cnt++;//更新以u爲起點的最後一條邊的編號
}
int main() {
memset(head,-1,sizeof(head));
cin>>n>>m;
int u,v,w;
for(int i=1; i<=m; i++) {
cin>>u>>v>>w;
add_edge(u,v,w);
/*
加雙向邊
add_edge(u, v, w);
add_edge(v, u, w);
*/
}
for(int i=1; i<=n; i++) { //n個起點
cout<<i<<endl;
for (int j=head[i]; j!=-1; j=edge[j].next) { //遍歷以i爲起點的邊
cout<<i<<" "<<edge[j].to<<" "<<edge[j].w<<endl;
}
cout<<endl;
}
return 0;
}
【測試樣例】
in:
5 7
1 2 1
2 3 2
3 4 3
1 3 4
4 1 5
1 5 6
4 5 7
out:
1 //以1爲起點的邊的集合
1 5 6
1 3 4
1 2 1
2 //以2爲起點的邊的集合
2 3 2
3 //以3爲起點的邊的集合
3 4 3
4 //以4爲起點的邊的集合
4 5 7
4 1 5
5 //以5爲起點的邊不存在
【參考文獻】
https://blog.csdn.net/sugarbliss/article/details/86495945
https://blog.csdn.net/acdreamers/article/details/16902023
https://blog.csdn.net/CSL201816080304/article/details/89198227
https://blog.csdn.net/LOOKQAQ/article/details/81304637
https://www.cnblogs.com/crazyacking/p/3761686.html