• 最短路径


    最短路径

    dijkstra:O(N^2),,单源最短路径,不能有负边.可以通过堆优化为O(nlogn+m)

    //图的结构都用邻接表写 
    //第一种:最简单的加上记录路径 
    struct node{
    	int v;
    	int dis;
    };
    vector<node> G[maxn];
    int pre[manx];  //最简单的一种pre写法(苦笑——
    //输出过程 
    int dis[maxn]={0};  //记录起点与其他各点的最短距离 
    void outputdfs(int v,int st){
    	if(v==st){
    		cout<<st<<endl;
    		return;
    	}
    		outputdfs(pre[v],st);
    		cout<<v<<" ";
    }
    void dijkstra1(int st){
    	int numnode,numedge,x,y,diss;
    	cin>>numnode>>numedge;
    	for(int i=0;i<numedge;i++){
    		cin>>x>>y>>diss;
    		node a,b;
    		a.v=x;a.dis=diss;b.v=y;b.dis=diss;
    		G[x].push_back(b);
    		G[y].push_back(a); //无向图 
    	}//以后可以直接写构造函数 
    	fill(dis,dis+maxn,INF);
    	dis[st]=0;  //
    	for(int i=0;i<numnode;i++) pre[i]=i; //初始化pre数组不要忘了!!!!
    	for(int i=0;i<numnode;i++){
    		int u=-1,numi=INF;
    		for(int j=0;j<numnode;j++){
    			if(vis[j]==0&&dis[j]<mini){
    				mini=dis[j];
    				u=j;
    			}
    		} //找点的过程 
    		if(u==-1) return; //退出标志
    		vis[u]=1;
    		//这是第一阶段
    		for(int j=0;j<G[u].size();j++){
    			//更新与找到的这个点相连的点
    			int v=G[u][j].v;
    			if(vis[v]==0&&dis[v]<dis[u]+G[u][j].dis){
    				dis[v]=dis[u]+G[u][j].dis;
    				pre[v]=u;
    			} 
    		} 
    	} 
    }
    

    堆优化的dijkstra算法(优先队列)

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=100010;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    int n,m;
    int head[maxn],nex[maxn],num;
    bool vis[maxn];
    int dis[maxn];
    struct node{
    	int u,v,c;
    	node(){}
    	node(int u,int v,int c){
    		u=u;v=v;c=c;
    	}
    }ed[maxn];
    struct node1{
    	int to,c;
    	node1(){}
    	node1(int to,int c):to(to),c(c){}
    	//要写运算符重载
    	bool operator <(const node1 &oth)const{
    		return c>oth.c;
    	} 
    };
    void adde(int u,int v,int c){
    	ed[num]=node(u,v,c);
    	nex[num]=head[u];
    	head[u]=num++;
    }
    bool relax(int u,int v,int c){
    	if(dis[v]<dis[u]+c){
    		dis[v]=dis[u]+c;
    		return true;
    	}
    	return false;
    }
    void inti(){
    	memset(head,-1,sizeof(head));
    	memset(nex,-1,sizeof(nex));
    	num=0;
    	int u,v,c;
    	for(int i=0;i<m;i++){
    		scanf("%d %d %d",&u,&v,&c);
    		adde(u,v,c);
    	}
    }
    void dij(int s){
    	for(int i=1;i<=n;i++){
    		dis[i]=INF;
    		vis[i]=false;
    	}
    	dis[s]=0;
    	priority_queue<node1> q;
    	q.push(node1(s,dis[s]));
    	for(int i=1;i<=n;i++){
    		while(!q.empty()&&vis[q.top().to]) q.pop();
    		if(q.empty()) break;
    		node1 op=q.top();
    		q.pop();
    		vis[op.to]=true;
    		for(int i=head[op.to];i+1;i=nex[i]){
    			if(relax(op.to,ed[i].v,ed[i].c)&&!vis[ed[i].v]){
    				q.push(node1(ed[i].v,dis[ed[i].v]));
    			}
    		}
    		
    	}
    }
    int main(){
    	
    return 0;
    } 

    有第二标尺的

    边权标尺(花费等,至于边权!=距离)

    int cost[maxn][maxn];
    int c[maxn];
    /*
    struct node{
    	int v;
    	int dis;
    };
    vector<node> G[maxn];
    int dis[maxn]={0};  */
    void dijkstra2(int st){
    	 fill(dis,dis+maxn,INF);
    	 dis[st]=0;
    	 fill(c,c+maxn,INF);
    	 c[st]=0;
    	 for(int i=0;i<numnode;i++){
    	 	int u=-1,mini=INF;
    	 	for(int j=0;j<numnode;j++){
    	 		if(vis[j]==0&&dis[j]<mini){
    	 			mini=dis[j];u=j;
    			 }
    		 }
    		 if(u==-1) return ;
    		 vis[u]=1;
    		 //以上为第一阶段 
    		 for(int j=0;j<G[u].size();j++){
    		 	int v=G[u][j].v;
    		 	if(vis[v]==0){
    		 		if(dis[v]>dis[u]+G[u][j].dis){
    		 			dis[v]=dis[u]+G[u][j].dis;
    		 			c[v]=c[u]+cost[u][v];
    				 }
    		 		else if(dis[v]==dis[u]+G[u][j].dis&&c[v]>c[u]+cost[u][v]){
    		 			c[v]=c[u]+cost[u][v]; //因为是花费,所以越小越好 
    				 }
    			 }
    			 
    		 }
    		 
    	 }
    }
    

    点权(例如资源等)越多越好 

    int weight[maxn];
    int w[maxn];
    /*
    struct node{
    	int v;
    	int dis;
    };
    vector<node> G[maxn];
    int dis[maxn]={0};  */
    void dijkstra3(int st){
    	fill(w,w+maxn,0);   //注意不同:点权的其他不等于起点的都赋值位0,边权赋值位无穷大 
    	w[st]=weight[st];
    	fill(dis,dis+maxn,INF);
    	dis[st]=0; 
    	//初始化阶段 
    	for(int i=0;i<numnode;i++){
    		int u=-1,mini=INF;
    		for(int j=0;j<numnode;j++){
    			if(vis[j]==0&&dis[j]<mini){
    				mini=dis[j];u=j;
    			}
    		}
    		if(u==-1) return ;
    		vis[u]=1;
    		//以上为第一阶段 
    		for(int j=0;j<G[u].size();j++){
    			int v=G[u][j].v;
    			if(vis[v]==0){
    				if(dis[v]>dis[u]+G[u][j].dis){
    					dis[v]=dis[u]+G[u][j].dis;
    					w[v]=w[u]+weight[v];
    				}
    				else if(dis[v]==dis[u]+G[u][j].dis&&w[v]<w[u]+weight[v]){
    					w[v]=w[u]+weight[v]
    				}
    			}
    		}	
    	}
    }
    

    路径条数

    int num[maxn]; //就多这一个数组 
    /*
    struct node{
    	int v;
    	int dis;
    };
    vector<node> G[maxn];
    int dis[maxn]={0};  */
    void dijkstra4(int st){
    	fill(num,num+maxn,0);   //与点权一样:与起点不同的都赋值为0;起点为1 
     	num[st]=1;
    	fiil(dis,dis+maxn,INF);
    	dis[st]=0;
    	for(int i=0;i<numnode;i++){
    		int u=-1,mini=INF;
    		for(int j=0;j<numnode;j++){
    			if(vis[j]==0&&mini>dis[j]){
    				mini=dis[j];u=j;
    			}
    		}
    		if(u==-1) return ;
    		vis[u]=1;
    		for(int j=0;j<G[u].size();j++){
    			int v=G[u][j].v;
    			if(vis[v]==0){
    				if(dis[v]>dis[u]+G[u][j].dis){
    					dis[v]=dis[u]+G[u][j].dis;
    					num[v]=num[u];   //继承 
    				}
    				else if(dis[v]==dis[u]+G[u][j].dis){
    					num[v]+=num[u];  //加上 
    				}
    			}
    		}
    	}
    }
    

    第二标尺不满足最优子结构时,需要改变算法,即不能在Dijkstra的算法过程中直接求出最优而是应该先求出所有的最优路径,然后选择第二标尺最优的那条路。所以采用Dijkstra+DFS的方法,Dijkstra求出所有的最优路径,DFS求出第二标尺最优的

    所以改变是pre[maxn]---vector<int> pre[maxn]‘

    vector<int> pre[maxn];
    void dijkstra5(int st){
    	fill(dis,dis+maxn,INF);
    	dis[st]=0;
    	for(int i=0;i<numnode;i++){
    		int u=-1,mini=INF;
    		for(int j=0;j<numnode;j++){
    			if(vis[j]==0&&mini>dis[j]){
    				mini=dis[j];u=j;
    			}
    		}
    		if(u==-1) return ;
    		vis[u]=1;
    		//以上为第一阶段
    		//改变的是下面的第二阶段,在记录最优路径的时候
    		for(int j=0;j<G[u].size();j++){
    			int v=G[u][j].v;
    			if(dis[v]>dis[u]+G[u][j].dis){
    				dis[v]=dis[u]+G[u][j].dis;
    				pre[v].clear();
    				//先清空
    				pre[v].push_back(u); 
    			}
    			else if(dis[v]==dis[u]+G[u][j].dis){
    				//如果距离一样 ,就压入
    				pre[v].push_back(u); 
    			}
    		} 		
    }
    } 
    
    	//接下来找出第二标尺最优的那个路径 
    	//当画出这个路径时,会发现是一颗树的结构,根节点是终点,叶子节点都是起点(所以在有些情况下需要逆序),这样走下来找到最优标尺
    	//因为有多条路径,每次决定走哪条,所以用递归搜索+回溯的方法
    	//有一点绝对要注意,因为最后的叶子节点(起点)无法自己入数组,所以需要自己碰到叶子节点是把它push进来 
    vector<int> temppath,path;  //一个用来临时存路径,一个用来存最优路径 
    int maxvalue;
    void DFS(int st,int v){
    	if(v==st){
    		temppath.push_back(v);
    		//计算这条路径上的最优路径值
    		int value=0;
    		//eg:边权值和
    		for(int i=temppath.size();i>0;i--){               //这两个例子其实都满足最优子结构,可以直接用dijkstra来解,但是这个通用模板必须记住 
    			//计算边权值和,边界时i>0;
    			int now=temppath[i],next=temppath[i-1];
    			value+=G[now][next].dis;
    		} 
    		//eg:点权值和
    		for(int i=temppath.size();i>=0;i--){
    			//计算点权值和:边界为i>=0
    			int id=temppath[i];
    			value+=weight[id]; 
    		}
    		 if(value>maxvalue){
    		 	maxvalue=value;
    		 	path=temppath;
    		 }
    		 //记录最优路径
    		 //不要忘记弹出噢!!!
    		 temppath.pop_back(); 
    		 return;
    		 //以及return噢!~ 
    	}
    	temppath.push_back(v);
    	for(int i=0;i<pre[v].size();i++){
    		DFS(st,pre[v][i]);
    	}
    	temppath.pop_back();
    	//也不要忘记弹出回溯噢!!!~ 
    }
    

    Bellman-ford:O(NM),

    对边进行遍历。不能有负权回路,但是能提示,可用循环队列。

    但是如果从原点无法到达负环的话,是不会有有影响的。

    可以处理负边权,再进行以此松弛操作既可以判断是不是存在负环 、所有的边进行操作,看能不能通过这条边来进行优化

    最短路径树:层数不超过V,源点s作为根节点,其他节点按照最短路径的节点顺序连接

    注意求路径数的时候,vector<int> pre[maxn]要改为set<int> pre[maxn]

    bool ford(int s){
    	fill(dis,dis+maxn,INF);
    	dis[s]=0;
    	//n是节点个数,因为最后是一棵树,所以边数为n-1即一共只需要n-1次循环
    	for(int i=0;i<n-1;i++){
    		for(int j=0;j<n;j++){
    			for(int z=0;z<adj[j].size();z++){
    				int v=adj[j][z].v;
    				int di=adj[j][z].dis;
    				if(di+dis[j]<dis[v]) dis[v]=dis[j]+di; 
    			}
    		}
    	}
    	//再遍历以下所有的边,看还能不能松弛 
    	for(int i=0;i<n;i++){
    		for(int j=0;j<adj[i].size();j++){
    			int v=adj[i][j].v;
    			int di=adj[i][j].dis;
    			if(dis[v]>dis[i]+di)  return 0;
    		}
    	} 
    	return 1;
    }
    //如果用ford算法求解路径的话
    //需要用
    set<int>  pre[maxn];
    int num[maxn];
    if(dis[v]>dis[j]+di){
    	dis[v]=dis[j]+di;
    	num[v]=num[j]; //直接覆盖
    	pre[v].clear(); 
    	pre[v].insert(j);
    } 
    else if(dis[v]==dis[j]+di){
    	pre[v].insert(j); //先插入
    	num[v]=0; //先付0
    	for(set<int>::iterator it=pre[v].begin();it!=pre[v].end();it++) num[v]+=num[*it]; //不是直接加*it啊 
    }
    
    

    SPFA:ford的队列实现,单源最短路径,与BFS的区别:出了队的可以再次入队。对ford的优化:只有最短路改变了的才可能继续改变其他的节点的最短路,所以没必要访问全部。

    判断有无负环的方法是计算每个节点的入队次数,如果入队次数超过n就存在负环了

    SPFA的两种优化:SLF和LLL

    SLF: Small Label First 策略. (比较常用)

    LLL: Large Label Last 策略. (不太常用)

     SPFA算法有两个优化算法 SLF 和 LLL: SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。 LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。个人觉得LLL优化每次要求平均值,不太好,为了简单,我们可以之间用c++STL里面的优先队列来进行SLF优化。

    int vis[maxn];//这是用来记录是不是在队列里面的
    int num[maxn]; //记录入队次数(如果说明不存在负环就不需要这个)
    bool spfa(int s){
    	fill(dis,dis+maxn,INF);
    	dis[s]=0;
    	vis[s]=1;
    	num[s]++; //入队次数+1
    	queue<int> q;
    	q.push(s);
    	while(!q.empty()){
    		int top=q.front();
    		q.pop();
    		vis[top]=0; //出队了
    		//接着访问这个节点的所有邻接边
    		for(int i=0;i<adj[top].size();i++){
    			int v=adj[top][i].v;
    			int diss=adj[top][i].dis;
    			if(dis[v]>dis[top]+diss) {
    				dis[v]=dis[top]+diss;
    				//先松弛,然后判断能不能入队
    				if(!vis[v]){
    					q.push(v);
    					vis[v]=1;
    					num[v]++;
    					if(num[v]>=n) return 0;
    				} 
    			}
    		} 
    	}
    	return 1; 
    } 

    Floyd:O(N^3),全源最短,可以处理负边权,可以判断负环

    负环判断:初始化所有的dp[i][i]=0后,如果结束时dp[i][i]<0,那么就存在负环

    //n在200以内
    //在main()函数里面先执行:
    for(int i=0;i<n;i++) dis[i][i]=0;
    //然后是函数体
    void floyd(){
    	for(int k=0;k<n;k++){
    		for(int i=0;i<n;i++){
    			for(int j=0;j<n;j++){
    				if(dis[i][k]!=INF&&dis[k][j]!=INF&&dis[i][k]+dis[k][j]<dis[i][j])
    				dis[i][j]=dis[i][k]+dis[k][j];
    			}
    		}
    }
    }  

     floyd求无向图最小环

    因为Floyd是按照结点的顺序更新最短路的,所以我们在更新最短路之前先找到一个连接点k,当前的点k肯定不存在于已存在的最短路f[i][j]的路径上,因为我们还没用这个k去更新最短路,
    相当于 (i -> k -> j -> j到i的最短路 -> i)这样一个环就找到了,接下来我们要记录路径,用path[i][j]表示在最短路i到j的路径上j的前一个结点,所以我们在更新最短路时也要更新这个点,
    原来的最短路是i -> j,现在变成了 i -> k -> j,所以有per[i][j] = pre[k][j],因为要找最小环,所以不断更新找到环的权值,环更新一次,路径也要更新一次,
    路径更新时根据pre数组迭代一下就ok了

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=310;
    const int INF= 0x3f3f3f3f;
    typedef long long LL;
    //无向图最小环问题
    //用Floyd求
    /*
    因为Floyd是按照结点的顺序更新最短路的,所以我们在更新最短路之前先找到一个连接点k,当前的点k肯定不存在于已存在的最短路f[i][j]的路径上,因为我们还没用这个k去更新最短路,
    相当于 (i -> k -> j -> j到i的最短路 -> i)这样一个环就找到了,接下来我们要记录路径,用path[i][j]表示在最短路i到j的路径上j的前一个结点,所以我们在更新最短路时也要更新这个点,
    原来的最短路是i -> j,现在变成了 i -> k -> j,所以有per[i][j] = pre[k][j],因为要找最小环,所以不断更新找到环的权值,环更新一次,路径也要更新一次,
    路径更新时根据pre数组迭代一下就ok了
    */
    //具体讲解:https://www.cnblogs.com/Yz81128/archive/2012/08/15/2640940.html#undefined
    //https://www.cnblogs.com/qwerta/p/9833391.html
    int n,m,ans=INF;
    int g[maxn][maxn];
    int dis[maxn][maxn];
    int pos[maxn][maxn];  //这个是放置中间节点的
    vector<int> path;  //存环
    void rad(){
        memset(g,0x3f,sizeof(g));
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++) g[i][i]=0;
        for(int i=1;i<=m;i++){
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            g[x][y]=g[y][x]=z;
        }
        memcpy(dis,g,sizeof(g));
    }
    void get_path(int i,int j){
        if(pos[i][j]==0) return;
        int k=pos[i][j];//k保存的只是i,j的中间节点,递归插入
        get_path(i,k);    //递归!!!
        path.push_back(k);
        get_path(k,j);
    }
    void floyd(){
        for(int k=1;k<=n;k++){   //k还没有用
        for(int i=1;i<k;i++){
            for(int j=i+1;j<k;j++){
                if(ans>(LL)(dis[i][j]+g[j][k]+g[k][i])){
                    ans=dis[i][j]+g[j][k]+g[k][i];  //找到了最小环
                    path.clear();  //就可以清空路径,因为有更好的了
                    path.push_back(i);
                    get_path(i,j);
                    path.push_back(j);
                    path.push_back(k);  //后面的两个自己放进去
                }
            }
        }  
        for(int i=1;i<=n;i++){   //用k来更新,就是普通的floyd算法了,顺便保存一下Pos数组
            for(int j=1;j<=n;j++){
                if(dis[i][j]>dis[i][k]+dis[k][j]){
                    dis[i][j]=dis[i][k]+dis[k][j];
                    pos[i][j]=k;
                }
            }
        }
    }
    }
     
     
    int main(){
        rad();
        floyd();
        if(ans==INF){   //如果没有找到环
            printf("No solution.");
            return 0;
        }
        for(int i=0;i<path.size();i++) printf("%d ",path[i]);
    return 0;
    }

    第k最短路

    用到A*算法
    理解为第k次走到终点,相当于终点被找到k次,出队k次

    第一步是求出估价函数,dis【】数组,每个点到des的距离,这里就需要用到反图,因为这样才可以从des出发开始求

    第二步就是A*算法,用一个优先队列, 把估价函数和当前走的步数加起来就是评价函数f,这里直接在结构体里面重载比较符就可以了,然后遇到终点k次以后就可以了

    #include<queue>
    #include<cstdio>
    #include<cstring>
    #define N 1001
    #define M 100001
    using namespace std;
    int n,s,t,k;
    int dis1[N];
    bool vis[N];
    int front[N],to[M],nxt[M],val[M],tot;
    int front2[N],to2[M],nxt2[M],val2[M],tot2;
    struct node
    {
        int num,dis;
        bool operator < (node p) const
        {
            return dis+dis1[num]>p.dis+dis1[p.num];
        } 
    }now,nt;
    void add(int u,int v,int w)
    {
        to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; val[tot]=w;
        to2[++tot2]=u; nxt2[tot2]=front2[v]; front2[v]=tot2; val2[tot2]=w; 
    }
    void init()
    {
        int m,u,v,w;
        scanf("%d%d",&n,&m);
        while(m--)
        {
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
        }
        scanf("%d%d%d",&s,&t,&k);
    }
    void spfa()
    {
        memset(dis1,63,sizeof(dis1));
        queue<int>q;
        dis1[t]=0;
        vis[t]=true;
        q.push(t);
        int now;
        while(!q.empty())
        {
            now=q.front();
            q.pop();
            vis[now]=false;
            for(int i=front2[now];i;i=nxt2[i])
                if(dis1[to2[i]]>dis1[now]+val2[i])
                {
                    dis1[to2[i]]=dis1[now]+val2[i];
                    if(!vis[to2[i]])
                    {
                        q.push(to2[i]);
                        vis[to2[i]]=true;
                    }
                }
        }
    }
    void Astar()
    {
        if(dis1[s]>1e9) 
        {
            printf("-1");
            return;
        }    
        if(s==t) k++;
        int cnt=0,last=-1;
        priority_queue<node>q;
        now.num=s;
        now.dis=0;
        q.push(now);
        while(!q.empty())
        {
            now=q.top();
            q.pop();
            if(now.num==t) 
            {
                cnt++;
                if(cnt==k) 
                {
                    printf("%d",now.dis);
                    return;
                }
            }
            for(int i=front[now.num];i;i=nxt[i])
            {
                nt.num=to[i];
                nt.dis=now.dis+val[i];
                q.push(nt);
            }
        }
        printf("-1");
    }
    int main()
    {
        init();
        spfa();
        Astar();
    }
    
     
    

      

  • 相关阅读:
    Tomcat启动报Error listenerStart错误
    The type javax.xml.rpc.ServiceException cannot be resolved.It is indirectly
    (转)Android之Adapter用法总结
    利用脚本启动java程序
    Android中的基类—抽取出来公共的方法
    关于 Android 中未公开的类(用@hide隐藏的类)
    Android java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    gen already exists but is not a source folder
    如何注册java程序为windows服务
    Android Socket编程
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12489284.html
Copyright © 2020-2023  润新知