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

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
    评论
### 回答1: 学习数据结构和算法通常需要以下几步: 1. 了解基本的编程知识,包括变量、条件语句、循环、函数等。 2. 学习基本的数据结构,如数组、链表、栈、队列、树等。 3. 学习常见的算法,如排序、搜索、图论等。 4. 多做题,熟练掌握数据结构和算法的应用。 5. 学习进阶的数据结构和算法,如哈希表、并查集、最短路等。 可以通过读书、看视频、做题等方式来学习数据结构和算法。有很多优秀的书籍和在线课程可以帮助你学习。 ### 回答2: 数据结构与算法是计算机科学中非常重要的基础知识,学习好这门课程对于提高编程能力和解决实际问题至关重要。以下是我对于如何学习数据结构与算法的建议。 第一,理解基本概念。首先要理解数据结构和算法基本概念,例如数组、链表、栈、队列等数据结构,以及排序、查找、递归、动态规划等算法。可以通过阅读相关教材、参考书籍或者在线教程来学习这些概念,并进行实践。 第二,掌握基本操作。在了解了基本概念之后,需要掌握数据结构与算法基本操作。这包括插入、删除、查找等操作,以及排序、查找等算法的具体实现方法。可以通过编写代码来实践这些操作,并进行调试和优化。 第三,解决问题。学习数据结构与算法的最终目的是解决实际问题。可以选择一些经典的问题,例如查找算法中的二分查找、排序算法中的快速排序等,通过分析问题的特点和要求,选择合适的数据结构和算法来解决,并进行实现和测试。 第四,练习和总结。学习数据结构与算法需要不断地进行练习和总结。可以选择一些习题进行练习,挑战自己的编程能力。同时,及时总结归纳所学到的知识和经验,将这些知识内化为自己的思维方式和工作习惯。 最后,不断学习和提高。数据结构与算法是一门广阔而深奥的学科,需要不断学习和提高自己的知识水平。可以参与相关的讨论和学习交流,阅读更深入的研究和应用资料,参与项目实践以提升自己的能力和经验。 总之,数据结构与算法是一门需要深入理解和不断实践的学科。通过理解基本概念、掌握基本操作、解决实际问题、练习和总结,以及持续学习和提高,可以逐渐掌握和应用好这门课程的知识。 ### 回答3: 学习数据结构与算法是程序员必备的基本功,以下是一些建议。 首先,了解数据结构的概念和基本的数据结构类型,如数组、链表、栈、队列、树、图等。掌握它们的定义、特点和基本操作。 其次,学习算法基本思想和常用的算法技巧,如递归、分治、贪心、动态规划等。了解它们的应用场景和解决问题的思路。 然后,通过学习经典的数据结构与算法的实现代码来加深理解。可以通过书籍、在线教程、视频课程等多种途径进行学习,找到适合自己的学习资源。 接着,通过刷题来提升自己的实践能力。可以选择一些在线刷题平台,如LeetCode、LintCode等,挑选一些经典的算法问题进行解答。在实践中不断思考和总结,逐渐掌握解决问题的套路和算法的优化思路。 此外,参与算法竞赛和交流也是一个不错的学习方式。可以参加一些算法竞赛平台的比赛,与其他程序员进行交流和学习。也可以参加一些线下的技术交流活动,与其他程序员面对面地讨论和学习。 最后,持续学习和实践是关键。数据结构与算法是一个广阔的领域,需要不断学习,不断实践,不断总结经验。在实际的项目中尽量应用所学的数据结构与算法,提升自己的编程能力。 总之,学习数据结构与算法需要理论与实践相结合,要有积极的学习态度和持之以恒的学习精神。只有不断努力,才能在数据结构与算法的学习中取得进步。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值