「笔记」图论模板
今天闲的没事来整理一下图论的模板(某些出自他处)
s勾p的邻接矩阵(adjacency matrix)
定义:
逻辑结构分为两部分:V和E集合。因此,用一个一维数组V(vertex)存放图中所有顶点数据;用一个二维数组E(edge)存放顶点间关系(边或弧)的数据,这个二维数组称为邻接矩阵。
代码实现:
//无向图
#include<iostream>
#include<cstring>
using namespace std;
int e,n;
int g[101][101];
int w;
int main() {
int x,y,z;
cin>>n>>e;
memset(g,0,sizeof(g));
for(int i=1; i<=e; i++) {
cin>>x>>y>>z;
g[x][y]=g[y][x]=z;
}
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
printf("%d ",g[i][j]);
}
cout<<endl;
}
}
s勾p的邻接表(Adjacency list)
定义:
邻接表,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。
对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。
邻接表是图的一种最主要存储结构,用来描述图上的每一个点。对图的每个顶点建立一个容器(n个顶点建立n个容器),第i个容器中的结点包含顶点Vi的所有邻接顶点。实际上我们常用的邻接矩阵就是一种未离散化每个点的边集的邻接表。
在有向图中,描述每个点向别的节点连的边(点a->点b这种情况)。
在无向图中,描述每个点所有的边(点a-点b这种情况)
代码实现:
#include<bits/stdc++.h>
using namespace std;
struct edge {
int u,v,w,next;
} edge[10001];
int first[1001],num=0;
int n,m;
void add(int u,int v,int w) {
num++;
edge[num].next=first[u];
edge[num].u=u;
edge[num].w=w;
edge[num].v=v;
first[u]=num;
}
int main() {
scanf("%d%d",&n,&m);
int u,v,w;
for(int i=1; i<=m; i++) {
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
for(int i=1; i<=n; i++) {
int k=first[i];
while(k!=0) {
cout<<edge[k].u<<" "<<edge[k].v<<endl;
k=edge[k].next;
}
}
return 0;
}
s勾p的深搜遍历(DFS)
#include<bits/stdc++.h>
#include<cstring>
using namespace std;
int e[1010][1010];
int vis[1010];
int n,m;
void dfs(int u);
int main() {
cin>>n>>m;
char x,y;
for(int i=1; i<=m; i++) {
cin>>x>>y;
e[x-65][y-65]=e[y-65][x-65]=1;
}
dfs(0);
return 0;
}
void dfs(int u) {
cout<<char(u+65)<<" ";
vis[u]=1;
for(int i=1; i<=n; i++) {
if(e[u][i]==1 && vis[i]==0) {
dfs(i);
}
}
}
s勾p的广搜遍历(BFS)
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int e[1010][1010];
int vis[1010],que[1010];
int n,m;
void bfs(int u);
int main() {
cin>>n>>m;
char x,y;
for(int i=1; i<=m; i++) {
cin>>x>>y;
e[x-65][y-65]=e[y-65][x-65]=1;
}
bfs(0);
return 0;
}
void bfs(int u) {
int h=0,t=1;
que[t]=u;
cout<<char(u+65)<<" ";
vis[u]=1;
while(h<t) {
h++;
int k=que[h];
for(int i=1; i<=n; i++) {
if(e[k][i]==1 && vis[i]==0) {
t++;
cout<<char(i+65)<<" ";
vis[i]=1;
que[t]=i;
}
}
}
}
s勾p的floyed算法
原理:
利用邻接矩阵判断
步骤:
1.把所有的边赋值无穷大,能通过的赋值
2. 三个for循环,k,i,j, k表示从i->j可以走i->k->j,不断取小值
3.选取出发点和结束点,直接输出就是最短路
代码实现:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAX = 300;
const int inf = 0x3f3f3f3f;
int d[MAX],ditu[MAX][MAX];
int main() {
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1; i <= n ; i++)
for(int j = 1; j <= n; j++)
ditu[i][j] = inf;
int x, y, t;
for(int i = 1; i <=m; i++) {
scanf("%d%d%d",&x,&y,&t);
ditu[x][y] = t;
}
for(int k = 1; k <= n ; k++) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(i!=k && i!=j && ditu[i][j]>ditu[i][k]+ditu[k][j])
ditu[i][j] = ditu[i][k]+ditu[k][j];
}
}
}
printf("%d",ditu[1][n]);
return 0;
}
然后我们可以进行两个优化
(1)、剪枝:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<string>
using namespace std;
int qread() {
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
template<typename T>inline void write(T x) {
if(x<0)putchar('-'),x*=-1;
if(x>=10)write(x/10);
putchar(x%10+'0');
}
int dis[1010][1010];
int main() {
int n,m,a,b,s;
cin>>n>>m;
for(int i=1; i<=m; ++i) {
a=qread();
b=qread();
s=qread();
dis[a][b]=dis[b][a]=s;
}
for(int k=1; k<=n; ++k)
for(int i=1; i<=n; ++i)
if(i!=k) {
for(int j=1; j<=i; ++j) {
if(i!=j&&j!=k&&dis[i][j]>dis[i][k]*dis[k][j]&&dis[i][k]!=0&&dis[k][j]!=0) {
dis[i][j]=dis[i][k]*dis[k][j];
dis[i][j]%=9987;
dis[j][i]=dis[i][j];
}
}
}
write(dis[1][n]);
return 0;
}
(2)、判断
#include<bits/stdc++.h>
using namespace std;
long long g[1010][1010];
long long n,m,a,b,s;
int qread()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int main() {
n=qread();m=qread();
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
g[i][j]=0xffffff;
}
}
for(int i=1; i<=m; i++) {
a=qread();b=qread();s=qread();
g[a][b]=s;
}
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
if(i!=k && g[i][k]!=0xffffff) {
for(int j=1; j<=n; j++) {
if(i!=j&&j!=k&&g[i][j]>g[i][k]*g[k][j]) {
g[i][j]=g[i][k]*g[k][j];
}
}
}
printf("%lld",g[1][n]%9987);
return 0;
}
插一句,一起食用更佳哦
s勾p的Dijkstra算法
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均采用永久和临时标号的方式。注意该算法要求图中不存在负权边。
代码实现:
//Dijkstra
#include<iostream>
#include<cstdio>
#define INF 9999999
using namespace std;
const int maxn=1010;
int dis[maxn];//储存单源最短路径
int w[maxn][maxn];//初始储存两点之间是否有路径
int f[maxn]= {0};
int n,m,s;
int x,y,z;
//输入及初步处理
void work() {
cin>>n>>m>>s;
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
if(j==i)w[i][j]=0;//自己到自己的路径是0
else w[i][j]=INF;//否则初始化为最大值
}
}
for(int i=1; i<=m; i++) {
scanf("%d%d%d",&x,&y,&z);//分别代表x到y之间有边,权值为z
w[x][y]=z;//无向图的话
w[y][x]=z;
}
for(int i=1; i<=n; i++) {
dis[i]=w[s][i];//有路径呗
f[i]=0;
}
}
//迪杰斯特拉算法求单源最短路径
//这就是模板啦!!
//木啥好说的
void dijkstra() {
dis[s]=0;
f[s]=1;
for(int i=1; i<=n; i++) {
int mind=INF;
int k;
for(int j=1; j<=n; j++) {
if(dis[j]<mind && !f[j]) {
mind=dis[j];
k=j;
}
}
if(mind==INF)break;//这里我经常忘记恒等于号,错了好几遍
f[k]=1;
for(int j=1; j<=n; j++) {
if(dis[j]>dis[k]+w[k][j]) {
dis[j]=dis[k]+w[k][j];
}
}
}
}
//输出函数
void write() {
for(int i=1; i<=n; i++) {
cout<<dis[i]<<" ";
}
}
int main() {
work();
dijkstra();
write();
return 0;
}
可以进行堆优化
#include<algorithm>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int,int> pairs;
priority_queue<pairs,vector<pairs>,greater<pairs> >q ;
int dis[100007],head[100007];
bool vis[100007];
int n,m,s,t,cnt;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
struct Edge{
int next,to,w;
}edge[100007];
void add_edge(int from,int to,int w){
edge[++cnt].next=head[from];edge[cnt].w=w;
edge[cnt].to=to;head[from]=cnt;
}
inline void dijkstra(){
memset(dis,0x3f,sizeof dis);
dis[s]=0;
q.push(make_pair(dis[s],s));
while(!q.empty()){
int x=q.top().second;q.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i=head[x];i;i=edge[i].next){
int to=edge[i].to;
if(dis[x]+edge[i].w<dis[to]){
dis[to]=dis[x]+edge[i].w;
q.push(make_pair(dis[to],to));
}
}
}
}
int main(){
n=read(),m=read(),s=read();
for(int i=1;i<=m;++i){
int u=read(),v=read(),w=read();
add_edge(u,v,w);
}
dijkstra();
for(int i=1;i<=n;++i)printf("%d ",dis[i]);
return 0;
}
s勾p的SPFA算法
是一种求单源最短路的算法
几乎所有的最短路算法其步骤都可以分为两步
1.初始化
2.松弛操作
1、初始化
d数组全部赋值为INF(无穷大);p数组全部赋值为s(即源点),或者赋值为-1,表示还没有知道前驱。然后d[s]=0; 表示源点不用求最短路径,或者说最短路就是0。将源点入队;(另外记住在整个算法中有顶点入队了要记得标记vis数组,有顶点出队了记得消除那个标记)
2、队列+松弛操作
读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队
以此循环,直到队空为止就完成了单源最短路的求解
代码实现:
#include<bits/stdc++.h>
#define INF 99999999
using namespace std;
const int maxn=1010;
int w[maxn][maxn],vis[maxn]= {0},dis[maxn];
int path[maxn],num[maxn]= {0};
int n,m,s;
int main() {
cin>>n>>m>>s;
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
if(j==i)w[i][j]=0;
else w[i][j]=INF;
}
}
int x,y,z;
for(int i=1; i<=m; i++) {
scanf("%d%d%d",&x,&y,&z);
w[x][y]=z;
w[y][x]=z;
}
for(int i=1; i<=n; i++) {
dis[i]=INF;
}
queue<int> dui;
dui.push(s);
dis[s]=0;
vis[s]=1;a
while(!dui.empty()) {
int u=dui.front();
dui.pop();
vis[u]=0;
for(int v=1; v<=n; v++) {
if(w[u][v]!=INF) {
if(dis[u]+w[u][v]<dis[v]) {
dis[v]=dis[u]+w[u][v];
if(!vis[v]) {
dui.push(v);
vis[v]=1;
}
}
}
}
}
for(int i=1; i<=n; i++) {
cout<<dis[i]<<" ";
}
return 0;
}
(几百年之后可能会续更)