zoukankan      html  css  js  c++  java
  • 图论算法

    图的存储

    • 邻接矩阵
    int edg[1010][1010];//邻接矩阵
    int main()
    {
    	int n,m;
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
        	int x,y,z;
    		cin>>x>>y>>z;//x到y权值为z的边
            edg[x][y]=z;
       }
    }
    
    • 邻接表(链式前向星)
    #include <bits/stdc++.h>
    using namespace std;
    int head[100010];
    int ver[100010];
    int nxt[100010];
    int edg[100010];
    int tot=0;
    inline void add(int x,int y,int z)
    {
    	ver[++tot]=y;
    	edg[tot]=z;
        nxt[tot]=head[x];
        head[x]=tot;
    }
     
    
    • 邻接表(vector)
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<iostream>
    #include<vector>
    #define N 10000
    using namespace std;
    struct EDGE
    {
        int to;//终点
        int cost;//边的权值
    };
    vector<EDGE>G[N];//G[i]中i表示出发点
    int m,n;
    int temp1;//出发点
    int main()
    {
        scanf("%d%d",&n,&m);
        while(m--)
        {
            EDGE e;
            scanf("%d%d%d",&temp1,&e.to,&e.cost);//输入出发点,终点,边的权值
            G[temp1].push_back(e);//将数据压入动态数组,表示在这个出发点下引出的边
            //相当于二维动态数组
        }
        return 0;
    }
    
    

    Floyd算法

    例题(一本通P1342)

    (O(n^3))

    设状态(f[k][i][j]):从i到j通过前k个点中的若干个的最短路径和
    对于第k个中转点 :

    走:(f[k-1][i][k]+f[k-1][k][j])

    不走:(f[k-1][i][j])

    显然,可以压缩到二维

    #include <bits/stdc++.h>
    using namespace std;
    struct node
    {
    	int x,y;
    } V[10010];
    double adm[1010][1010];
    double floyd[1010][1010];
    int n,m;
    int s,t;
    
    int main()
    {
    	cin>>n;
    	for(int i=1;i<=n;i++)
    		cin>>V[i].x>>V[i].y;
    	cin>>m;
    	for(int i=0;i<=n;i++)
    	{
    		for(int j=0;j<=n;j++)
    		{
    			if(i==j) 
    			{
    				adm[i][j]=0;//到自己的距离为0 
    			}
    			else adm[i][j]=0x3f3f3f3f;//初始化 
    		}
    	}
    	for(int i=1;i<=m;i++)
    	{
    		int a,b;
    		cin>>a>>b;
    		adm[a][b]=adm[b][a]=min(sqrt((V[a].x-V[b].x)*(V[a].x-V[b].x)+(V[a].y-V[b].y)*(V[a].y-V[b].y)),adm[a][b]);//松弛 
    	}
    	cin>>s>>t;
    	for(int k=1;k<=n;k++)
    	{
    		for(int i=1;i<=n;i++)
    		{
    			for(int j=1;j<=n;j++)
    			{
    				if(i!=j&&i!=k&&k!=j) 
    				{
    					adm[i][j]=min(adm[i][j],adm[i][k]+adm[k][j]); //状转方程 
    				}
    			}
    		}
    	}
    	cout<<fixed<<setprecision(2)<<adm[s][t];
    	return 0;
    }
    

    Dijkstra算法(不适用负权图)

    基本思想:

    1. 将图上的初始点看作一个集合S,其它点看作另一个集合

    2. 根据初始点,求出其它点到初始点的距离d[i] (若相邻,则d[i]为边权值;若不相邻,则d[i]为无限大)

    3. 选取最小的d[i](记为d[x]),并将此d[i]边对应的点(记为x)加入集合S

    (实际上,加入集合的这个点的d[x]值就是它到初始点的最短距离)

    4. 再根据x,更新跟 x 相邻点 y 的d[y]值:d[y] = min{ d[y], d[x] + 边权值w[x][y] },因为可能把距离调小,所以这个更新操作叫做松弛操作。

    (仔细想想,为啥只更新跟x相邻点的d[y],而不是更新所有跟集合 s 相邻点的 d 值? 因为第三步只更新并确定了x点到初始点的最短距离,集合内其它点是之前加入的,也经历过第 4 步,所以与 x 没有相邻的点的 d 值是已经更新过的了,不会受到影响)

    5. 重复3,4两步,直到目标点也加入了集合,此时目标点所对应的d[i]即为最短路径长度。
    原文链接

    时间复杂度(O(n^2))

    #include <bits/stdc++.h>
    using namespace std;
    const long long int INF=(1<<31)-1;
    int edg[2020][2020];//邻接矩阵
    int dis[2020];//记录距离
    int vis[2020];//集合标记
    int n /*点*/,m /*边*/; 
    int sta/*起点*/,end/*终点*/;
    
    int main()
    {
    	cin>>n>>m>>sta;
    	memset(dis,0x3f,sizeof(dis));
    	memset(vis,0,sizeof(vis));
    	memset(edg,0x3f,sizeof(edg));
    	for(int i=1;i<=m;i++)
    	{
    		int x/*起点*/,y/*终点*/,z/*边权*/;
    		cin>>x>>y>>z;
    		edg[x][y]=min(z,edg[x][y]);//有向图写法 
    	}
    	dis[sta]=0;
    	for(int i=1;i<=n;i++)
    	{
    		int k=-1;
    		for(int j=1;j<=n;j++)
    		{
    			if(vis[j]==0&&(k==-1||dis[k]>dis[j]))//找到最短的dis 
    				k=j;
    		}
    		vis[k]=1;//加入集合(即打上标记 
    		for(int j=1;j<=n;j++)
    		{
    			if(!vis[j])
    				dis[j]=min(dis[j],dis[k]+edg[k][j]);
    		}//更新相邻点的dis(未相邻的点的 edg 值为无穷大,不会更新 
    	}
    	for(int i=1;i<=n;i++)
    		if(dis[i]>=0x3f3f3f3f/2)
    			cout<<INF<<" ";//无解处理 
    		else cout<<dis[i]<<" ";
    	return 0;
    }
    

    堆优化版Dijkstra

    直接使用堆(优先队列)来找最短dis的点

    同时使用邻接表(vector或链式前向星实现)降低空间消耗

    单次时间复杂度(O(nlogn))

    #include <bits/stdc++.h>
    using namespace std;
    const long long int INF=(1<<31)-1;
    int head[1000010];//表头目录 
    int ver[1000010];//右节点目录 
    int edge[1000010];//边权值 
    int nxt[1000010];//第一个与它相连的点的下标 
    int tot;//邻接表节点个数 
    int vis[1000010];//集合标记
    int dis[1000010];//距离记录 
    int n /*点*/,m /*边*/; 
    int sta/*起点*/;
    priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;/*第一维距离,第二维编号*/
    inline void add(int x,int y,int z)//建邻接表 
    {
    	ver[++tot]=y;
    	edge[tot]=z;
    	nxt[tot]=head[x];//下一节点 
    	head[x]=tot;
    }
    int main()
    {
    	cin>>n>>m>>sta;
    	memset(dis,0x3f,sizeof(dis));
    	memset(vis,0,sizeof(vis));
    	for(int i=1;i<=m;i++)
    	{
    		int x,y,z;
    		cin>>x>>y>>z;
    		add(x,y,z);
    	}
    	dis[sta]=0;
    	q.push(make_pair(0,sta));//首元素入队列 
    	while(!q.empty())
    	{
    		int x=q.top().second;//利用优先队列(堆)直接找到最短dis的点 
    		q.pop();
    		if(vis[x]) continue;
    		vis[x]=1;//加入集合 
    		for(int i=head[x];i;i=nxt[i])//链表遍历 
    		{
    			int y=ver[i],z=edge[i];
    			if(dis[y]>dis[x]+z)
    			{
    				dis[y]=dis[x]+z;
    				q.push(make_pair(dis[y],y));//更新元素入队 
    			}
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		cout<<dis[i]<<" ";
    	}
    	return 0;
    }
    

    SPFA

    它死了 (天天被卡

    (SPFA)(Shortest Path Faster Algorithm) [图的存储方式为邻接表]

    (Bellman-Ford)算法的一种队列实现,减少了不必要的冗余计算。

    算法大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,

    并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。

    时间复杂度最坏为(O(VE))(极不稳定容易被卡)

    #include <bits/stdc++.h>
    using namespace std;
    const int INF=(1<<31)-1;
    int head[1000010];
    int nxt[1000010];
    int edge[1000010];
    int ver[1000010];
    int tot;//邻接表
    int dis[1000010];//记录最短路
    int vis[1000010];//记录是否入队列
    int num[1000010];//记录入队次数
    int n/*点数*/,m/*边数*/,q;
    inline void add(int x,int y,int z)
    {
    	ver[++tot]=y;
    	edge[tot]=z;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    bool SPFA(int s)//传参传源点编号 
    {
    	queue<int> q;
    	memset(dis,0x3f,sizeof(dis));
    	memset(vis,0,sizeof(vis));
    	memset(num,0,sizeof(num));
    	dis[s]=0;//初始化源点距离
    	vis[s]=1,num[s]++;//标记源点
    	q.push(s);//源点入队
    	while(!q.empty()) 
    	{
    		int x=q.front();
    		q.pop();
    		vis[x]=0;//出队取消标记 
    		for(int i=head[x];i;i=nxt[i])
    		{
    			int y=ver[i],z=edge[i];
    			if(dis[y]>dis[x]+z)
    			{
    				dis[y]=dis[x]+z;
    //				cout<<x<<' '<<y<<' '<<dis[y]<<' '<<dis[x]<<endl;
    				if(!vis[y]) //当前点不在队列中 
    				{
    					q.push(y);//入队 
    					vis[y]=1;//标记 
    					num[y]++;
    					if(num[y]>n) return false;//如果这个点入队超过n次,判负环 
    				}
    			}
    		}
    	}
    	return true;
    }
    int main()
    {
    	cin>>n>>m>>q;
    	for(int i=1;i<=m;i++)
    	{
    		int x,y,z;
    		cin>>x>>y>>z;
    		add(x,y,z);
    	}
    	bool flag=SPFA(q);
    	if(flag==0) 
    	{
    		cout<<INF;
    		return 0;
    	}
    	for(int i=1;i<=n;i++)
    	{
    		if(dis[i]<0x3f3f3f3f) cout<<dis[i]<<" ";
    		else cout<<INF<<' ';
    	}
    	return 0; 
    }
    

    拓补排序

    对一个有向无环图 (Directed Acyclic Graph简称DAG) (G)进行拓扑排序,是将(G)中所有顶点排成一个线性序列,使得图中任意一对顶点(u)(v),若边(<u,v>∈E(G)),则(u)在线性序列中出现在(v)之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。

    拓扑排序算法主要是循环执行以下两步,直到不存在入度为(0)的顶点为止。

    (1).选择一个入度为(0)的顶点并输出之;

    (2).从网中删除此顶点及所有出边。

    循环结束后,若输出的顶点数小于网中的顶点数,则输出“有回路”信息,否则输出的顶点序列就是一种拓扑序列。

    注意:这里得到的排序并不是唯一的! 就好像你早上穿衣服可以先穿上衣也可以先穿裤子,只要里面的衣服在外面的衣服之前穿就行。

    #include <bits/stdc++.h>
    using namespace std;
    int head[1000010];
    int ver[1000010];
    int nxt[1000010];
    int tot=0;
    int n,m;
    int num[1000010];
    int ind[1000010];
    int oud[1000010];
    int all=0;
    queue<int> q;
    inline void add(int x,int y)//链式前向星存图 
    {
        ver[++tot]=y;
        nxt[tot]=head[x];
        head[x]=tot;
    }
    void topo()//拓补核心函数 
    {
    	while(!q.empty())
    	{
    		int x=q.front();
    		q.pop();
    		all++;//记录出队标号 
    		num[all]=x;
    		for(int i=head[x];i;i=nxt[i])
    		{
    			int y=ver[i];
    			ind[y]--;//删除出边,则出边所连接点的入度-1
    			if(ind[y]==0) q.push(y);//入度为 0,入队准备扩展 
    		}
    	}
    	if(all==n)
    	{
    		for(int i=1;i<=all;i++)
    			cout<<num[i]<<" ";//输出序列 
    	}
    	else cout<<"有回路"; 
    }
    int main()
    {
    	cin>>n>>m;
    	for(int i=1;i<=m;i++)
    	{
    		int x,y;
    		cin>>x>>y;
    		add(x,y);
    		ind[y]++;oud[x]++;//统计出入度 
    	}
    	for(int i=1;i<=n;i++)
    	{
    		if(ind[i]==0)
    			q.push(i);
    	}
    	topo();
    	return 0;
    }
    

    未完待续......

    ------------------------------------------------------------------------------少女祈祷中......

  • 相关阅读:
    C# 保存图片文件异常--文件名、目录名或卷标语法不正确。
    NPOI 导出Excel WPS格式正常 Office格式异常
    ionic 项目安装依赖出现以下错误
    关于升级npm 出现 “Refusing to delete C:UsersltAppDataRoaming pm px.cmd:”
    关于 NPOI 单元的样式CellStyle问题
    c# 字符串的比较大小
    c# 根据路径获取文件信息以及删除文件
    Eclipse 快捷键大全
    Smarty 的安装
    js实现页面跳转的几种方式
  • 原文地址:https://www.cnblogs.com/IzayoiMiku/p/12928228.html
Copyright © 2011-2022 走看看