【归纳】图论的各种基本算法的总结

14 篇文章 1 订阅
14 篇文章 0 订阅

图论的题也刷了不少了,但是近期才发现前面的一些dij什么的都忘记怎么写了,甚至分不清楚dij和spfa的区别了…所以想到这里做一些简单图论算法归纳。主要涉及的算法有:Floyd算法,dijkstra算法,spfa算法,prim算法和kruskal(其实这两个就是最小生成树算法),以及一维,二维的并查集算法(抱歉萌新最近也就学了这些算法大佬们见笑了QAQ)。


欧克!let’s begin !

首先,先说说几个算法中最简单的算法,floyd算法.

其实这个算法,就是依据动态规划原理,枚举出jk中间的一个点i以作为jk连接的桥梁,从而递推出任意两个点直接距离的最小值。有人问我i,j,k三个循环为什么不能调换位置,原因很简单,就是因为枚举ij之间的点k的时候只要用到前面的结论f(ji)和f(ik)的,所以ij必须是最快更新的一个,后面才能够用到。
虽然代码简单,但是时间复杂度o(n3),一不留神就超时了,注意数据。代码如下:

        for(int i=1;i<=n;i++)
        	for(int j=1;j<=n;j++)
        		for(int k=1;k<=n;k++)
			{
		                if(g[j][i]+g[i][k]<g[j][k])
	                        g[j][k]=g[j][i]+g[i][k];//先算出各个点之间的权值情况,前提是已经全部初始化gij=+∞,具体大小判断得依据题意
			}

dij算法.

原理:
1.先标记起点,然后从起点出发,得出到各个点的最小距离。
2.再每次找出离起点最近(并且之前还没有讨论过)的那个点,从这个点出发,将前面得到的起点到各个点的最短路径和以这个点为中介点,再到各个点的距离大小作比较,去最小值。时间复杂度o(n2)。
代码如下:


int i,j,t,flag[N],dis[N],g[N][N];
void dij()
{
    for(i=1;i<=t-1;i++)//起点不用讨论最后一个
    {
        int maxx=2e9,now=0;//最小值和当前最小值位置
        for(j=1;j<=t;j++)
        {
            if(!flag[j])//如果j未被讨论过
            {
                if(dis[j]<maxx)
                {
                    maxx=dis[j];//记录最小值
                    now=j;//记录最小值位置
                }
            }
        }
        flag[now]=1;//搜索这个点,同时标记已经搜索
        if(!now)break;//说明已经搜索完毕
        for(int j=1;j<=t;j++)
        {
            if((dis[j]>dis[now]+g[now][j])&&!flag[j])//注意终点不能是以前已经走过的位置,否则又回到原来的地方,讨论重复点,肯定更长
                dis[j]=dis[now]+g[now][j];
        }
    }
}

spfa算法

其实个人觉得这个算法和dij有点类似,不过它用队列做了一些优化,而且算法稍有不同。
原理:
1.同样以起点出发,先算出到各个点的最小距离然后入队最小距离的各个点。
2.当队列非空时,分别以每个队列中的点作为中介点,再算出到别的点最短距离;同时该点继续入队,直到继续入队时到所有点距离都不是最小时,队列变空,停止循环。
代码如下:

int dis[N],map[N][N],vis[N],n,m,a[5555];//n是对象数目,假设1 是起点(其实这里随便可以随便设,哪个是起点就最先让哪个入队)
void spfa()//dis是起点到各点最小距离,vis是标记变量
{
    int m,k;queue<int> q;q.push(1);//起点入队
    while(!q.empty()){//非空说明还要讨论情况,继续
        m=inf;int k=q.front();//k为当前讨论间隔点
        for(int j=1;j<=n;j++){
            if(map[k][j]!=inf&&dis[j]>dis[k]+map[k][j]){//如果kj可以联通并且之前到j的最小距离大于以k作为中介点的距离和,则赋值
                dis[j]=dis[k]+map[k][j];
                if(!vis[j]){//说明该轮循环j还没有入队(可能之前已经入队,这里不重复入队)
                    q.push(j);
                    vis[j]=1;
                }
            }
        }
        vis[k]=0;q.pop();//重新赋值为vis=0是为了后面对该数据的更新(有可能遇到以k为中介点更小距离的情况)
    }
}

最小生成树的两种算法

首先简单说说啥是最小生成树(详细见百度,这里只是简单提及一下)。就是建一颗树,使得所有连接边的权值和最小。
1.prim算法:
原理介绍:
1.tot记录当前连接点的数目,当n=tot时说明所有点都已经完成连接,最小生成树建立完毕
2.对还没有连接的点讨论,选取已连接点中与该点连接后权值最小的那个点,并相连
3.每次将已经连接的点记录如数组,便于下次对已连接点讨论
代码如下:

    while(cin>>n)
    {
        init();//初始化函数,这里就不多写了,memset什么的会用吧
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                cin>>g[i][j];
        f[1]=1;p[++tot]=1;//首先以第一个作为起点,同理起点也可以因情况而变化
        while(tot<n)//pirm算法,最小生成树
        {
            minx=inf;
            for(int i=1;i<=tot;i++)
                for(int j=1;j<=n;j++)
                    if(!f[j]&&minx>g[p[i]][j])//以此考虑每个树中的元素与剩下的元素相连的情况,找出最小值
                    {minx=g[p[i]][j];sp=j;}//如果找到还没有加入最小生成树的,而且于该树枝连接的值最小,则赋值
            ans+=minx;p[++tot]=sp;f[sp]=1;//该代码中ans是记录所有边的最小权值和,p存放所加入点位置

        }
        cout<<ans<<endl;
    }

2.kruskal算法
这个算法的整体性比较强,还用到了并查集,所以干脆一起讲了吧
原理:就是 排序+并查集
1.对所有边的权值排序。
2.通过并查集搜寻两个点是否来自于同一个发源地,如果相同则说明已经连上最小生成树,跳过;否则连上并标记这个点的发源地是树的源点。(由于已经排好序故在前面的情况肯定是最优解,后面遇到已经连上的点权值肯定比前面的情况要大)
具体看下列代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,s,step,x,ans,y,z,f[330];
struct node
{
    int x,y,val;
    bool operator < (node a)const//运算符重载,这里是为了排序需要,根据结构体中的val进行排序
    {
        return val<a.val;
    }
}p[10001];
int find(int k)//并查集哦,用于寻找两个点的发源地
{
    if(k!=f[k])return find(f[k]);
    return k;
}
int main()
{
    while(cin>>n>>m)
    {
        memset(f,0,sizeof f);
        memset(p,0,sizeof p);
        ans=0;step=0;
        for(int i=1;i<=n;i++)f[i]=i;
        for(int i=1;i<=m;i++)
        {
            cin>>x>>y>>z;
            p[i].x=x;p[i].y=y;p[i].val=z;
        }
        sort(p+1,p+m+1);//对所有边的权值进行排序
        for(int i=1;i<=m;i++)
        {
            int a=find(f[p[i].x]);//分别寻找两个点的祖先
            int b=find(f[p[i].y]);
            if(a!=b)
            {
                ans=max(ans,p[i].val);//这里的ans用于记录操作过程中的最大权值
                f[a]=b;step++;//step记录建树过程步数
            }
        }
        cout<<step<<' '<<ans<<endl;
    }
    return 0;
}

啊啊啊。

内容太多了,具体并查集内容我的其他博客里面也有,这里只是简单提及了一下~
好滴,大咖一起来复习下哈(其实我也就是为了自己的复习用),祝愿大家学业有成啊!不专业的地方大佬见谅!

  • 11
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值