zoukankan      html  css  js  c++  java
  • 关于路径存储的常见优化——前向星与链式前向星

    关于路径存储的常见优化——前向星与链式前向星

    我们在做搜索和动规题目时,一般用到的是邻接矩阵或者邻接表
    由于邻接矩阵占用空间过大邻接表需要调用vector过于繁杂

    所以在图论学习时,我们需要引入一类新的存储方式:前向星

    前向星

    前向星是一种存储有关边的数据的存储方式
    一般储存的是:第 i 条边的起点,第 i 边的终点,第 i 边的边权
    在这里插入图片描述

    struct node{
    	int from;//某条边的起点
    	int to;//某条边的终点 
    	int l;//某条边的边权 
    }edge[1010];
    

    然后我们进行读入数据
    前向星仍会会有一些不足
    比如,需要在所有边都读入完毕的情况下对所有边进行一次排序

    bool cmp(node x,node y){
    	return x.form<y.form;
    }
    int main(){
    	sort(edge,edge+m,cmp)//排序
    return 0;
    }
    

    为什么要进行排序呢?为了方便我们在后面对起点进行操作
    所以这是一种用时间换空间的做法

    比如我们现在举个例子:
    在这里插入图片描述
    如图,数据输入顺序如下

    1 2 2
    3 4 1
    2 3 3
    1 4 9

    我们将数据存入前向星得到

    i from to l
    1 1 2 3
    2 3 4 3
    3 2 3 3
    4 1 4 9

    然后排序后得到

    i from to l
    1 1 2 3
    2 1 4 9
    3 2 3 3
    4 3 4 3

    利用前向星会有排序操作,那该如何解决呢?
    还能做到更优吗?

    链式前向星

    提到链式前向星,你会联想到什么?
    链表前向星
    都对,综合一下,其实链式前向星就是用数组模拟链表的一种对前向星的优化
    虽说鱼和熊掌不可得兼,但链式前向星真正意义上做到了优化空间占用又减少时间占用
    因为它避免了对数据的排序

    那它是怎么做到的呢?我认为这是巧妙、有趣的

    同样,链式前向星会定义:第 i 边的终点,第 i 边的边权
    但第 i 条边的起点会和 i(边编号) 被当做数组模拟链表的指针使用

    链表是一种存储上非连续非顺序的存储结构
    所以我们利用这一点无需排序

    具体怎么做呢?

    首先定义储存结构

    struct node{
    	int next;//这条边同起点的上一条边的位置 
    	int to;//这条边的终点 
    	int l;//这条边的边权 
    }edge[1010];
    int head[1010];//这条边所在起点的最后一条边 
    

    lto 的含义都是不变的,表示第 i 条边的终点和第 i 边的边权

    next 表示的是与第 i 条边相邻的一条同起点边,相当于链表的子指针
    head是作为记录的是以第 i 条边所在起点为起点的首条边,相当于链表的头指针

    (这个地方很巧妙,记录的是上一条条边的位置和最后一条边的位置,稍后会讲到)

    首先我们输入和存储 to 和 l没有任何疑问,和前向星一致,而form我们先暂时用临时变量x表示
    于是:

    int main(){
    	for(int i=0;i<m;i++){
    		cin>>x>>y>>z;
    		edge[i].to=y;
    		edge[i].l=z; 
    	}
    	return 0;
    }
    

    然后我们对next和head进行处理

    memset(head,-1,sizeof(head));
    

    先对head进行初始化,由于我们是从i=0开始存储的,所以head赋值为-1
    否则模拟链表的最后一个元素会指向一些奇奇怪怪的地方

    来看一下代码

    int main(){
    	for(int i=0;i<m;i++){//边的编号i
    		cin>>x>>y>>z;//i条边的起点,终点,边权 
    		edge[i].next =head[x];//黑色箭头为其赋值(1),绿色箭头表示指向(2)
    		edge[i].to =y;
    		edge[i].l =z; 
    		head[x]=i; //红色数据表示更新(3),蓝色箭头表示指向(4)
    	}
    }
    

    举一组数据:
    在这里插入图片描述

    1 2
    2 3
    3 4
    1 3
    4 1
    1 5
    4 5

    初始应该为:

    i(边的编号) to(边的终点) next(同起点的临边的编号,子指针) head(所在起点最开始的边的编号,头指针)
    0 2 -1 0
    1 3 -1 1
    2 4 -1 2
    3 3 0 3
    4 1 -1 4
    5 5 3 5
    6 5 4 6

    然后我们依次操作后

    这样按照程序操作:
    在这里插入图片描述(注意,head[x]是指x为起点的最后一条边)
    得到存储:

    i(边的编号) to(边的终点) next(同起点的临边的编号,子指针) head(所在起点最开始的边的编号,头指针)
    0 2 -1 5
    1 3 -1 1
    2 4 -1 2
    3 3 0 5
    4 1 -1 6
    5 5 3 5
    6 5 4 6

    也就是一个数组模拟链表的的图
    在这里插入图片描述我们就不难发现,巧妙之处在于
    从开始向后输入数据head[X]的值可能会再次刷新
    所以head其实存储的是以X为起点的最后一条边,next是自后向前连接
    这个模拟链表是以x为起点从最后一条边向前连接
    避免了X为起点的第一条边向后连接许多麻烦

    所以,大致代码为:

    #include<bits/stdc++.h>
    using namespace std;
    struct node{
    	int next;//这条边同起点的上一条边的位置 
    	int to;//这条边的终点 
    	int l;//这条边的边权 
    }edge[1010];
    int head[1010];//这条边所在起点的最后一条边 
    int main(){
    	cin>>m;
    	for(int i=0;i<m;i++){//边的编号i
    		cin>>x>>y>>z;//i条边的起点,终点,边权 
    		edge[i].next =head[x];//黑色箭头为其赋值(1),绿色箭头表示指向(2)
    		edge[i].to =y;
    		edge[i].l =z; 
    		head[x]=i; //红色数据表示更新(3),蓝色箭头表示指向(4)
    	}
    	
    	//其他操作......
    	
    	return 0;
    }
    

    总结

    总结一下,前向星和链式前向星都是对边进行存储
    均对空间进行优化,均需要存储终节和视情况存储边权

    前向星需要对边进行排序,要在结构体中单独存储边的起点X

    而链式前向星则是用数组模拟链表的方式避免了耗时的排序
    head储存起点X的最后一条边,作为头指针;next从后向前指向,作为子指针
    多使用一个head数组,但仍是优化了空间,也不浪费时间

    推荐在数据较大时候,使用链式前向星,以达到不会在排序时浪费更多时间从而避免超时的目的
    前向星由于写法更加简单,可以在一些数据不大的情况下使用

    --END--

  • 相关阅读:
    八数码难题 (codevs 1225)题解
    小木棍 (codevs 3498)题解
    sliding windows (poj 2823) 题解
    集合删数 (vijos 1545) 题解
    合并果子 (codevs 1063) 题解
    等价表达式 (codevs 1107)题解
    生理周期 (poj 1006) 题解
    区间 (vijos 1439) 题解
    区间覆盖问题 题解
    种树 (codevs 1653) 题解
  • 原文地址:https://www.cnblogs.com/pqh-/p/13395341.html
Copyright © 2011-2022 走看看