zoukankan      html  css  js  c++  java
  • 最短路径之差分约束

    一、前言

    本文的目的是探讨最短路径与差分约束之间的关系。为了方便理解,本文将从存储图的数据结构,最短路径的算法,以及最短路径算法和差分约束之间的相互转换关系来讨论。而基于最短路径的有一个著名的三角形不等式,即两边之和大于第三边或者两边只差小于第三边,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的最长路径,就是求得在满足约束条件下的最小值。
  • 相关阅读:
    同事跳槽京东后,分享给我一份JAVA核心开发手册(架构筑基+开源框架+分布式架构+微服务架构+性能调优)
    只有2年经验的Java程序员,面试25K的阿里巴巴后端岗,已拿offer
    985硕士粉秋招拿下快手44万offer,面试资料学习经验分享
    年薪50万的程序员到底有多累、多辛苦?句句扎心。
    蚂蚁金服首发887页Java面试宝典!还原真实面试情景+面试题
    P8首谈做Java,在一线大厂做到金字塔顶端的人平时都如何学习?
    poj 2153 Rank List(查找,Map)
    算法导论 6-2 d叉堆
    算法导论 6.5.9 堆实现K路归并问题
    poj 2051 Argus(优先队列)
  • 原文地址:https://www.cnblogs.com/waaaafool/p/10558590.html
Copyright © 2011-2022 走看看