一些基础的图论知识

图论

以图为研究对象,图论中的图是由若干给定的点及连接两点的线所构成的图形,用来描述某些实体之间的某种特定关系,用点代表实体,用连接两点的线表示两个实体间具有的某种关系,细分有有向图、无向图,有环图、无环图,连接上可以有权值或无权值,重点在表示连接关系、处理的方法上

连接表示方法

1.邻接矩阵

2.邻接表

3.链式前向星

拓扑排序

P4017 最大食物链计数
#include<bits/stdc++.h>
using namespace std;

const int N=5005,MOD=80112002;
int n,m,dp[N],out[N],in[N];
bool reflect[N][N];

signed main()
{
	cin>>n>>m;
	int a,b;
	for(int i=1;i<=m;i++)
	{
		cin>>a>>b;
		in[b]++;
		out[a]++;
		reflect[a][b]=1;
	}
	int marki;
	queue<int> q;
	for(int i=1;i<=n;i++)
	{
		if(in[i]==0)
		{
			marki=i;//可能有多个 
			q.push(i);
			dp[i]=1;
		}
	}
	int ans=0;
	while(!q.empty())//拓扑排序的思想
	{
		int t=q.front();
		q.pop();
		for(int k=1;k<=n;k++)
		{
			if(reflect[t][k])
			{
				dp[k]=(dp[k]+dp[t])%MOD;
				in[k]--;
				if(in[k]==0)
				{
					q.push(k);
					if(out[k]==0)
					{
						ans=(ans+dp[k])%MOD;//这里可能有多个最底层 
					}
				}
			}
		}
	}
	cout<<ans;
	return 0;
}
P2712 摄像头
#include<bits/stdc++.h>
using namespace std;

int n,in[550],cnt;
bool matrix[550][550],film[550];

int main()
{
	cin>>n;
	int a,b,t,maxn=0;
	for(int i=1;i<=n;i++)
	{
		cin>>a>>t;
		film[a]=1;
		for(int j=0;j<t;j++)
		{
			cin>>b;
			matrix[a][b]=1;
			in[b]++;
		}
		maxn=max(maxn,a);
	} 
	queue<int> q;
	for(int i=0;i<=maxn;i++)
	{
		if(in[i]==0&&film[i]==1)
		{
			q.push(i);
		}
	}
	while(!q.empty())
	{
		int c=q.front();
		q.pop();
		cnt++;
		for(int k=0;k<=maxn;k++)
		{
			if(matrix[c][k]&&film[k]==1)
			{
				in[k]--;
				if(in[k]==0)
				{
					q.push(k);
				}
			}
		}
	}
	if(cnt==n) cout<<"YES";
	else cout<<n-cnt;
	return 0;
}
P1122 最大子树和
#include<bits/stdc++.h>
using namespace std;//无向的

const int N=16010;
int n,dp[N],out[N],head[N*2],cnt,maxi=1;
bool used[N];

struct Edge
{
	int u,v,next;
}edge[N*2];

void add(int u,int v)//链式前向星 
{
	edge[cnt].u=u;
	edge[cnt].v=v;
	edge[cnt].next=head[u];
	head[u]=cnt++;
}

int main() 
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		head[i]=-1;
	}
	for(int i=1;i<=n;i++)
	{
		cin>>dp[i];
	}
	int b,c;
	for(int i=1;i<n;i++)
	{
		cin>>b>>c;
		add(b,c);
		add(c,b);
		out[b]++;
		out[c]++;
	}
	for(int i=1;i<=n;i++)
	{
		if(out[i]>out[maxi]) maxi=i;
	}
	//cout<<maxi<<endl;
	queue<int> q;
	for(int i=1;i<=n;i++)
	{
		if(out[i]==1)
		{
			q.push(i);
			used[i]=1;
		}
	}
	while(!q.empty())
	{
		int t=q.front();
		q.pop();
		//cout<<" t的值是"<<t<<endl;
		for(int i=head[t];i!=-1;i=edge[i].next)
		{
			//cout<<" i取了"<<i<<endl;
			int num=edge[i].v;
			//cout<<" numshi "<<num<<endl;
			if(used[num]==0)
			{
				out[num]--;
				dp[num]=max(dp[num],dp[num]+dp[t]);
				//cout<<"dp[i]为 "<<dp[num]<<endl;
				if(out[num]==0||(num!=maxi&&out[num]==1))
				{
					q.push(num);
					used[num]=1;
				}
			}
		}
	}
	int maxn=-2000000;
	for(int i=1;i<=n;i++)
	{
		maxn=max(maxn,dp[i]);
	}
	cout<<maxn;
	return 0;
}

最短路径算法

有Floyd算法,Bellman-Ford算法、SPFA算法和Dijkstra算法

图的规模小,用Floyd。若边的权值有负数,需要判断负圈;图的规模大,且边的权值非负,用Dijkstra;图的规模大,且边的权值有负数,用SPFA,需要判断负圈。Floyd、dijkstra是优选算法,分别解决多源对多源问题,单源对多源问题,反向建图可以达到多源对单源,bellman-ford可以用来判断负环

Floyd-Warshall

允许有负数,结点n<300,用邻接矩阵,重点在不一定是单源

P3371 【模板】单源最短路径(弱化版)
#include<bits/stdc++.h>
using namespace std;
#define int long

const int INF=2147483647;
int n,m,s;
int dp[310][310];

signed main()
{
	cin>>n>>m>>s;
	int a,b,c;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			dp[i][j]=INF;
		}
	}
	for(int i=0;i<m;i++)
	{
		cin>>a>>b>>c;
		dp[a][b]=min(dp[a][b],c);//注意初始化取最短边细节,可能会有重边
	}
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++)
		{
			if(dp[i][k]!=INF)
			{
				for(int j=1;j<=n;j++)
				{
					if(dp[k][j]!=INF)
					{
						dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
					}
				}
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(s==i) cout<<"0 ";
		else cout<<dp[s][i]<<" ";
	}
	return 0;
}

判断负环可以用自己连结为0,再后来变成负数看出

Bellman-Ford

用来解决单源路径问题,n*m<10^7,使用邻接表

P3371 【模板】单源最短路径(弱化版)
#include<bits/stdc++.h>
using namespace std;
#define int long long

const long M=1e5+10,N=1e3+10,INF=2147483647;
int n,m,s;
struct Edge
{
	int u,v,w;
}edge[M];
int d[N];//pre是连接的前驱节点,d是连接的距离 

signed main()
{
	cin>>n>>m>>s;
	for(int i=0;i<m;i++)
	{
		cin>>edge[i].u>>edge[i].v>>edge[i].w;
	}
	for(int i=1;i<=n;i++)
	{
		d[i]=INF;
	}
	d[s]=0;
	for(int i=1;i<=n;i++)
	{
		for(int k=0;k<m;k++)
		{
			int x=edge[k].u,y=edge[k].v;
			d[y]=min(d[y],d[x]+edge[k].w);
			//cout<<"x="<<x<<" d[x]="<<d[x]<<" y="<<y<<" d[y]="<<d[y]<<endl;
			//cout<<d[y]+edge[k].w<<endl;
		}
	}
	for(int i=1;i<=n;i++)
	{
		cout<<d[i]<<" ";
	}
	return 0;
}
SPFA

用邻接表和链式前向星都行,用于nm大于上述规则的题目,但是SPFA算法不稳定,最坏情况O(mn)

P3371 【模板】单源最短路径(弱化版)
#include<bits/stdc++.h>
using namespace std;
#define int long

int a=pow(2,31)-1;
const int N=1e5+10,M=2e5+10,INF=a;
struct Edge
{
	int from,to,w;
	Edge(int a,int b,int c)//内构函数
	{
		from=a;
		to=b;
		w=c; 
	}
};
vector<Edge> e[N];
int n,m,s;

signed main()
{
	cin>>n>>m>>s;
	for(int i=0;i<m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		e[a].push_back(Edge(a,b,c));//vector的用法,push_back 
	}
	int d[N];
	bool inq[N];
	for(int i=1;i<=n;i++)
	{
		d[i]=INF;
		inq[i]=false;
	}
	d[s]=0;
	queue<int> q;
	q.push(s);
	inq[s]=true;
	while(!q.empty())
	{
		int t=q.front();
		q.pop();
		inq[t]=false;
		for(int i=0;i<e[t].size();i++)
		{
			int v=e[t][i].to,wei=e[t][i].w;//二维数组,存储了结构体变量 
			if(d[t]+wei<d[v])
			{
				d[v]=d[t]+wei;
				if(!inq[v])
				{
					inq[v]=true;
					q.push(v);
				}
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		cout<<d[i]<<" ";
	}
	return 0;
}
Dijkstra

最高效的最短路径算法,使用了贪心的思想

#include<bits/stdc++.h>
using namespace std;
#define int long

long a=pow(2,31)-1;
const int N=1e5+10,M=2e5+10;
const long INF=a;
struct Edge
{
	int from,to,w;
	Edge(int a,int b,int c)//内构函数
	{
		from=a;
		to=b;
		w=c;
	}
};

struct s_node
{
	int id,dis;
	s_node(int b,int c)//内构函数初始化方便 
	{
		id=b;
		dis=c;
	}
	bool operator<(const s_node &a) const//操作符重载
	{
		return dis>a.dis;
	}
};

vector<Edge> e[N]; 
long n,m,s,d[N],done[N];

void dijkstra()
{
	for(int i=1;i<=n;i++)
	{
		d[i]=INF;
	}
	d[s]=0;
    priority_queue<s_node,vector<s_node>,less<s_node>> Q;
	Q.push(s_node(s,d[s]));
	while(!Q.empty())
	{
		s_node u=Q.top();
		Q.pop();
		if(done[u.id])
		{
			continue;
		}
		done[u.id]=true;
		for(int i=0;i<e[u.id].size();i++)
		{
			Edge y=e[u.id][i];
			if(done[y.to])
			{
				continue;
			}
			if(d[y.to]>y.w+u.dis)
			{
				d[y.to]=y.w+u.dis;
				Q.push(s_node((y.to),d[y.to]));
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		cout<<d[i]<<" ";
	}
}

signed main()
{
	cin>>n>>m>>s;
	for(int i=0;i<m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		e[a].push_back(Edge(a,b,c));//vector的用法,push_back
	}
	dijkstra();
	return 0;
}

如果计算从各个节点到一个节点的情况,可以用反向图来做

最小生成树

用kruskal算法来做,同样的,最大生成树也是

分层图

#include<bits/stdc++.h>
using namespace std;

struct Edge
{
    int to,next,cost;
}edge[2500001];
int cnt,head[110005];

void add_edge(int u,int v,int c=0)
{
    edge[++cnt]=(Edge){v,head[u],c};
    head[u]=cnt;
}

int dis[110005];
bool vis[110005];
void Dijkstra(int s)
{
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > points;
    points.push(make_pair(0,s));
    while(!points.empty())
    {
        int u=points.top().second;
        points.pop();
        if(!vis[u])
        {
            vis[u]=1;
            for(int i=head[u];i;i=edge[i].next)
            {
                int to=edge[i].to;
                if(dis[to]>dis[u]+edge[i].cost) 
                {
                    dis[to]=dis[u]+edge[i].cost;
                    points.push(make_pair(dis[to],to));
                }
            }
        }
    }
}

int main()
{
    int n,m,k,s,t;
    cin>>n>>m>>k>>s>>t;
    int u,v,c;
    for(int i=0;i<m;++i)
    {
        cin>>u>>v>>c; 
        add_edge(u,v,c);
        add_edge(v,u,c);
        for(int j=1;j<=k;++j)
        {
            add_edge(u+(j-1)*n,v+j*n);
            add_edge(v+(j-1)*n,u+j*n);
            add_edge(u+j*n,v+j*n,c);
            add_edge(v+j*n,u+j*n,c);//连接分层图 
        }
    }
    for(int i=1;i<=k;++i)
		{
			add_edge(t+(i-1)*n,t+i*n);
		}
		Dijkstra(s);
    cout<<dis[t+k*n];
    return 0;
}

各种图

完全图

也称简单完全图。假设一个图有n个顶点,那么如果任意两个顶点之间都有的话,该图就称为完全图

连通图(无向图)、强连通图(有向图)

从顶点v到w有路径,就称顶点v和m连通。(路径是由顶点和相邻顶点序偶构成的边所形成的序列,其实就是一堆相连的顶点及其边)
如果图中任意俩顶点都连通,则该图为连通图

若从顶点v到m有路径,则称这俩顶点时强连通的。若任意一对顶点都是强连通的,称此图为强连通图

连通分量(无向图)、强连通分量(有向图)

与连通图对应,极大连通子图是无向图的连通分量,极大强连通子图是有向图的强连通分量

极大连通分量(无向图)极大强连通分量(有向图)

极大是要求该连通子图/强连通子图包含其所有的边

极小连通分量(无向图)极小强联通分量(有向图)

极小是在保持连通的情况下使边数最少的子图(暗指无向图),有向图中的极大强连通子图称为有向图的强连通分量

生成树

连通图的生成树是包含图中全部顶点的一个极小连通子图

生成森林

在非连通图中,连通分量的生成树构成了非连通图的生成森林
tarjan算法博主正在更新中

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值