zoukankan      html  css  js  c++  java
  • 【算法学习笔记】38.最短路问题 SJTU OJ 1105 path

    path

    Description

    以下摘自是某ACM队某次对话的某民间记载:

    ……

    ”接下来来讨论一下关于如何吃饭的问题。“

    唰唰唰。画出了一张无向图。

    ”我们现在处于S点,食堂处于T点。“

    指指点点。

    ”本来吃饭是个很简单的问题,这条路是最短路径,我们顺着走过去就好。“

    队长画出了一条最短路径。

    ”但是你们两个非要提出古怪的要求。“

    拿起笔指向其中一人。

    ”首先是你,想要走一条人最少的路线,让我感到非常头疼,因为这意味着我们可能要绕很远的一段路。“

    笔尖一转。

    ”然后是你,居然非要走一条最阴暗的路线,我已经完全不能够理解你在思考些什么了。“

    ……

    记载到此结束。

    我们对这段历史很有兴趣。现在已知上述队长画出的图,以及图中的S、T和各边的权值,求三人所要求的路线。

    提示:没有学习过最短路径的同学可以上网查找一下相关资料,推荐使用SPFA或者dijkstra算法。

    Input Format

    输入的第一行包含一个不超过10的正整数T,表示数据的组数。 接下来有T个部分,每个部分的第一行包括四个正整数n、m、s、t,(1<=n<=1000,0<=m<=10000),s、t表示起点终点。接下来有m行,第i行描述第i条边的信息,所有权均为正整数,并且不超过32768,格式见样例,并且保证数据中的格式与样例一致。 顶点标号为1~n标号。

    Output Format

    输出包含T行,每行包括三个正整数分别按顺序表示路径的最小长度和、路径的最少人数和,以及路径的最小亮度值。

    Sample Input

    1
    3 3 1 3
    Name: Short but crowd road, From: 1, To: 2, Length: 1, People number: 10, Light: 1;
    Name: Normal road, From: 2, To: 3, Length: 1, People number: 1, Light: 1;
    Name: Long but not crowd road, From: 1, To: 3, Length: 10, People number: 1, Light: 1;
    

    Sample Output

    2 1 1


    此题就是字符串处理加上最短路径问题。
    关于最短路径问题经典算法主要有两个.
    算法1:
    Dijkstra算法步骤如下:
    G={V,E}
    1. 初始时令 S={V0},T=V-S={其余顶点},T中顶点对应的距离值
      若存在边<V0,Vi>,d(V0,Vi)为<V0,Vi>弧上的权值
      若不存在边<V0,Vi>,d(V0,Vi)为∞
    2. 从T中选取一个与S中顶点 有关联边 且 权值最小 的顶点W,加入到S中
    3. 对其余T中顶点的 距离值 进行修改: 若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值 (松弛操作)
    重复上述步骤2、3,直到S中包含所有顶点,即W=Vi为止 表示找到了终点
     
    Dijkstra就是不断地去寻找、更新. 它的每一次更新都要遍历所有的T中的点, 这个是耗时的最重要原因. 当然可以用队列优化等等策略去改进.
    代码如下,这个只是Demo,实际情况超时
    //sjtu oj 1105
    //dijkstra 算法应用
    
    
    
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int main(int argc, char const *argv[])
    {
        int N = 5;
        int const INFINITY = 9999999;
        //初始化Graph 邻接矩阵
        int** Graph = new int*[N+1];
        for (int i = 0; i < N+1; ++i){
            Graph[i] = new int[N+1];
            for (int j = 0; j < N+1; ++j)
                Graph[i][j]=0;
        }
        
        Graph[1][2]=Graph[2][1]=1;
        Graph[1][3]=Graph[3][1]=5;
        Graph[2][4]=Graph[4][2]=2;
        Graph[2][5]=Graph[5][2]=4;
        Graph[3][4]=Graph[4][3]=2;
        Graph[3][5]=Graph[5][3]=3;
        Graph[4][5]=Graph[5][4]=1;
        
        for (int i = 1; i <= N; ++i){
            for (int j=1; j <= N; ++j){
                cout<<Graph[i][j]<<" ";
            }
            cout<<endl;
        }
        //初始D数组
        int* D = new int[N+1];
        int start = 1;    //设置起点
        int end = 5;    //设置终点
        for (int i = 1; i <= N; ++i)
            //把所有和起点直接相邻的点的权重加载为D
            D[i] = Graph[start][i]>0 ? Graph[start][i] : INFINITY;
        D[start] = 0;//起点自己和自己的距离是0
        
        int* S = new int[N],*Q = new int[N];//S表示所有的已经计算出最短距离的点
        int p_S=0,p_Q=0,len_S=1,len_Q=N-1;
        S[p_S++] = start;//最开始只有起点在S中
        for (int i = 1; i <= N; ++i) if(i!=start)
            Q[p_Q++] = i;//把所有非起点的点都加入到Q中
        
        
        while(len_Q>=1){//Q中还有元素 就要从Q里选出一个加到S里
            cout<<"S:"<<endl;
            for (int i = 0; i < len_S; ++i)
            {
                cout<<S[i]<<" ";
            }cout<<endl;
            cout<<"Q:"<<endl;
            for (int i = 0; i < p_Q; ++i) if(Q[i]!=0)
            {
                cout<<Q[i]<<" ";
            }cout<<endl;
            
            //选Q中的点u的标准就是 d[u]最小的点
            int u = 0,minD = INFINITY;
            int qid = 0;
            for (int i=0; i < p_Q; ++i) if(Q[i]!=0){
                if(D[Q[i]]<minD){
                    u = Q[i]; minD = D[u]; qid = i;
                }
            }
            cout<<"u:"<<u<<endl;
            if(u==0 or u==end)    //u==0    表示现在Q里的所有点都和start无法连接
                //u==end表示找到了终点 不必继续了
                break;
            //将u加入到S中 并且更新所有Q中点的D值
            Q[qid]=0;        len_Q--;    
            S[p_S++]=u;        len_S++;
            //对所有和u相关的Q中的点进行D值更新
            for (int i = 0; i < p_Q; ++i) if(Q[i]!=0)
            {
                int v = Q[i];
                if(Graph[u][v]>0)//找到所有和u相连的i点
                if(D[v] > D[u]+Graph[u][v])
                    D[v] = D[u]+Graph[u][v];//更新
            }  
        }
        
        cout<<D[end]<<endl;  
        delete[] S;
        delete[] Q;
        delete[] D;
        delete[] Graph; 
        
        return 0;
    }
    Dijkstra
     
    算法2:
    SPFA和BFS很像 只是它允许一个点重复进入待处理队列,因为在不断更新.搭配邻接链表使用更佳
    它的基本思想就是 构造一个队列 首元素是起点,然后对和起点相连的每个点进行遍历, 如果可以进行松弛操作,则把这个点也加到处理队列中, 留待继续优化因它而起的其他点.
    注意 这个算法无法中途终止 必须到队列为空(为空表示没有要继续优化的点了,所有的最短距离估计值即是最优)
    PS:没有进行优化 如果是双端队列可以进行对不同元素,按条件放到头或者尾,从而减少不必要的更新. 
    代码如下:
    #include <iostream>
    #include <cstring>
    #include <sstream>
    #include <cstdio>
    #include <queue>
    #include <vector>
     
    //用SFPA来做  主要的想法是BFS和队列优化 数据结构用邻接链表
    #define INFINITY 32769
    using namespace std;
     
    struct  Edge
    {
        int to;
        int length;
        int pepole;
        int light;
    };
     
    vector<Edge> adjmap[1001];//邻接链表来存储每个点的边集 注意:无向图的处理
    bool in_queue[1001];//记录某个顶点是否在队列中
    int D[1001][3]={0};//D来存储从起点到某一点的最短距离(暂时的) [0]是length [1]是people [2]是light
    int n,m,s,e;//点 边 起点 终点
    //从字符串里提取信息
    int getINT(string edge,string tag,string seg=","){
        //有点傻..好像按顺序找数字就可以了?两位数不好办吧..
        stringstream ss;
        int res;
        int start = edge.find(tag) + tag.size();
        int end = edge.substr(start).find(seg);
        ss<<edge.substr(start,end);
        ss>>res;//转换类型
        return res;
    }
     
    inline int getWeight(Edge e,int flag){
        switch(flag){
            case 0:
                return e.length;
            case 1:
                return e.pepole;
            case 2:
                return e.light;
        }
        return 0;
    }
     
    //返回最"短"路径的权重总和
    int spfa(int flag){//spfa不可以像dijsktra那样遇到end中途断掉 必须全部结束
        //s=source flag = 0 ,1 ,2
        queue<int> q;//正要遍历的节点的号码 BFS
        for (int i = 1; i <= n; ++i)//对所有的点进行初始化
        {
            D[i][flag]=INFINITY;
            in_queue[i]=false;
        }
        q.push(s); in_queue[s]=true;
        D[s][flag]=0;
        //初始化完毕
        
        while(!q.empty()){
            int u = q.front();
            q.pop(); in_queue[u]=false;
            unsigned long size = adjmap[u].size();
            for (int i = 0; i < size; ++i)
            {
                int to = adjmap[u][i].to;
                int weight = getWeight(adjmap[u][i],flag);
                //判断是否进行松弛操作
                if((D[u][flag]<INFINITY) and (D[to][flag] > D[u][flag]+weight)){
                    //松弛
                    D[to][flag] = D[u][flag] + weight;
                    if(!in_queue[to]){
                        in_queue[to]=true;
                        q.push(to);
                    }
                }
            }
        }
        
        //对所有边
        return D[e][flag];
    }
     
    int main(int argc, char const *argv[])
    {
        int T;cin>>T;
        for (int i = 0; i < T; ++i)
        {
            
            cin>>n>>m>>s>>e;
            getchar();//因为第一行的回车没读 要getchar一下 否则RE
            //初始化邻接链表
            for (int i = 0; i < n; ++i)
                adjmap[i].clear();
            //对m个边进行初始化
            for (int i = 0; i < m; ++i)
            {
                string edge;
                getline(cin,edge);
                
                while(edge=="")    //据说有空行...
                    getline(cin,edge);
                
                int from,to,pepole,light,length;
                //读取
                from   = getINT(edge,"From: ");
                to     = getINT(edge,"To: ");
                length = getINT(edge,"Length: ");
                pepole = getINT(edge,"People number: ");
                light  = getINT(edge,"Light: ",";");
                Edge e;
                e.to = to;
                e.length = length;
                e.pepole = pepole;
                e.light  = light;
                adjmap[from].push_back(e);
                e.to = from;//无向图要处理两次
                adjmap[to].push_back(e);
            }
            memset(D,0,sizeof(D));
            cout<<spfa(0)<<" "<<spfa(1)<<" "<<spfa(2)<<endl;
            
        }
        return 0;
    }
     
     
    /*
    2
    3 3 1 3
    Name: Short but crowd road, From: 1, To: 2, Length: 1, People number: 10, Light: 1;
    Name: Normal road, From: 2, To: 3, Length: 1, People number: 1, Light: 1;
    Name: Long but not crowd road, From: 1, To: 3, Length: 10, People number: 1, Light: 1;
    3 3 1 2
    Name: Short but crowd road, From: 1, To: 2, Length: 1, People number: 10, Light: 1;
    Name: Normal road, From: 2, To: 3, Length: 1, People number: 1, Light: 1;
    Name: Long but not crowd road, From: 1, To: 3, Length: 10, People number: 1, Light: 1;
     
     */
    SPFA
     
     
  • 相关阅读:
    夫妻合伙创业,度过艰苦的时期,酒楼生意蒸蒸日上
    搬运工做起了石头生意,他的石雕产品深受喜爱
    都市白领开毛衣店,仅一年时间收入20万元
    高中生下海创业,几十年来他始终将家乡的利益放在首位
    白手起家的他,创立公司,身价达到数千万
    从搬砖工到亿万富豪,这些年他经历了什么?
    货车司机创业做物流,他只为赢得老百姓的口碑
    患难夫妻做服装,他们的公司在全国做出了名堂
    struts2注解的作用
    struts2注解的作用
  • 原文地址:https://www.cnblogs.com/yuchenlin/p/sjtu_oj_1105.html
Copyright © 2011-2022 走看看