[置顶] 链式前向星

前向星:

一种数据结构,以储存边的方式来存储图。

构造方法如下:读入每条边的信息,将边存放在数组中,把数组中的边按照起点顺序排序,前向星就构造完了。

用于:点的数目太多,或两点之间有多条弧的时候。一般在别的数据结构不能使用的时候才考虑用前向星。

优缺点:除了不能直接用起点终点定位以外,前向星几乎是完美的。

链式前向星:

是前向星的优化。在代码量减小很多的情况下效率得到了较大的提升。

/*摘录自https://www.cnblogs.com/zmin/p/7349100.html*/

我们首先来看一下什么是前向星.

 

前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序,

并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了.

 

用len[i]来记录所有以i为起点的边在数组中的存储长度.

用head[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[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))



如果用链式前向星,就可以避免排序.


我们建立边结构体为:


struct Edge

{

     int next;

     int to;

     int w;

};


其中edge[i].to表示第i条边的终点,edge[i].next表示与第i条边同起点的下一条边的存储位置,edge[i].w为边权值.


另外还有一个数组head[],它是用来表示以i为起点的第一条边存储的位置,实际上你会发现这里的第一条边存储的位置其实

在以i为起点的所有边的最后输入的那个编号.


head[]数组一般初始化为-1,对于加边的add函数是这样的:


  1. void add(int u,int v,int w)  
  2. {  
  3.     edge[cnt].w = w;  
  4.     edge[cnt].to = v;  
  5.     edge[cnt].next = head[u];  
  6.     head[u] = cnt++;  
  7. }  

初始化cnt = 0,这样,现在我们还是按照上面的图和输入来模拟一下:


edge[0].to = 2;     edge[0].next = -1;      head[1] = 0;

edge[1].to = 3;     edge[1].next = -1;      head[2] = 1;

edge[2].to = 4;     edge[2],next = -1;      head[3] = 2;

edge[3].to = 3;     edge[3].next = 0;       head[1] = 3;

edge[4].to = 1;     edge[4].next = -1;      head[4] = 4;

edge[5].to = 5;     edge[5].next = 3;       head[1] = 5;

edge[6].to = 5;     edge[6].next = 4;       head[4] = 6;


很明显,head[i]保存的是以i为起点的所有边中编号最大的那个,而把这个当作顶点i的第一条起始边的位置.


这样在遍历时是倒着遍历的,也就是说与输入顺序是相反的,不过这样不影响结果的正确性.

比如以上图为例,以节点1为起点的边有3条,它们的编号分别是0,3,5   而head[1] = 5


我们在遍历以u节点为起始位置的所有边的时候是这样的:


for(int i=head[u];~i;i=edge[i].next)


那么就是说先遍历编号为5的边,也就是head[1],然后就是edge[5].next,也就是编号3的边,然后继续edge[3].next,也

就是编号0的边,可以看出是逆序的.

迪杰斯特拉:前向星加优化:
#include<iostream>
#include<cstring>
#include<cmath>
#include<string>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
const int MAX_V = 200010;
const int MAX_E = 2000010;
const int INF = 0x3f3f3f3f;
int V,E,cnt;
int heap[MAX_V],dis[MAX_V];

struct Edge{
    int to,next,cost;
}rng[MAX_E];
void add(int u,int v,int cost){
    rng[cnt].to = v;
    rng[cnt].next = heap[u];
    rng[cnt].cost = cost;
    heap[u] = cnt++;
}
struct Rule{
    bool operator()(int &a,int &b)const{
        return dis[a] > dis[b];
    }
};
inline int read()
{
    int X=0,w=1; char ch=0;
    while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
    return X*w;
}
void Dijkstra(int a_){
    memset(dis,INF,sizeof(dis));
    priority_queue<int,vector<int>,Rule > q;
    dis[a_] = 0;q.push(a_);
    
    while(!q.empty()){
        int u = q.top();q.pop();
        for(int k=heap[u];k != -1;k = rng[k].next){
            int &v = rng[k].to;
            if(dis[v] > dis[u] + rng[k].cost){
                dis[v] = dis[u] + rng[k].cost;
                q.push(v);
            }
        }
    }
}
int main(void){
    cnt = 0;
    memset(heap,-1,sizeof(heap));
    V = read(),E = read();
    int x,y,z;
    for(int i=1;i<=E;i++){
        x = read(),y = read(),z = read();
        add(x,y,z);
    }
    Dijkstra(1);
    if(dis[V] == INF){
        printf("-1\n");
    }
    else
        printf("%d\n",dis[V]);
    return 0;
}

spfa:前向星加优化:

/*摘录自https://www.cnblogs.com/crazyacking/p/3761686.html*/

#include<algorithm>
#include<cstdio>
#inlude<cstring>
#include<cstdlib>
#include<iostream>
#include<vector>
#include<queue>
#include<stack>
#include<iomanip>
#include<string>
#include<climits>
#include<cmath>
#define MAXV 10010
#define MAXE 50010
#define LL long long
using namespace std;
int T,n,m,u,v,w;
int now,home,goal;
bool vis[MAXV];
LL dis[MAXV];
namespace Adj
{
        // edge数组是一个边集数组,存放一条边的信息
        // to --- 该条边的终点
        // next --- 下一条要访问的边(存的是edge数组的下标).即:访问完了edge[i],下一条要访问的就是edge[edge[i].next],如果next为0,表示now这个节点作为起点的边已经全部访问完.(下一步:Q.front())
        // w --- 该条边的权值
        struct Node
        {
                int to,next,w;
        };
        Node edge[MAXE];

        // idx --- edge数组的下标
        // head[i] --- 表示以i节点为起点的所有出边在edge数组中的起始存储位置为head[i].(如果head[i]为0,表示结点i没有出边)
        int idx,head[MAXV];
        // 初始化
        void init()
        {
                idx=1;
                memset(head,0,sizeof(head));
        }

        // 加边函数
        void addEdge(int u,int v,int w) // 起点,终点,权值
        {
                edge[idx].to=v;   // 该边的终点
                edge[idx].w=w;    // 权值
                edge[idx].next=head[u]; //  (指向head[u]后,head[u]又指向了自己)
                head[u]=idx;  // 以u结点为起点的边在edge数组中存储的下标
                idx++;
        }
}
using namespace Adj;
void visit(int sta)
{
    for(int i=1;i<=n;i++)
    {
        vis[i]=0;
        dis[i]=LLONG_MAX;
    }
    // 起点进队
    queue<int>Q;
    Q.push(sta);
    vis[sta]=1;
    dis[sta]=0;
    while(!Q.empty())
    {
        int now=Q.front();
        Q.pop();
        vis[now]=0;  // 在spfa中这儿需要改为0,因为每个节点需要重复进队
        for(int i=head[now];i;i=edge[i].next)  //取出now结点在edge中的起始存储下标(当i=0,即edge[i].next为0,说明以now节点为起始点的边全部访问完)
        {
            int w=edge[i].w;
            int son=edge[i].to;
            printf("%d --> %d  , weight = %d\n",now,edge[i].to,edge[i].w);
            if(dis[now]+w<dis[son]) // 松弛操作
            {
                    dis[son]=dis[now]+w;
                    if(!vis[son])
                    {
                        Q.push(son); // 子节点未访问过
                        vis[son]=1; //  标记已访问
                    }
            }

        }
    }
    puts("/*************************************** END ******************************************/");
    for(int i=1;i<=n;++i)
    {
            printf("%d --> %d shortest distance is %d\n",sta,i,dis[i]);
    }

}

int main()
{
    while(1)
    {
       Adj::init();
       scanf("%d",&n);
       scanf("%d",&m);
       for(int i=1;i<=m;++i) // 输入m条边
       {
               int s,e,w; // 起点 终点 权值
               scanf("%d %d %d",&s,&e,&w);
               addEdge(s,e,w);  //若是无向图,反过来再加一次
       }
       int start_point;   //访问的起点
       scanf("%d",&start_point);
       visit(start_point);
    }
    return 0;
}

posted @ 2018-03-11 11:56  可爱如我  阅读(111)  评论(0编辑  收藏  举报