链式前向星

【前向星】
不要把前向星想成什么高深莫测的东西,它其实就是一种边集数组。
前向星特别适合用来优化SPFA、DFS、BFS。

前向星构建过程如下:
先把图中每条边的起点按照从小到大的顺序排序,如果起点一样,那么把终点按照从小到大的顺序排序。此过程目的是确立图中边的位序。
构建数组head[i],记录图中以点i为起点的所有出边的第一个位序。
构建数组len[i],记录图中以点i为起点的出边数。

链式前向星_第1张图片
 

针对上图,假设我们输入边的顺序为:

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为起点的上一条边的编号

}

链式前向星_第2张图片


依据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<

        for(int j=head[i]; j!=-1; j=edge[j].next){ //遍历以i为起点的所有边        

            cout<

        }

        cout<

    }

第一层for循环:依次遍历以1...n为起点的边的集合。
第二层for循环:遍历以i为起点的所有边,j首先等于head[i]。注意:head[i]表示与点i起点相同的最后一条边的编号。
然后,通过edge[j].next来找与边j起点相同的上一条边的编号,终止条件为edge[j].next=-1。

 

 

【链式前向星代码】
完整代码为:

#include
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<


【测试样例】
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
 


 

你可能感兴趣的:(信息学竞赛)