最短路径之差分约束

一、前言

本文的目的是探讨最短路径与差分约束之间的关系。为了方便理解,本文将从存储图的数据结构,最短路径的算法,以及最短路径算法和差分约束之间的相互转换关系来讨论。而基于最短路径的有一个著名的三角形不等式,即两边之和大于第三边或者两边只差小于第三边,a+b>c和a-b<c。

  1. 图的存储结构

    工欲善其事必先利其器,为了更好的理解最短路径,必须先了解存储图的数据结构。

    一般而言,图的存储方式,有按节点存储按边存储两种方式。
  • 传统来说,按节点存储的是临接矩阵存储方式,优点是实现简单,一个二维数组就能实现。但缺点也很明显,需要O(n^2)的存储空间。
/*
*a[i][j]代表的是以i为起点j为终点的边的权值。
*一般而言的初始化是先让要初始化的每个节点都为无穷大。然后再输入。
*/
int a[N][N];

  • 可是在大部分情况下,我们的边数远远没有打到O(n^2),这种情况下,我们可以选择按边存储。按边存储的主要存储方式是邻接表,前向星,以及链式前向星。
/*
*邻接表是为每个节点建立好边的关系,将与每一个定点连接的边连成一个链表。
*在C++中,有为我们封装好的list链表,可以直接使用。
*
*/
list<int> li[N];

/*
*前向星也是按边存储,通过构造结构体节点,存储每一条边的开始节点,终止节点,和边权值。
*再将所有节点数组按照开始节点的大小进行排序,就可以达到连续访问从一起点出发的边。
*但同时缺点也明显,需要进行一次排序,开销也比较大。
*/
struct edge{
    int u,v,w;
    edge(){}
    edge(int _u,int _v,int _w){
        u=_u;
        v=_v;
        w=_w;
    }
}
edge eg[N];
bool cmp(edge a,edge b){
    return a.u<b.u;
}
sort(eg,eg+N,cmp);


/*
*链式前向星按照我的理解,就是以数组的方式构造链表。
*每条边的next都是指向上一条以相同节点开始的边。
*head[u]指向的总是以u开头的最后一条加入的边的地址。
*/
struct edge1{
    int u,v,w,next;
    edge(){}
    edge(int _u,int _v,int _w,int _next){
        u=_u;
        v=_v;
        w=_w;
        next=_next;
    }
}
edge eg[N];
void addedge(int u,int v,int w){
    eg[count]=edge1(u,v,w,head[u]);
    head[u]=count++;
}

/*
*其实根本不用这么复杂的结构,c++提供给了我们vector容器。
*/
vector<edge> eg[N];
eg[u].push_back(edge(u,v,w));
  1. 最短路径算法

    聊完了基本的数据结构,既然本文是介绍最短路径之差分约束,接下来就讲介绍最短路径的算法。最短路径的算法一般而言,是分为两种,一种是正权边,一种是存在负权边。
  • 正权边,使用的是dijkstra算法,为了优化,一般加入优先队列进行使用。
/*
*节点的数据结构
*id代表节点的编号,value代表源点到这点的当前最短距离
*/
struct node{
    int id;
    int value;
    node(){}
    node(int _id,int _value){
        id=_id;
        value=_value;
    }
    bool operator < (const node &a)const{
        return value>a.value;
    }
}
struct edge{
    int u,v,w;
    edge(){}
    edge(int _u,int _v,int _w){
        u=_u;
        v=_v;
        w=_w;
    }
}
vector<edge>eg[N];//存储边,利用vector实现链式前向星。
bool vis[N];//该节点是否已经属于扩展过的,是就是true,否就是false。
int pre[N],dist[N];//pre[i]存储到i的上一个节点,dist[i]存储源点到i的当前最短距离。
void dijkstra(int s){
   priority_queue<node> que;
   que.push(node(s,0);
   while(!que.empty()){
       node temp=que.front();
       int u=temp.id;
       que.pop();
       vis[u]=true;
       for(int i=0;i<eg[u).size;i++){
           int v=eg[u][i].v;
           int w=eg[u][i].w;
           if(!vis[v]&&dist[v]>dist[u]+w){
               dist[v]=dist[u]+w;
               pre[v]=u;
               que.push(node(v,dist[v]);
           }
       }
   }
}
void init(){
    for(int i=0;i<n;i++){
        vis[i]=false;
        dist[i]=INF;//INF表示为不可达
        eg[i].clear();
    }
}
  1. 而当图中存在负权边时,就需要使用,bellman-ford算法或者spfa算法,下面我们将介绍spfa算法,可以将其视作是bellman-ford算法的优化版本。
/*
*
*/
struct node{
    int id;
    int value;
    node(){}
    node(int _id,int _value){
        id=_id;
        value=_value;
    }
    bool operator < (const node &a)const{
        return value>a.value;
    }
}
struct edge{
    int u,v,w;
    edge(){}
    edge(int _u,int _v,int _w){
        u=_u;
        v=_v;
        w=_w;
    }
}
bool inq[N];//标记定点是否在队列中,true表示在,flase表示不在
vector<edge> eg[N];//用vector来模拟链式前向星,或者说邻接表
int dist[N],visitcount[N];//dist[i]记录从源点到i的当前最短距离,visitcount[i]表示i节点被访问的次数;
void spfa(int s,int n){
    for(int i=0;i<=n;i++){
        inq[i]=0;
        visitcount[i]=0;
    }
    priority_queue<node> que;
    que.push(node(s,0));
    inq[s]=1;
    while(!que.empty()){
        node temp=que.front();
        int u=temp.id;
        que.pop();
        inq[u]=0;
        viscount[u]++;
        if(visitcount[u]>n){
            cout<<"No answer!"<<endl;
        }
        for(int i=0;i<eg[u].size();i++){
            int v=eg[u][i].v;
            int w=eg[u][i].w;
            if(dist[v]>dist[u]+w){
                dist[v]=dist[u]+w;
                if(!inq[v]){
                    que.push(node(v,dist[v]));
                    inq[v]=true;
                }
            }
        }
    }
}

二、差分约束

1.差分约束的定义

如若一个系统由n个变量和m个不等式组成,并且这m个不等式对应的系数矩阵中每一行有且仅有一个1和-1,其它的都为0,这样的系统称为差分约束( difference constraints )系统。

即可以化为:

x[i]-x[j]<=ak

x[c]-x[d]>=ak1

...

x[n]-x[p]<=akn

也就是说,差分约束,就是一组不等式的集合。同时我们观察,我们用来求最短路径的方程,dist[u]> dist[v]+w,通过移项,可以看到,和差分约束的方程相同,实际上,我们可以把x[i]-x[j]<=ak看作是,从节点j指向节点i的一条边为ak的有向图。于是通过多个不等式的集合,我们可以相应的建出有向图。例如下图:

同时,我们可以从上面的介绍中看到x[i]-x[j]<=ak,x[i]-x[j]>=ak都是从i到j的一条边,边值是ak,但是有什么区别吗?

  • x[i]-x[j]<=ak,表示的是每一条边,都小于ak,所以我们可以看到,例如上图从0->3的路有三条,0->2->3 路径长度L1, 0->3路径长度L2,0->1->2->3路径长度L3三条路。
  • 所以实际上,不管我们怎么到达,都得满足全部约束,所以x[3]-x[0]<=min(L1,L2,L3)。也就是意味着,当我们在求从0到3的最短路径,就是在求满足约束情况下的最大值。
  • x[i]-x[j]>=ak,与上面相反,所得的实际结果应该是都大于每一条边,所求的x[3]-x[0]>=max(L1,L2,L3),所以求0到3的最长路径,就是求得在满足约束条件下的最小值。
posted @ 2019-03-19 14:43  waaaafool  阅读(368)  评论(0编辑  收藏  举报