电子学会七级-数据结构-图

Dijkstra
把整个集合分成两部分,确定的最短路点集合、未确定最短路点集合
在未确定最短路的点中,确定一个点,对这个点对应的邻接点进行松弛操作
视频
https://www.bilibili.com/video/BV1zz4y1m7Nq?share_source=copy_web
https://www.bilibili.com/video/BV16R4y1T7tC/?spm_id_from=333.788

为啥不能处理负权?
首先我们要清楚一个点:Dijkstra是每次贪心的选择跟当前邻接的点,而不会去考虑处邻接之外的其他点

而如果所有Dijkstra算法适用于不存在负权边的图(有无向均可),这个是因为迪杰斯特拉算法是基于贪心策略,每次都找一个距源点最近的点,然后将该距离定为这个点到源点的最短路径;但如果存在负权边,那么直接得到的最短路不一定是最短路径,因为可能先通过并不是距源点最近的一个次优点,再通过一个负权边,使得路径之和更小,这样就出现了错误。如下:

1——>2权值为5,1——>3权值为6,3——>2权值为-2,求1到2的最短路径时,Dijkstra就会选择权为5的1——>2,但实际上1——>3——>2才是最优的结果。

另外如果包含负环,则意味着最短路径不存在。因为只要在负权回路上不断兜圈子,所得的最短路长度可以任意小。负数的绝对值越大,该值就越小。

https://www.luogu.com.cn/problem/P1339

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

const int maxn=2510;
int G[maxn][maxn];//邻接矩阵 
int dist[maxn];//存储源点到i的最短长度 
bool vis[maxn];//v[i]存储节点i是否已确定最小路径 
int n,m,s,t;//n个点 m条边 s源点 t目标点 
 
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	memset(G,0x7f,sizeof(G));
	memset(vis,true,sizeof(vis));
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		G[x][y]=G[y][x]=z;
	}
	memset(dist,0x7f,sizeof(dist));
	dist[s]=0;//开始到源点距离为0 
	for(int i=1;i<=n;i++){//找出每个点的最短路 
		//找当前最短路的最小值 //默认最大值 从1开始 0不使用 dist[0]为0x7f 最大值
		int k=0;
		for(int j=1;j<=n;j++){
			if(dist[j]<dist[k] &&vis[j]){//找为确定所有节点最小距离
				k=j;//更新最小 
			}
		}
		vis[k]=false;//k点的最小路径长度确定了
		if(k==t) break;//确定到e的最短路径 结束
		for(int j=1;j<=n;j++){//松弛i节点后续的节点 更新d数组值 
			if(dist[k]+G[k][j]<dist[j]){
				dist[j]=dist[k]+G[k][j];
			}
		}
	}
	printf("%d\n",dist[t]);
	return 0; 
}

单源最短路径(标准版)
https://www.luogu.com.cn/problem/P4779
Dijkstra 优先队列 邻接表优化

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

const int maxn=1e5+5;
int n,m,s;//n个点m条边 从s点出发 
int dist[maxn];
struct node{
	int y,w;//y 目标点 w源点到目标点的长度 
	node(int y=0,int w=0):y(y),w(w){};//构造函数 
	bool operator < (const node &p) const {//结构体运算符重载 p为堆顶元素 w>p.w为小顶堆
		return w>p.w;
	}
};
vector<node> e[maxn];//邻接表 

void dijkstra(int s){
	memset(dist,0x3f,sizeof(dist));//初始赋最大值 
	dist[s]=0;//源点到源点距离为0 
	priority_queue<node> q;//优先队列 
	q.push(node(s,0));//开始节点放入优先队列 
	while(!q.empty()){
		node top=q.top();
		int k=top.y;
		q.pop();
		if(top.w>dist[k]) continue;//多次松弛 放入队列的部分不是最小的不处理 
		for(int i=0;i<e[k].size();i++){//松弛操作 找当前最短路节点所有邻接边 
			int y=e[k][i].y;
			int w=e[k][i].w;
			if(dist[k]+w<dist[y]){//通过当前最短路节点比原来的小  可松弛 
				dist[y]=dist[k]+w;//用较小替换原来的值 
				q.push(node(y,dist[y]));//加入优先队列 尝试判断进入已确定集合 
			}	
		}
	}
}

int main(){
	scanf("%d%d%d",&n,&m,&s);//n个点 m条边 从s点开始 
	for(int i=1;i<=m;i++){//循环每条边 
		int u,v,w;//u到v有一条边 边权为w 
		scanf("%d%d%d",&u,&v,&w);//输入 
		e[u].push_back(node(v,w));//构造邻接表 
	}
	dijkstra(s);//调用函数 计算从s开始到个点的最短路径距离 
	for(int i=1;i<=n;i++){//输出到i最短路径距离 
		printf("%d ",dist[i]); 
	}
}

https://www.luogu.com.cn/problem/P1346
https://www.luogu.com.cn/problem/P1821

Bellman-Ford算法
随机松弛边,有盲目性,对应优化算法SPFA
Bellman-Ford 为什么需要n-1次循环
https://www.cnblogs.com/myeln/articles/16291775.html

https://www.luogu.com.cn/problem/P1744

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

const int N = 105;
const int M = 1005;
int x[N], y[N];// x 每家点横坐标  y每家点纵坐标 
double dis[N];// 到i点的最短路径 
int n, m, s, t;//n所有点数 m所有边数 s源点 t目标点 

double get_dis(int k1, int k2){//计算两坐标之间距离 
    return sqrt((x[k1]-x[k2])*(x[k1]-x[k2])+(y[k1]-y[k2])*(y[k1]-y[k2]));
}

struct Edge{//边结构体 
	int u, v;//u 边起点 v边终点 
	double w;//边长 
	
	Edge(int U =0, int V=0, double W = 0.0) {//构造函数 方便构造边 
		u=U; v= V; w=W;
	}
	
}e[M << 1];//无向图 边为 2*M 构造e为边集数组 

void bellman_ford(int s){//从 
	for(int i=1;i<=n;i++)
		dis[i]=0x3f3f3f3f;// 
	dis[s]=0;//到起点的最短路径为0 

	for(int i=1;i<=n-1;i++)//更新n-1次
		for(int j=1;j<=m;j++){//更新每一条边
			Edge &ei = e[j];//结构体变量简化如下写法 
			if(dis[ei.u] + ei.w < dis[ei.v]){//到u最短路径+uv边<之前到v的最短路径 松弛 
				dis[ei.v] = dis[ei.u] + ei.w;//松弛 到v的最短路径长度使用经过u的路径  
			}
		}
}

int main(){
    cin >> n;//n个点 
    for(int i=1;i<=n;i++){//读取所有坐标点 
    	cin>>x[i]>>y[i];
	}
	
    cin>>m;//m条边 
    for(int i=1;i<=m;i++) {
		int u,v;//u 起点  v终点 w边长 
		cin>>u>>v; 
		double w;
		w=get_dis(u,v);
		e[i]=Edge(u,v,w);//构造一条边 起点 终点 边长 
		e[i+m]=Edge(v,u,w);//构造反向边 
	}

	cin>>s>>t;//输入 s源点  t目标点 
	m <<= 1;//无向图 变数 m*2 
    bellman_ford(s);//调用bellman ford算法计算最短路 

    printf("%.2lf", dis[t]);//输出到目标点t的最短路径长度 
    return 0;
}

SPFA算法
从源点开始松弛每条边 邻接点被松弛 就加入队列尝试松弛被松弛点的邻接点

https://www.luogu.com.cn/problem/P3371

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+100;
struct node{//结构体 
	int y,w;//y终点 w起点到终点的边权 
	node(int yy,int ww){//构造函数 构造时赋值 方便后续赋值 简化代码 
		y=yy;
		w=ww;
	}
};
bool v[maxn];//是否能放入队列 
vector<node> e[maxn];//邻接矩阵 
int d[maxn];//源点到目标点的距离 
int n,m,s;//n节点数 m边数 s起始节点编号 

//spfa算法计算所有源点到可达目标点的最短路径长度 
void spfa(int s){
	memset(d,0x3f,sizeof(d));
	memset(v,true,sizeof(v));
	d[s]=0;
	queue<int> q;//存放被松弛过的点 
	q.push(s);
	v[s]=false;
	while(!q.empty()){//队列有被松弛的点 继续 
		//取队列头 
		int x=q.front();
		q.pop();
		v[x]=true;//出队设置可以放入队列 
		//松弛x邻接点
		for(int i=0;i<e[x].size();i++){
			int y=e[x][i].y;
			int w=e[x][i].w;
			//松弛邻接节点 他们的边权是w
			if(d[x]+w<d[y]){
				d[y]=d[x]+w;
				//加入队列是为了松弛邻接点 既然已经在队列 出队时自然可以松弛 没必要重复添加
				//此时d[y] 已经被改变 出队时松弛计算就是最小 
				if(v[y]==true){//不在队列 可以松弛 则加入队列 
					q.push(y);//加入队列   
					v[y]=false;//加入队列后 设为false 不允许队列中存在多个 
				}
			} 
		} 
	}
}

int main(){
	cin>>n>>m>>s;//输入n个点 m条边 s编号 
	for(int i=1;i<=m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		e[x].push_back(node(y,z));//邻接表构建 
	}
	spfa(s);// spfa算法计算s开始到可达点的最短路径长度 
	for(int i=1;i<=n;i++){
		if(d[i]==d[0]){//如果不可达 输出2的31次方-1 
			cout<<(1<<31)-1<<' ';
		}else{
			cout<<d[i]<<' ';//输出最短路径长度 
		}
	}
}

https://www.luogu.com.cn/problem/P3385

判断负环

//从起点, 经过最短路径到终点, 期间最多经过 N-1 个结点. 如果超过N个, 则一定有负环. 
//用 times[x] 表示 1 到 x 的最短路包含的边数,times[1]=0。每次用 dis[x]+w(x,y) 更新 dis[y] 时, 
//也用 times[x]+1 更新 times[y]。此过程中若出现 cnt[y]≥n,则图中有负环。最坏情况复杂度也是 O(nm)。

#include<bits/stdc++.h>
const int INF=0x3f3f3f3f;
const int maxn=2001;
using namespace std;
struct edge{
	int v,w;
	edge(int _v=0,int _w=0){//构造函数 
		v=_v;
		w=_w;
	}
};
int n;
vector<edge>g[maxn];//邻接表 
int d[maxn];
bool vis[maxn];
queue<int>q;
int times[maxn];//记录松弛次数
void spfa(int s){
	memset(vis,0,sizeof(vis));
	memset(d,INF,sizeof(d));//d数组从最大开始松弛 
	memset(times,0,sizeof(times));
	
	while (!q.empty())	q.pop();//多组数据,处理前要全部清空
	
	q.push(s);//从s点开始 
	vis[s]=1;//已经放入 
	d[s]=0;//源点到s为0 
	times[s]=1;//s点更新 
	while (!q.empty())	{
		int now=q.front();
		q.pop();
		vis[now]=0;
		for(int i=0;i<g[now].size();i++){//遍历now起点的邻接点 
			int v=g[now][i].v;//终点 
			int w=g[now][i].w;//长度 
			if(d[v]>d[now]+w){//到v最短路径>到now最短路径 +w  now+此边 比原来小 松弛 
				d[v]=d[now]+w;
				times[v]++;//经过v点次数+1 
				if(times[v]>n){//经过v点次数>n 
					puts("YES");//说明有负环,输出YES
					return;//返回,不需要继续遍历
				}
				if(!vis[v]){//松弛时 v点不在队列 
					q.push(v);//加入队列 
					vis[v]=1;//打标不能加入队列 
				}
			}
		}
	}
	puts("NO");//永远没有点松弛次数大于n,说明无负环
}
int main()
{
	int t,m;
	scanf("%d",&t);//t 组数据 
	for (int i=1;i<=t;i++){//循环处理每组数据 
		scanf("%d%d",&n,&m);//n 节点数 m边的条数 
		int u,v,w;//u起始节点 v终点节点 w边权 
		for (int j=1;j<=m;j++){
			scanf("%d%d%d",&u,&v,&w);//输入 
			g[u].push_back(edge(v,w));//构造邻接表数据 
			if  (w>=0)  g[v].push_back(edge(u,w));//大于等于0时双向边 --根据录入条件说明 
		}
		spfa(1);//调用函数,起点为1
		for (int j=1;j<=n;j++)
			g[j].clear();//清空
	}
	return 0;
}

https://www.luogu.com.cn/problem/P1359

https://www.luogu.com.cn/problem/P1339

多源最短路 弗洛伊德 Floyd算法
https://www.luogu.com.cn/problem/P2935

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

const int maxn=510;
int f[maxn][maxn];//f[i][j] 表示i到j的最小路径
int a[maxn];//存储喜欢的牧场
int n,s,m;//n个牧场 s个喜欢的牧场 m条无向边 
int main(){
	cin>>n>>s>>m;//n个牧场 s个喜欢的牧场 m条无向边 
	for(int i=1;i<=s;i++){//记录所有喜欢的牧场 
		cin>>a[i];
	}
	
	memset(f,0x30,sizeof(f));//最小路径默认无穷大 
	
	for(int i=1;i<=n;i++){//自己到自己的最短路径为0 
		f[i][i]=0;
	}
	
	for(int i=1;i<=m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		if(z<f[x][y]){//赋值两点间最短路径  
			f[x][y]=f[y][x]=z;//无向图赋值 
		}
	}
	//弗洛伊德算法核心 Floyd
	for(int k=1;k<=n;k++){// 每个途径点 刷新一次矩阵 
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(f[i][k]+f[k][j]<f[i][j]){//i~k最短路+k~j最短路比i~j的最短路小 更新i~j最短路 
					f[i][j]=f[i][k]+f[k][j];//更新i~j最短路
				}
			}
		}
	}
	
	int k=0,ans=1e9;//k记录 住k处 到喜欢牧场最短路径和最小的牧场编号  ans记录 住k处 到喜欢牧场最短路径和
	for(int i=1;i<=n;i++){
		int sum=0;//从i出发到所有喜欢点的最短路径和 
		for(int j=1;j<=s;j++){
			sum+=f[i][a[j]];
		}
		if(sum<ans){//i到所有喜欢牧场最短路径和比ans小 更新ans 
			ans=sum;//更新ans 
			k=i;//更新牧场编号 
		}
	}
	cout<<k;
	return 0;
}

https://www.luogu.com.cn/problem/P2910
https://www.luogu.com.cn/problem/P1359
https://www.luogu.com.cn/problem/P2419
https://www.luogu.com.cn/problem/P2888

最小生成树 -Kruskal
https://www.luogu.com.cn/problem/P2330


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

const int maxn=310;
const int maxm=1e5+10;

struct edge{//边集数组 
	int x,y,w;//起点 终点 长度 
}e[maxm];

int n,m;
int f[maxn];//i点父节点编号 

//从x找根 路径压缩算法 不是根 把自己挂在根下面 
int find(int x){
	if(f[x]!=x){// 父节点不是自己  即不是根 
		f[x]=find(f[x]);//通过父节点找根 并把根赋值给x的父节点  x加入根节点子节点 路径压缩 
	}
	return f[x];
}
bool cmp(edge P,edge Q){
	return P.w<Q.w;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w);//m条道路 起始点 终点 边权 
	}
	sort(e+1,e+m+1,cmp);//按边权从小到大排序 
	
	for(int i=1;i<=n;i++){
		f[i]=i;//i的父节点设置自己  所有节点初始为根节点 
	}
	int cnt=0;
	for(int i=1;i<=m;i++){//遍历m条边 
		int rx=find(e[i].x);//找x根节点 
		int ry=find(e[i].y);//找y的根节点 
		if(rx!=ry){//根不同 不连通  可以加入连通 
			if(++cnt==n-1){
				printf("%d %d\n",n-1,e[i].w);//分值最大的道路分值 最后的一条 已排序 
				return 0;
			}
			f[rx]=ry;//连通 fx的父节点指向ry  rx和ry有一个根节点 连通 
		}
	}
} 

https://www.luogu.com.cn/problem/P3366
https://www.luogu.com.cn/problem/P1265

http://noi.openjudge.cn/ch0308/

邻接矩阵

https://www.luogu.com.cn/problem/P1294
题解

#include<iostream>
using namespace std;

const int maxn = 30;

int G[maxn][maxn];//邻接矩阵 G[i][j] 表示i-j的距离 
int vis[maxn];//一次方案中是否使用过某个节点 
int n,m,ans;//ans表示某方案最大距离 

//u从某节点开始 sum到当前节点最大距离 
void dfs(int u,int sum) {
    if(sum>ans)  ans=sum;
    for(int i=1;i<=n;i++)//找所有可以发的下一个节点 
       if(!vis[i] && G[u][i]) {//!vis i没被使用过 且G[u][i] u-i有路(距离不为0) 
               vis[i]=1;//i节点占住  
               dfs(i,sum+G[u][i]);//递归下一层节点 
               vis[i]=0;//i节点释放 
       }
}

int main() {
    cin>>n>>m;
    int u,v,w;
    for(int i=0;i<m;i++) {
        cin>>u>>v>>w;
        G[u][v]=G[v][u]=w;//有向图 u~v=w 且 v~u=w 
    }
    for(int i=1;i<=n;i++) {
        vis[i]=1;//占用此节点 
        dfs(i,0);//从第一个节点开始作为第一个方案 
        vis[i]=0;//释放此节点 
    }
    cout<<ans;//输出最大距离 
    return 0;
}

https://www.luogu.com.cn/problem/P1199

//https://www.luogu.com.cn/blog/wjyyy/solution-p1199
//https://my.oschina.net/u/4414193/blog/3915091 
//博弈 最优都拿不到 可以选择拿到最优勇士对应的次最大 可以被人拿到 
#include<bits/stdc++.h>
using namespace std;
int G[510][510];//邻接矩阵 
int main(){
    int n;
    scanf("%d",&n);//武将个数 
    for(int i=1;i<n;i++){
    	for(int j=i+1;j<=n;j++){
            scanf("%d",&G[i][j]);//武将组合默契值 A -> B
            G[j][i]=G[i][j];//武将组合默契值 B -> A
        }
	}
        
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        sort(G[i]+1,G[i]+1+n);//每列选取最优勇士 
        ans=ans>G[i][n-1]?ans:G[i][n-1];//选出排名第二中最大的那个
    }
    printf("1\n%d\n",ans);//一定有解
    return 0;
}

拓扑排序

https://www.luogu.com.cn/problem/P1137

/*
一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,
是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),
则u在线性序列中出现在v之前。 (源自百度)

通俗的说就是,一张有向无环图的拓扑序可以使得任意的起点u,它的一个终点v,
在序列中的顺序是u在前v在后
*/
#include<bits/stdc++.h>
using namespace std;
 
const int maxn=100000+15;
int n,m,sum,tot;//n个城市 m条道路 sum边
//head[] 下标为起点 value为最后一个终点edge
//ru[] 记录每个点入度 
//ts 排序好的拓扑序列
//dp 到节点i可以访问最多城市数 
int head[maxn],ru[maxn],ts[maxn],dp[maxn]; 
struct EDGE{
    int to;//边的终点 
	int next;//
}edge[maxn<<2];

void add(int x,int y){//链式前向星加边 
    edge[++sum].next=head[x];//增加一条边 next为前一个 插入前面 
    edge[sum].to=y;//加入终点 
    head[x]=sum;//head移动到当前 
}
void topsort(){//拓扑排序 
    queue <int> q;
    for (int i=1;i<=n;i++){//入度为0放入队列 
    	if (ru[i]==0) {
		    q.push(i);
		    ts[++tot]=i;//记录topo排序节点 
		}
	}
    
    while (!q.empty()){//队列不为空 
        int u=q.front();q.pop();//出队 
        for (int i=head[u];i;i=edge[i].next){// 遍历以u出去的边 
            int v=edge[i].to;//终点 v 
            ru[v]--;//删除u v边 v出度减1 
            if (ru[v]==0) {//如果减1后v出度为0,加入队列 
	            q.push(v);ts[++tot]=v;//加入topo排序的节点 
	        }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        ru[v]++;//加一条边 终点入度加1 
    }
    topsort();
    for (int i=1;i<=n;i++) dp[i]=1;//赋初值 默认到达城市为1 
    for (int i=1;i<=n;i++){//遍历所有节点 
        int u=ts[i];//按拓扑序列遍历 
        for (int j=head[u];j;j=edge[j].next){//遍历临接点 
            int v=edge[j].to;//v 目标点 
            dp[v]=max(dp[v],dp[u]+1);//到起始点 和目标点 取最大 可以访问的节点取最大 
        }
    }
    for (int i=1;i<=n;i++)//到i城市最多游览城市数 
    	printf("%d\n",dp[i]);
    return 0;
}

https://www.luogu.com.cn/problem/P3371

https://www.luogu.com.cn/problem/P1608

链式前向星
参考拓扑排序
https://www.luogu.com.cn/problem/P1137

边集数组
参考 Bellman-Ford算法
https://www.luogu.com.cn/problem/P1744

邻接表

https://www.luogu.com.cn/problem/P1199

https://www.luogu.com.cn/problem/P1137

https://www.luogu.com.cn/problem/P3916

链式前向星

https://www.luogu.com.cn/problem/U81206

https://www.luogu.com.cn/problem/P2661

https://www.luogu.com.cn/problem/P1351

https://www.luogu.com.cn/problem/P1197

图的基本应用

https://www.luogu.com.cn/problem/P5318

https://www.luogu.com.cn/problem/P1113

https://www.luogu.com.cn/problem/P4017

https://www.luogu.com.cn/problem/P1807

https://www.luogu.com.cn/problem/P2853

https://www.luogu.com.cn/problem/P1983

基础树上问题

https://www.luogu.com.cn/problem/P5836

https://www.luogu.com.cn/problem/P3629

https://www.luogu.com.cn/problem/P3379

https://www.luogu.com.cn/problem/P5536

最短路

https://www.luogu.com.cn/problem/P3371

https://www.luogu.com.cn/problem/P4779

https://www.luogu.com.cn/problem/P1629

https://www.luogu.com.cn/problem/P1144

https://www.luogu.com.cn/problem/P1522

最小生成树

https://www.luogu.com.cn/problem/P3366

https://www.luogu.com.cn/problem/P2872

https://www.luogu.com.cn/problem/P1396

https://www.luogu.com.cn/problem/P2121

https://www.luogu.com.cn/problem/P1194

https://www.luogu.com.cn/problem/P1195

https://www.luogu.com.cn/problem/P4047

连通性问题

https://www.luogu.com.cn/problem/P3387

https://www.luogu.com.cn/problem/P3388

https://www.luogu.com.cn/problem/P2341

https://www.luogu.com.cn/problem/P2863

https://www.luogu.com.cn/problem/P1726

算法训练营

https://vjudge.net/article/2652

图的存储

https://vjudge.net/problem/洛谷-P3916

https://vjudge.net/problem/UVA-11175

https://vjudge.net/problem/POJ-3275

图的遍历

https://vjudge.net/problem/UVA-572

https://vjudge.net/problem/UVA-1599

https://vjudge.net/problem/POJ-2488

https://vjudge.net/problem/POJ-3278

图的连通性

图的应用

最短路径

最小生成树

拓扑排序

关键路径

posted @ 2022-03-19 16:48  new-code  阅读(26)  评论(0编辑  收藏  举报