图的复习(萌新之人所写)

floyd算法

该算法有一个核心步骤就是使用三个for循环将全部有向(或者无向)图各个点之间的最短距离储存在一个二维数组里面,所使用的数据结构是一个邻接矩阵。可以处理带权值的也可以处理没有带权值的图,没有带权值的图往往是使用一个bool类型的邻接矩阵。

核心代码

这是储存带权图

cpp 复制代码
for(int k=1;k<=n;k++)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(a[i][k]<inf&&a[k][j]<inf&&a[i][j]>a[i][k]+a[k][j])
			{
				a[i][j]=a[i][k]+a[k][j];
			}
		}
	}
}

这是不带权并且使用bool类型来装

cpp 复制代码
for(int k=1;k<=n;k++)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(a[i][k]&&a[k][j])
			{
				a[i][j]=true;
			}
		}
	}
}

例题:刻入光盘

这一题就是需要使用不带权和使用bool类型类型来判断两点是否已经联通,也就是上面提供的第二个模板,如果i->k联通且k->j联通,那么i->j就联通,那么此时就已经将i->j标记为true,也就是已经联通的意思。

这里还需值得学习的一点就是,这里也使用了并查集的一点思想,就是利用一个fa数组将各个点之间的关系链接起来,如果fa数组所存的数字就是自己本身的下标,那么就代表着是最开始借出去光碟的人,那么ans(答案)就需要加加。这个可以通过一个for循环来检查一遍

cpp 复制代码
#include<iostream>
#define N 220
using namespace std;
int n;
int a[N][N];
int fa[440];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int x;
		while(cin>>x&&x)
		{
			a[i][x]=1;
		}
	}
	for(int i=1;i<=440-10;i++)
	{
		fa[i]=i;
	}
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(a[i][k]&&a[k][j])
				a[i][j]=1;
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(a[i][j])
			{
				fa[j]=fa[i];
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		if(fa[i]==i)
		{
			ans++;
		}
	}
	cout<<ans<<endl;
	return 0;
}

P6464 [传智杯 #2 决赛] 传送门

这一题就是第一个带权图

我们看一下这一题的数据大小,只有100所以很小,所以我们直接使用暴力解法。每一边都尝试使其为0(就是重新使权值为0)然后在这个新的带权图重新走一边floyd算法重新计算全源最短路径

这里值得注意的是我们可以分别重新以i和j走一遍floyd这样就可以使得时间复杂度变为n的4次方

代码如下:

cpp 复制代码
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
const int N=150;
int n,m,a[N][N],a1[N][N];
void inback()
{
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)a1[i][j]=a[i][j];
}
int ans=2e9;

int main()
{
	cin>>n>>m;
	memset(a,0x3f,sizeof(a));
	for(int i=1;i<=m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		a[x][y]=a[y][x]=z;
	}
	for(int k=1;k<=n;k++) 
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
	inback();
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++){
		a1[i][j]=a1[j][i]=0;
		for(int k1=1;k1<=n;k1++)
		for(int k2=1;k2<=n;k2++){
			a1[k1][k2]=min(a1[k1][k2],a1[k1][i]+a1[i][k2]);
		}
		
		for(int k1=1;k1<=n;k1++)
		for(int k2=1;k2<=n;k2++){
			a1[k1][k2]=min(a1[k1][k2],a1[k1][j]+a1[j][k2]);
		}
		int tmp=0;
		for(int k=1;k<=n;k++)
		for(int t=1;t<k;t++){
			tmp+=a1[k][t];
		}
		
		ans=min(ans,tmp);
		inback();
	}
	cout<<ans<<endl;
	return 0;
}

并查集数据结构 和 Kruskal算法

并查集就是使用一个一维数组通过子节点的下标指向父节点来使其成为一颗树,其核心就是构建find函数和Union函数。

并查集是一种数据结构,对这种数据和结构来说最常用结合的算法就是kruskal算法

对于kruskal算法来说:就是一种采取最优解的方法,常常使用排序,所以在这里我们需要知道定义结构体算法和运算符重载

对此我们还需要知道最小生成树使k>=n-1 这常常是最小生成树的退出条件

代码如下:

村村通

这一题的主要思路就是其实就是使用并查集来解决,如果fa数组中所存放的值就是自己本身,那么就代表这个为一个整体。有多少个整体就需要修多少条路

代码如下:

cpp 复制代码
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1100;
int fa[N];
int n,m;
void ini()
{
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
	}
}
int cha(int x){
	return fa[x]==x?x:fa[x]=cha(fa[x]);
}
void Union(int x,int y)
{
	x=cha(x);
	y=cha(y);
	if(x==y)return ;
	fa[x]=y;
}
int main()
{
	
	while(cin>>n>>m&&n)
	{
		ini();
		for(int i=1;i<=m;i++){
			int x,y;
			cin>>x>>y;
			Union(x,y);
		}
		int ans=0;
		for(int i=1;i<=n;i++)
		{
			if(fa[i]==i)
			ans++;	
		}
		cout<<ans-1<<endl;
	}
	return 0;
}

最小生成树

如果有n个点,那么就有n-1条边,所以这就是我们循环的退出条件。 既然我们要使用到kruskal算法,那么我们就要将最小的边利用排序先放在前面。然后再使用并查集Union将其联合。

代码如下:

cpp 复制代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
const int N=2e5+100;

int fa[N];

struct node 
{
	int x,y,w;
}e[N];

bool cmp(node x,node y){
	return x.w<y.w;
}

int cha(int x){
	return fa[x]==x?x:fa[x]=cha(fa[x]);
}

void Union(int x,int y){
	x=cha(x);y=cha(y);
	if(x==y)return ;
	fa[x]=y;
}

int main()
{
	cin>>n>>m;
	
	for(int i=1;i<=m;i++){
		int x,y,z;
		cin>>e[i].x>>e[i].y>>e[i].w;
	}
	
	sort(e+1,e+1+m,cmp);
	
	for(int i=1;i<=N-100;i++){
		fa[i]=i;
	}	
	int ans=0;
	int tmp=0;
	for(int i=1;i<=m;i++)
	{
		if(cha(e[i].x)!=cha(e[i].y)){
			Union(e[i].x,e[i].y);
			tmp++;
			ans+=e[i].w;
			if(tmp>=n-1){
				cout<<ans<<endl;
				return 0;
			}	
		}		
	}
	cout<<"orz"<<endl;
	return 0;
}

dijstra算法

dijstra算法又称单源最短路径算法,是指定某地到某地的求最短距离的算法,这里需要掌握的是dijstra算法的两个核心步骤,以及一个关于空间复杂度的优化(链式前向星),和时间复杂度的优化(堆优化,优先队列)

【模板】单源最短路径(标准版)

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;
const int maxN = 100010;
const int maxM = 500010;
int n, m, s, cnt;
struct node
{
	int id;
	int dis;
	bool operator< (const node& x) const {
		return x.dis < dis;
	}
};
priority_queue<node> q;
struct EDGE
{
	int to;
	int w;
	int next;
}edge[maxM];
int head[maxM];
bool vis[maxM];
int ans[maxM];
void add(int u, int v, int w)
{
	cnt++;
	edge[cnt].to = v;
	edge[cnt].w = w;
	edge[cnt].next = head[u];
	head[u] = cnt;
}
int main()
{
	cin >> n >> m >> s;
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		cin >> u >> v >> w;
		add(u, v, w);
	}
	for (int i = 1; i <= maxM; i++)
	{
		ans[i] = 0x7fffffff;
	}
	
	ans[s]=0;
	q.push(node{s,0});
	while(!q.empty())
	{
		node tmp=q.top();
		q.pop();
		int k=tmp.id;
		if(vis[k])
		continue;
		vis[k]=true;
		for(int i=head[k];i!=0;i=edge[i].next)
		{
			int to=edge[i].to;
			if(!vis[to]&&ans[to]>ans[k]+edge[i].w)
			{
				ans[to]=ans[k]+edge[i].w;
				q.push(node{to,ans[to]});
			}
		}
	}
	
	for (int i = 1; i <= n; i++)
	{
		cout << ans[i] << " ";
	}
	return 0;
}
相关推荐
打码人的日常分享1 分钟前
企业人力资源管理,人事档案管理,绩效考核,五险一金,招聘培训,薪酬管理一体化管理系统(源码)
java·数据库·python·需求分析·规格说明书
27669582922 分钟前
京东e卡滑块 分析
java·javascript·python·node.js·go·滑块·京东
爱写代码的刚子3 分钟前
C++知识总结
java·开发语言·c++
冷琴199611 分钟前
基于java+springboot的酒店预定网站、酒店客房管理系统
java·开发语言·spring boot
daiyang123...37 分钟前
IT 行业的就业情况
java
睡不着还睡不醒1 小时前
【数据结构强化】应用题打卡
算法
sp_fyf_20241 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-05
人工智能·深度学习·神经网络·算法·机器学习·语言模型·自然语言处理
爬山算法1 小时前
Maven(6)如何使用Maven进行项目构建?
java·maven
.生产的驴1 小时前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript
爱学的小涛1 小时前
【NIO基础】基于 NIO 中的组件实现对文件的操作(文件编程),FileChannel 详解
java·开发语言·笔记·后端·nio