美文网首页
【数据结构】 对于“前向星”的理解

【数据结构】 对于“前向星”的理解

作者: tysnd | 来源:发表于2018-12-20 14:11 被阅读0次

    在做图论题的时候,关于如何存图,经常会很纠结。用邻接矩阵,可能会很浪费空间;用邻接表,结构很紧凑,但是链表结构又较难实现。所以,网上有大牛发明了“前向星”,也就是静态链表,即用数组实现邻接表的功能。下面介绍一下我对前向星的一些理解。
    注:由于前向星其实和邻接表的原理相同,只不过是用数组实现,因此本文在解释前向星原理的时候经常与邻接表进行比较。希望读者在看本文之前先理解邻接表的原理和实现。
    注二:对于树同样可以静态建树,可以参见我的另一篇文章https://www.jianshu.com/p/7e6488e40863
    图结构示例如下:

    示例.png
    如果使用邻接表存储,结构如下
    邻接表.png
    可以看出,邻接链表在每个顶点后接一个链表,表示这个顶点的邻接点。那么,如果合理地使用数组存储这个链表,显然更为优越——既能保证不浪费空间,实现起来也比链表容易。
    于是,前向星应运而生。前向星和邻接链表不同的一点就是,对于每个顶点,前向星存储的是该顶点的邻接边而非邻接点,也就是说,相比于邻接表在每个顶点后挂上一串该顶点的邻接点,前向星在每个顶点后面“挂”上一串该顶点的邻接边。这里的后一个“挂”是不准确的,我们可以在脑海中可以想象这个结构,但要注意实现“前向星”的数据结构并非如此。
    以下是实现前向星的数据结构
    //存边的信息的结构体
    struct Edge
    {
        int to;//某个顶点u的邻接点 
        int next;//顶点u的下一条邻接边的编号 
        int val;//该邻接边的权值 
    };
    
    //两个必要的数组
    Edge edge[edgenumber];//edge[i]表示编号为i的边 
    int head[vexnumber];//head[u]表示u的其中一条邻接边的编号 
    

    只看上面的数据结构,相信大家还是很疑惑:
    为什么Edge结构体不用记录顶点u?
    两个数组又有什么用?
    不要着急,我下面会仔细分析这些问题。

    正如我上面所说,前向星存储的是每个顶点的邻接边。可以在脑海中想象一下,在每个顶点后面连一个链表,不过链表的节点表示的是邻接边而非邻接点。而用数组实现这个链表,就是前向星。

    为什么不要存储顶点u?
    不管用哪种结构存图,都应该是每读入一条边的信息,就更新一次图结构。读入一条边u-->v,那么我们在存储图的时候,对于顶点u,该边就是u的一条邻接边,我们就把这条边“挂”在u后面。等读入所有的边,建图完成,顶点u后“挂”的边就全是顶点u的邻接边,因此只需要记录这些邻接边的另一个顶点,而不需要存储顶点u。
    由于边是按顺序一条一条读入,我们就很自然的想到对边进行编号,然后将这些边存入edge数组。对于Edge结构体中的next,其实就相当于链表中的next指针。不过此处的next是int型变量,存储的是下一条邻接边的编号。
    那么head数组是干什么的呢?
    这个就相当于邻接链表中的表头数组,head[u]就表示顶点u的某一条邻接边。根据这条边的next,就能找出顶点u所有的邻接边。
    如果还是很难想象,没关系,我们可以先来模拟一下。对于上文的示例,作为无向图,假设边权全为1,如果题目给数据,会给出如下数据,由于已经说明是无向图,就不必每条边都正反给出两次了。
    5 5 //顶点数和边数
    1 2 1
    1 4 1
    2 3 1
    2 5 1
    3 5 1
    我先给出初始化和建图的代码,然后再做解释。

        for(int i=0;i<vexnumber;i++)
        {
            head[i]=-1;
        }
        cin>>edgenum;
        //由于是无向图,要正向反向都建边
        for(int i=0;i<2*edgenum;i+=2)
        {
            int from,to,v;
            cin>>from>>to>>v;
            //第一条
            edge[i].to=to;
            edge[i].val=v;
            edge[i].next=head[from];//类似链表头插
            head[from]=i;
            //第二条
            edge[i+1].to=from;
            edge[i+1].val=v;
            edge[i+1].next=head[to];
            head[to]=i+1;
        }
    

    以下是模拟建图的过程
    需要注意的是,下文edge数组图片中的from->to列并不出现在Edge结构体中,我将其写出来仅仅是为了直观的展示这条边的两个顶点。
    初始情况
    初始化head数组为-1,类似链表中的NULL。


    初始edge数组.png
    初始head数组.png

    读入第一条边
    由于第一条边是 1--2,而且图是无向图,所以要在1--2和2--1建边。更新edge[i].next为head[u],同时更新head数组,使其值为相应的边的编号。这个操作类似链表的头插操作。


    edge数组.png
    head数组.png

    读入第二条边


    edge数组.png head数组.png

    读入第三条边


    edge数组.png head数组.png

    读入第四条边


    edge数组.png head数组.png

    读入第五条边


    edge数组.png head数组.png

    这样建图就完成了,为了直观,画成邻接表形式,但注意下图并非真正的存储结构。


    图的逻辑结构.png

    对于图论题,遍历图是经常要做的操作。对于前向星,遍历图的操作依然类似邻接表。以下是遍历图的代码

    for(int cur=1;cur<=vexnum;cur++)
    {
        for(int k=head[cur];k!=-1;k=edge[k].next)
        {
            //相应操作
            //......
            //...... 
        }
    }
    

    思考一下就能看出,其中cur表示顶点,而k为cur的邻接边在edge数组中的下标。
    仅仅只是看了数组中存储的东西,应该还是很难完全理解。因此希望读者自己模拟一遍,加深理解。
    以上就是我对前向星的理解。其实前向星采用的就是邻接链表的思想,只不过存储的是顶点的邻接边,并且用数组实现。总而言之,前向星节省空间,便与实现,是存储图的很优秀的数据结构。只要理解其原理,在做赛题的时候省时省力,事半功倍。

    相关文章

      网友评论

          本文标题:【数据结构】 对于“前向星”的理解

          本文链接:https://www.haomeiwen.com/subject/fjiekqtx.html