数据结构-图论

DFS

连通

P352 连通分量(无向图),强连通分量(有向图)

题目(连通)pata1034 head of a gang

核心代码:

void dfs( int u,int& head,int &numMember,int &totalvalue )//head,numMember,totalvalue随每一次递推dfs改变,但它们最终结果以最后一次递推为准(不具备回溯)
{
    vis[u]=true;
    if( w[u] > w[head] ) head=u;
    numMember++;
    
    for(int i=0;i<index;i++)
    {
        if( G[u][i]>0 )//能从u点达到i点
        {
            totalvalue+= G[u][i];//能从u点达到i点就加上u~i边的边权,不要求i点未被访问过
            G[u][i]=G[i][u]=0;
            if(vis[i]==false) dfs(i,head,numMember,totalvalue);
        }
    }
}
void dfstrave()
{
    for(int i=0;i<index;i++)
    {
        if(vis[i]==false)
        {
            int head=i,numMember=0,totalvalue=0;
            dfs(i,head,numMember,totalvalue);
             if( numMember>2 && totalvalue>k )
             {
                gang[ int2str[head] ] = numMember;
             }
        }
    }
}

学习点 1:void dfs( int u,int& head,int &numMember,int &totalvalue )head,numMember,totalvalue随每一次递推dfs改变,但它们最终结果以最后一次递推为准(不具备回溯)
学习点2:题目需要加上连通图的所有边权,4号边的边权也不能漏,所以代码用的是if( G[u][i]>0 ) totalvalue+= G[u][i],而不是if( G[u][i]>0 && vis[i]==false ) totalvalue+= G[u][i]
在这里插入图片描述

最小生成树

MST性质

在这里插入图片描述
在这里插入图片描述

Prime算法

U集合 为落在最小生成树中的顶点,TE集合 为最小生成树中的边集合,V-U集合 为尚未落在最小生成树中的顶点集,在U集合和V-U集合之间找一条最小权值的边,然后将该边的另一端顶点并入到U集合中,并将该边加入到TE集合中。
在这里插入图片描述

kruskal算法

开始每个顶点自成一个连通分量,在保证不成环的情况下,不断地加入最小权值的边,直到所有点都在同一个连通分量下。
在这里插入图片描述

最短路径算法

Dijkstra算法

用于解决单源最短路径问题
基本思想:对图G(V,E)设置集合S,存放已被访问过的顶点(这些顶点已经被求出最短路径),然后每次从集合V-S中选择与起点s的最短路径最小的一个顶点(记为u),访问并加入集合S。之后令u为中介点,优化起点s与所有从u能到到的顶点v之间的最短距离。这样的操作执行n次(n为顶点个数),直到集合S已包含所有顶点。

邻接矩阵版

不要忘了初始化,G数组:inf,dist数组:inf

时间复杂度O(V^2)
注意输入:

fill(G[0],G[0]+maxn*maxn,inf);//别漏
for(int i=0;i<m;i++)
{
	int A,B,len;
	scanf("%d %d %d",&A,&B,&len);
	G[A][B]=min(len,G[A][B]);
	G[B][A]=G[A][B];//无向图
}

注意下面写法不要提前加上vis[s]=true,因为第一步就是将s点纳入S集合

int n,G[maxn][maxn];
int d[maxn];
bool vis[maxn]={false};
void dijkstra(int s)//s为起点
{

	fill(d,d+maxn,inf);
	d[s]=0;
	
	for(int i=0;i<n;i++)//循环n次
	{
		int u=-1,MIN=inf;
		for(int j=0;j<n;j++)//找到V-S集合中距离起点最近的u
		{
			if(vis[j]==false && d[j]<MIN)
			{
				u=j;
				MIN=d[j];
			}  
		}
		if(u==-1) return;//剩下的顶点与起点不连通
		vis[u]=true;//将u加入S集合中
		for(int v=0;v<n;v++)
		{
			//以u为中继节点做松弛操作
			if(vis[v]==false && G[u][v]!=inf && d[u]+G[u][v]<d[v] )
			{
				d[v]=d[u]+G[u][v];
			}
		}
		
	}
}

领接矩阵版改进

有时候题目有要求,比如在最短路径有多条的情况下,我们要求点权最小或者边权最小(边权不一定是距离),我们可以先记录所有的最短路径,再dfs出符合的情况。
因为存在d[u]+G[u][v]==d[v],所以前驱可能有多个,使用vector<int> pre[maxn]存储

vector<int> pre[maxn];
void dijkstra(int s)
{
	fill(d,d+maxn,inf);
	d[s]=0;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			int u=-1,MIN=inf;
			if( vis[j]==false && d[j]<MIN )
			{
				u=j;
				MIN=d[j];
			}
		}
		if(u==-1) return;
		vis[u]=true;
		for(int v=0;v<n;v++)
		{
			if(vis[v]==false && G[u][v]!=inf )
			{
				if(d[u]+G[u][v] < d[v])
				{
					d[v]=d[u]+G[u][v];
					pre[v].clear();//将v的前驱结点修改为u
					pre[v].push_back(u);
				}
				else if(d[u]+G[u][v] == d[v])
				{
					pre[v].push_back(u);//有多个前驱结点
				}
			}
		}
	}
}

关于后面如何dfs:
在这里插入图片描述
从尾结点开始dfs,递归的边界为到达路径的起点,到达边界后算出此时到 value,如果该value优于记录的optvalue,保存该路径并更新optvalue.

题目

pata1003 Emergency

题意:给出N个城市,M条无向边。每个城市都有一定的救援小队,所有边的边权已知。求起点到终点的最短路径条数以及最短路径上的救援小组数目之和(多条最短路径取最大的)
num[i]表示:从起点s到达顶点i的最短路径条数,初始num[s]=1,其余num[i]为0。
w[i]表示:最短路径上,起点s到达顶点i的点权和。

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=510;
int n,m;
int G[maxn][maxn];
int d[maxn];
bool vis[maxn]={false};
int c1,c2;//起点,终点
int val[maxn];//记录点权
int num[maxn];//记录起点到i之间最短路径的条数
int w[maxn];//最短路径情况下,记录起点到i之间的最大权值
void dijkstra(int s)
{
	fill(d,d+maxn,inf);
    memset(num,0,sizeof num);
    memset(w,0,sizeof w);
	d[s]=0;
    //初始化num和w
    num[s]=1;
    w[s]=val[s];
	
	for(int i=0;i<n;i++)
	{
		int u=-1,MIN=inf;
		for(int j=0;j<n;j++)
		{
			if(vis[j]==false && d[j]<MIN)
			{
				u=j;
				MIN=d[j];
			}  
		}
		if(u==-1) return;
		vis[u]=true;
		for(int v=0;v<n;v++)
		{
			//以u为中继节点做松弛操作
			if(vis[v]==false && G[u][v]!=inf )
			{
                if( d[u]+G[u][v]<d[v] )
                {
                    d[v]=d[u]+G[u][v];
                    num[v]=num[u];
                    w[v]=w[u]+val[v];
                }
                else if( d[u]+G[u][v]==d[v] )
                {
                    num[v]+=num[u];
                    if( w[u]+val[v] > w[v]) w[v]=w[u]+val[v];
                }
				
			}
        }
	}
}
int main()
{
    cin>>n>>m>>c1>>c2;
    for(int i=0;i<n;i++) cin>>val[i];    
    
    fill(G[0],G[0]+maxn*maxn,inf);
    for(int i=0;i<m;i++)
    {
	    int A,B,len;
	    scanf("%d %d %d",&A,&B,&len);
	    G[A][B]=min(len,G[A][B]);
	    G[B][A]=G[A][B];//无向图
    }
    
    dijkstra(c1);
    cout<<num[c2]<<" "<<w[c2]<<endl;

    return 0;
}

pata1087 All Roads Lead to Rome

注意dfs中的复制操作path=temppath

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=210;
int n,k;
int num=0;
map<string,int> city2int;
map<int,string> int2city;
string start;
int happiness[maxn];
int dst;
int G[maxn][maxn];
int dist[maxn];
bool vis[maxn];
vector<int> pre[maxn];
void dijkstra(int s)
{
    memset(vis,false,sizeof vis);
    fill(dist,dist+maxn,inf);
    dist[s]=0;
    for(int i=0;i<num;i++)
    {
        int u=-1,MIN=inf;
        for(int j=0;j<num;j++)
        {
            if(!vis[j] && dist[j]<MIN)
            {
                u=j;
                MIN=dist[j];
            }
        }
        if(u==-1) return;
        vis[u]=true;
        for(int v=0;v<num;v++)
        {
            if(!vis[v] && G[u][v]!=inf)
            {
                if( dist[u]+G[u][v]<dist[v] )
                {
                    dist[v]=dist[u]+G[u][v];
                    pre[v].clear();
                    pre[v].push_back(u);
                }
                else if( dist[u]+G[u][v]==dist[v] )
                {
                    pre[v].push_back(u);
                }
            }
        }
    }
}
int happval=0;
double aver_happval=0.0;
vector<int> path,temppath;
int routenum=0;
//从hone dfs到target
void dfs(int u)
{
    if(u==0)
    {
        routenum++;
        temppath.push_back(u);
        int val=0;
        for(int i=0;i<temppath.size();i++) val+=happiness[temppath[i]];
        if(val>happval)
        {
            happval=val;
            aver_happval=happval/(temppath.size()-1);
            path=temppath;
           
        }
        else if(val==happval)
        {
            double my_aver_happval=happval/(temppath.size()-1);
            if(my_aver_happval>aver_happval)
            {
                aver_happval=my_aver_happval;
                path=temppath;
            }
        }
        temppath.pop_back();
        return;
    }
    temppath.push_back(u);
    for(int i=0;i<pre[u].size();i++)
    {
       dfs(pre[u][i]);
    }
    temppath.pop_back();
}
int main()
{
    cin>>n>>k;
    cin>>start;
    city2int[start]=num;
    int2city[num]=start;
    num++;
    for(int i=0;i<n-1;i++)
    {
        string s;
        int t;
        cin>>s>>t;
        if(s=="ROM") dst=num;
        city2int[s]=num;
        int2city[num]=s;
        happiness[num]=t;
        num++;
    }
    fill(G[0],G[0]+maxn*maxn,inf);
    for(int i=0;i<k;i++)
    {
        string a,b;
        int t;
        cin>>a>>b>>t;
        int a1=city2int[a];
        int b1=city2int[b];
        G[a1][b1]=min(t,G[a1][b1]);
        G[b1][a1]=G[a1][b1];
    }
    dijkstra(0);
    dfs(dst);
    cout<<routenum<<" "<<dist[dst]<<" "<<happval<<" "<<(int)aver_happval<<endl;
    for(int i=path.size()-1;i>0;i--)
    {
        cout<<int2city[path[i]]<<"->";
    }
    cout<<"ROM"<<endl;
    return 0;
}

Floyd算法

求全源最短路径,其边值可为负权,基本思想为 对(i,j)这条路径做n次松弛操作,松弛完后即是最短路径。O(n^3)
预处理:

fill(dis[0],dis[0]+maxn*maxn,inf);
cin>>n>>m;//边数,顶点数
for(int i=0;i<n;i++)
{
	cin>>u>>v>>w;
	dis[u][v]=w;
}
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];
				}
			}
		}
	}
}

Bellman-Ford和SPFA算法

能够处理负边权,算法事件复杂度:O(VE)
对图中的边进行V-1轮操作,每轮都遍历图中所有的边u->v,以u为中介结点对v进行松弛。
判断负环:如果再进行一轮操作,仍然能够被松弛,说明存在负环。

优化:SPFA算法
时间复杂度:O(kE)k一般不超过2
思想:只有当某个顶点u的d[u]值改变,从它出发的v(边u->v)的d[v]值才会改变。

queue<int> Q;
源点s入队;
while(队列非空)
{
	取出队首元素u;
	for(u的所有邻接边u->v)
	{
		if(d[u]+dis<d[v])
		{
			d[v]=d[u]+dis;
			if(v不在当前队列)
			{
				v入队;
				if(v入队的次数大于n-1)
				{
					说明有负环;
					return;
				}
			}
		}
	}
}

拓扑排序与关键路径

DAG:有向无环图
在这里插入图片描述

拓扑排序

C1是C5的前驱,C1是C3的直接前驱
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
一个AOV网的拓扑序列不是唯一的。
如何检测AOV网是否有环:如果拓扑序列包含了所有的顶点,说明没有环;否则有环。

关键路径

在这里插入图片描述
在这里插入图片描述
v2事件说明A活动结束,B,C活动开始

源点:入度为0的顶点
汇点:出度为0的顶点
关键路径:源点到汇点最长的路径

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值