链式前向星(静态邻接表)存储图及简单的最短路径(dijkstra+spfa)

链式前向星,也叫静态邻接表,今天我讲的是关于图的东西,所以都以图的角度去看待这个较优化的存储结构。

存图优缺点

存图的时候,链式前向星是一种介于邻接矩阵和和邻接表的一种存储结构。

邻接矩阵

存图一般都用这个呀!不过啊,当遇到稀疏图的时候,顶点特别多,而边就那么几条,空间浪费必然很大,所以我们又想到了邻接表

邻接表

邻接表(未优化过的链式前向星)是最常用存储结构之一。 但是 vector(动态数组) 的时间效率较低 (较普通数组而言)。

所以,链式前向星也就因运而生。链式前向星是介于 邻接矩阵 和 邻接表 之间比较均衡的一种数据结构。

链式前向星

先来看一下代码:

//链式前向星的存储结构
struct node
{
    int to;//边指向的终点
    int next;//下一条边的存储下标
    int w;//权值
}edge[maxn];
int head[maxn];
int cnt;
//增边
void add_edge(int u,int v,int w)
{
    edge[cnt].w=w;
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;//需初始化cnt=0
}

举个列子:

这是我拿别人家博客的图片的哦,希望不要介意呀!

对于这样一个图,我们进行增边操作;

1.首先,cnt初始化,可以是0,也可以是一,看你个人喜好。head[]数组初始化,可以是0也可以是1.

2.调用函数增边:

  for(int i=0;i<n;i++)//n代表边数
    {
        cin>>u>>v>>w;//u是起始点,v是这条边的终点,w是权值
        add_edge(u,v,w);
    }

例如上图,就是输入(我这里没权值,直接忽略吧):

1 2 

2 3

3 4

1 3

4 1

1 5

4 5

这样就建成了一张链式前向星的图。

其他相信大家都好理解,就是 edge[].next=head[]  这个比较模糊,其实就是存的是以 i 为起点,连接着 i 的顶点编号的最大编号所在的边数,就如上图(按输入顺序编号),1->2(第0条边),1->3(第3条边),1->5(第5条边),而edge[5].next=3;edge[3].next=0;所以在遍历的时候,就相当于倒着遍历,就有这样的语句

 for(int i=head[u];i!=-1;i=edge[i].next){
        ...
}//u是起点(当前节点)

有了这样一个邻接表的基础,那么对于图的最大经典问题,最短路,在这样的存储结构下又有什么不一样呢。看下面代码:

dijsktra 

//堆优化的最短路
typedef pair<int ,int > P;//存编号与距离
void dijkstra(int s)//s为起点
{
    priority_queue< P,vector<P>,greater<P> > q;
    memset(d,inf,sizeof(d));
    //memset(vis,0,sizeof(vis));
    d[s]=0;
    q.push(P(0,s));
    while(!q.empty())
    {

        P p=q.top();
        q.pop();
        int u=p.second;
        if(d[u]<p.first)
            continue;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(d[v]>d[u]+edge[i].w){
                d[v]=d[u]+edge[i].w;
                q.push(P(d[v],v));
            }
        }
    }
}

上面是用了堆优化(priority_queue)后的dijkstra模板,最主要的思想就是松弛到不能再松弛位置, 然后遍历的过程就是松弛过程。可能对于一些题目, priority_queue< P,vector<P>,greater<P> > q;中的 greater 会有一些超时,重载运算符也是一种解决办法。复杂度:

O((V+E)lgV)

SPFA

int d[maxn];//记录最短距离

int vis[maxn];//判断是否在队列中

void SPFA(int s)//可以计算含负权的图
{
    memset(d,inf,sizeof(d));
    memset(vis,0,sizeof(vis));
    queue<int > q;
    q.push(s);
    vis[s]=1;
    d[s]=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        //开始遍历松弛
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(d[v]>d[u]+edge[i].w)
            {
                d[v]=d[u]+edge[i].w;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
}

有没有觉得这个非常像,就是相差了vis[]这样一个数组判断,而这也就是差别,vis[]在SPFA就是判断当前点是否在队列中,从而进行松弛操作,还有就是vis[]还能判断图中是否有“负权环”(当vis[]>n, 就表示有环)。

复杂度:O(kE)O(kE)。

适用场景

如果是稠密图,Dijkstra+heap比SPFA快。稀疏图则SPFA更快。SPFA可以有SLF和LLL两种优化,SLF就是d比队头小就插入队头,否则插入队尾。

 全部代码:

#include<iostream>
#include<queue>
#include<string.h>
using namespace std;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f;
//链式前向星的存储结构
struct node
{
    int to;//边指向的终点
    int next;//下一条边的存储下标
    int w;//权值
}edge[maxn];
int head[maxn];
int cnt;
//增边
void add_edge(int u,int v,int w)
{
    edge[cnt].w=w;
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;//需初始化cnt=0
}

int d[maxn];//记录最短距离

int vis[maxn];//判断是否在队列中
/*
void SPFA(int s)//可以计算含负权的图
{
    memset(d,inf,sizeof(d));
    memset(vis,0,sizeof(vis));
    queue<int > q;
    q.push(s);
    vis[s]=1;
    d[s]=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        //开始遍历松弛
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(d[v]>d[u]+edge[i].w)
            {
                d[v]=d[u]+edge[i].w;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
}
*/
//堆优化的最短路
typedef pair<int ,int > P;//存编号与距离

void dijkstra(int s)//s为起点
{
    priority_queue< P,vector<P>,greater<P> > q;
    memset(d,inf,sizeof(d));
    //memset(vis,0,sizeof(vis));
    d[s]=0;
    q.push(P(0,s));
    while(!q.empty())
    {

        P p=q.top();
        q.pop();
        int u=p.second;
        if(d[u]<p.first)
            continue;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(d[v]>d[u]+edge[i].w){
                d[v]=d[u]+edge[i].w;
                q.push(P(d[v],v));
            }
        }
    }
}

int main()
{
    int m;//顶点数
    int n;//边数
    int u,v,w;
    cnt=0;
    cin>>m>>n;
    memset(head,-1,sizeof(head));
    for(int i=0;i<n;i++)
    {
        cin>>u>>v>>w;
        add_edge(u,v,w);
    }
    dijkstra(1);
    //SPFA(1);
    for(int i=1;i<=m;i++)
        cout<<d[i]<<" ";
    cout<<endl;
    return 0;
}

注意:对于inf这个定义,0x7fffffff和0x3f3f3f3f这两个也是一个大坑,

很多人可能设为0x7fffffff,这个数的确是32-bit int的最大值,符号位为0,其他的都是1

但在很多情况下,0x7fffffff会出现错误,比如溢出,这样两个无穷大数相加会变成负数,还有如在做dijkstra求最短路时,当做松弛操作,判断if (d[u]+w[u][v]<d[v]) d[v]=d[u]+w[u][v]时,若u到v没有路劲,w[u][v]=0x7fffffff,这样d[u]+w[u][v]会变成负数,这就产生了错误。

所以,memset(d,0x3f,sizeof(d));

发布了31 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/sjs_caomei/article/details/82181607