鏈式前向星

【前向星】
不要把前向星想成什麼高深莫測的東西,它其實就是一種邊集數組。
前向星特別適合用來優化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
 


 

發佈了58 篇原創文章 · 獲贊 12 · 訪問量 9710
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章