目录
图的存储
图常用的两种存储方式是邻接矩阵跟邻接表
邻接矩阵
邻接矩阵定义
const int maxn=1005; //最大顶点数
int G[maxn][maxn]; //G存储图的边
图的邻接矩阵的遍历方式
深度优先遍历(DFS)
DFS脱单法则:先追一个妹子,如果不合适,换追这个妹子的朋友,还不合适就追妹子朋友的朋友…🤪
void dfs(int v){ //邻接矩阵的深度优先遍历
visit[v]=1; //标记已访问
cout<<v<<' '; //访问方式
for(int i=1;i<=N;i++){
if(visit[i]==0&&G[v][i]==1) //如果i未被访问且i与v有相连的边,则访问顶点i
dfs(i); //访问i
}
}
广度优先遍历(BFS)
BFS脱单法则:同时追几个妹子,直到找到合适自己的🤪
void bfs(int s){ //s为访问的起点
queue<int>q; //定义队列
q.push(s); //把起始顶点入队
visit[s]=1; //把起始顶点标记为已访问
while(!q.empty()){ //当队列不为空时,一直循环
int v=q.front(); //取队首顶点
cout<<v<<' '; //访问的方式
q.pop(); //访问完该顶点,即可将其出队
for(int i=1;i<=N;i++){//遍历所有顶点,找到所有与v相连的顶点
if(visit[i]==0&&G[v][i]==1){ //如果i未被访问且i与v有相连的边,则把i入队
visit[i]=1; //访问标记要在此处写
q.push(i); //入队
}
}
}
}
完整程序代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005; //最大顶点数
int G[maxn][maxn],N,M;
int visit[maxn]={0}; //初始化未访问
void dfs(int v){ //邻接矩阵的深度优先遍历
visit[v]=1; //标记已访问
cout<<v<<' '; //访问方式
for(int i=1;i<=N;i++){ //零阶矩阵需要遍历1~N
if(visit[i]==0&&G[v][i]==1) //如果i未被访问且i与v有相连的边,则dfs(i)
dfs(i); //访问i
}
}
void bfs(int s){ //s为访问的起点
queue<int>q; //定义队列
q.push(s); //把起始顶点入队
visit[s]=1; //把起始顶点标记为已访问
while(!q.empty()){ //当队列不为空时,一直循环
int v=q.front(); //取队首顶点
cout<<v<<' '; //访问的方式
q.pop(); //访问完该顶点,即可将其出队
for(int i=1;i<=N;i++){
if(visit[i]==0&&G[v][i]==1){ //如果i未被访问且i与v有相连的边,则把i入队
visit[i]=1; //访问标记要在此处写
q.push(i); //入队
}
}
}
}
int main(){
cin>>N>>M; //N为定点数1~N,M为边的个数
for(int i=0;i<M;i++){
int a,b;
scanf("%d %d",&a,&b);
G[a][b]=G[b][a]=1;//有向图的输入
//G[a][b]=1; 有向图的输入
}
// dfs(1); //深度优先遍历
// bfs(1); //广度优先遍历
return 0;
}
//输入
7 7
4 1
4 6
1 3
6 5
6 7
3 2
3 5
邻接表
邻接表的定义
vector<int>G[maxn]; //初始化大小maxn
图的邻接矩阵的遍历方式
深度优先遍历(DFS)
void dfs(int v){
visit[v]=1;
cout<<v<<' ';
for(int i=0;i<G[v].size();i++){
if(visit[G[v][i]]==0)
dfs(G[v][i]);
}
}
广度优先遍历(BFS)
void bfs(int s){
queue<int>q;
q.push(s);
visit[s]=1;
while(!q.empty()){
int v=q.front();
q.pop();
cout<<v<<' ';
for(int i=0;i<G[v].size();i++){
if(visit[G[v][i]]==0){ //注意顶点是G[v][i],而不是i
visit[G[v][i]]=1;
q.push(G[v][i]);
}
}
}
}
完整程序代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005; //最大定点数
int N,M;
vector<int>G[maxn]; //初始化maxn
int visit[maxn]={0}; //初始化全部顶点为未访问
void dfs(int v){
visit[v]=1; //标记已访问
cout<<v<<' '; //访问方式
for(int i=0;i<G[v].size();i++){ //与邻接矩阵不同的是,只需要遍历与v相连接的顶点
if(visit[G[v][i]]==0) //如果未访问
dfs(G[v][i]); //访问
}
}
void bfs(int s){
queue<int>q;
q.push(s);
visit[s]=1;
while(!q.empty()){
int v=q.front();
q.pop();
cout<<v<<' ';
for(int i=0;i<G[v].size();i++){
if(visit[G[v][i]]==0){ //注意顶点是G[v][i],而不是i
visit[G[v][i]]=1;
q.push(G[v][i]);
}
}
}
}
int main(){
cin>>N>>M; //N为定点数1~N,M为边的个数
for(int i=0;i<N;i++){
int a,b;
scanf("%d %d",&a,&b);
//无向图输入
G[a].push_back(b); //表示a与b相连
G[b].push_back(a); //表示b与a相连
//有向图输入
//G[a].push_back(b); //表示a指向b
}
dfs(1); //深度优先遍历
// bfs(1); //广度优先遍历
return 0;
}
图的相关算法
最短路径
单源最短路径(Dijktra)
Dijkstra脱单法则:找到所有自己亲近的朋友,以这些朋友为中介,让他们找到你想追的女孩的最短路径🤪
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
const int inf=0xfffffff; //设inf为无穷大
int G[maxn][maxn]; //存放图
int N,M,dis[maxn+1];//N为顶点数,M为边数,dis是存放源点到各个顶点的距离
bool visit[maxn]={false};//访问
void Dijkstra(int s){ //s为源点
fill(dis,dis+maxn+1,inf);//初始化源点到各顶点的距离都为无穷
dis[s]=0; //源点到源点的距离为0
for(int i=1;i<=N;i++){ //循环N次
int u=-1,min=inf;
for(int j=1;j<=N;j++){ //找到未访问,且与源点距离最短的点
if(dis[j]<min&&visit[j]==false){
u=j;
min=dis[j];
}
}
visit[u]=true; //标记未已访问
if(u==-1) return; //如果找不到则u还是为-1,即该源点与其他顶点不连通
for(int v=1;v<=N;v++){
if(visit[v]==false&&G[v][u]!=inf&&dis[u]+G[u][v]<dis[v]){ //以顶点u作为标尺优化所有能使距离变小的且未访问的顶点
dis[v]=G[u][v]+dis[u]; //更新dis[v]
}
}
}
}
int main(){
cin>>N>>M;
fill(G[0],G[0]+maxn*maxn,inf); //初始化所有边的距离为无穷
for(int i=0;i<M;i++){
int a,b,c; //a,b为顶点,c为权值(距离)
cin>>a>>b>>c;
G[a][b]=G[b][a]=c; //无向图
//有向图可写成以下
//G[a][b]=1;
}
Dijkstra(1); //源点为1
for(int i=1;i<=N;i++){
cout<<dis[i]<<' ';
}
return 0;
}
/**输入**
5 6
1 2 3
1 3 1
2 4 2
2 5 1
3 4 6
3 5 5
*/
/**输出**
0 3 1 5 4
*/
Dijkstra+DFS求解算法
算法思路分两部分:
1、对源点进行Dijkstra,在求最短路径的时候,需要用到二维数组pre[maxn]记录前驱顶点
2、通过对终点进行DFS,可枚举所用的最短路径,可统计最短路径的条数、或者计算每条路径的优化尺度等等,选择最优的路径。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005; //最大顶点数
const int inf=0x3fffffff; //设inf为无穷大
int G[maxn][maxn]; //存放图
int N,M,st,ed,dis[maxn+1],n;//N为顶点数,M为边数,dis是存放源点到各个顶点的距离,n为最短路径的条数
bool visit[maxn]={false};//访问
vector<int>pre[maxn],path;//pre[]为二维数组,来存每个顶点的前驱顶点,path为dfs时走的路径
void Dijkstra(int s){ //s为源点
fill(dis,dis+maxn+1,inf);//初始化源点到各顶点的距离都为无穷
dis[s]=0; //源点到源点的距离为0
for(int i=1;i<=N;i++){ //循环N次
int u=-1,min=inf;
for(int j=1;j<=N;j++){ //找到未访问且与源点距离最短的点
if(dis[j]<min&&visit[j]==false){
u=j;
min=dis[j];
}
}
visit[u]=true; //标记未已访问
if(u==-1) return; //如果找不到则u还是为-1,即该源点与其他顶点不连通
for(int v=1;v<=N;v++){
if(visit[v]==false&&G[u][v]!=inf){
if(dis[u]+G[u][v]<dis[v]){ //以顶点u作为标尺优化所有能使距离变小的且未访问的顶点
dis[v]=G[u][v]+dis[u]; //更新dis[v]
pre[v].clear(); //找到更短的路径,则删除顶点v的所有的前驱顶点
pre[v].push_back(u); //将u作为新的前驱顶点
}
else if(dis[u]+G[u][v]==dis[v]){//如果相等则说明短路路径不只一条
pre[v].push_back(u);//同样把u作为v的前驱结点
}
}
}
}
}
void DFS(int ed){ //ed为终点
path.push_back(ed);//将顶点ed放入路径中
if(ed==st){ //如果dfs到起点,这时一条以起点为st终点为ed的最短路径生成
n++;
for(int i=path.size()-1;i>=0;i--){ //由于是从终点往前dfs,故所走的路径是倒过来的,逆序输出即可
cout<<path[i]<<' ';
}
cout<<endl;
}
for(int i=0;i<pre[ed].size();i++){ //DFS顶点ed所有的前驱顶点
DFS(pre[ed][i]);
}
path.pop_back(); //递归到此处,所有经过顶点ed的路径已枚举完毕,即可删除。
}
int main(){
cin>>N>>M>>st>>ed; //N、M分别为顶点数跟边数,st为源点,ed为终点
fill(G[0],G[0]+maxn*maxn,inf); //初始化所有边的距离为很大的数来表示无穷
for(int i=0;i<M;i++){
int a,b,c; //a,b为顶点,c为权值(距离)
cin>>a>>b>>c;
G[a][b]=G[b][a]=c; //无向图
//有向图可写成以下
//G[a][b]=1;
}
Dijkstra(st); //源点为st
DFS(ed); //终点为ed
return 0;
}
/**输入**
6 7 1 6
1 2 3
1 3 4
1 5 1
2 3 1
2 4 1
3 6 2
4 5 1
*/
/**输出**
1 3 6
1 2 3 6
1 5 4 2 3 6
*/
全源最短路径(Floyd)
#include<bits/stdc++.h>
using namespace std;
const int maxv=500; //最大顶点数
const int INF=0xfffffff; //设INF为很大的数
int N,M; //N、M分别为顶点数跟边数
int dis[maxv][maxv];
void Floyd(){
for(int k=0;k<N;k++){
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
if(dis[i][k]!=INF&&dis[k][j]!=INF&&dis[i][k]+dis[k][j]<dis[i][j]){
dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
}
}
int main(){
cin>>N>>M;
fill(dis[0],dis[0]+maxv*maxv,INF); //初始化图中的所有边都为INF,即所有边都不存在
for(int i=0;i<N;i++)
dis[i][i]=0; //顶点i到i的距离为0
for(int i=0;i<M;i++){
int u,v,w; //u、v为顶点,w为边权
cin>>u>>v>>w;
//dis[u][v]=G[v][u]=w; //无向图
dis[u][v]=w; //有向图
}
Floyd(); //算法入口
for(int i=0;i<N;i++){ //打印
for(int j=0;j<N;j++)
cout<<dis[i][j]<<' ';
cout<<endl;
}
return 0;
}
/**输入**
6 8
0 1 1
0 3 4
0 4 4
1 3 2
2 5 1
3 2 2
3 4 3
4 5 3
*/
/**输出**
0 1 5 3 4 6
268435455 0 4 2 5 5
268435455 268435455 0 268435455 268435455 1
268435455 268435455 2 0 3 3
268435455 268435455 268435455 268435455 0 3
268435455 268435455 268435455 268435455 268435455 0
*/
最小生成树
最小生成树之Prim
Prim跟Dijkstra的思路基本相同,区别是dis数组的意义不同,在Dijsktra算法中dis数组记录的是顶点到源点的距离,而Prim算法中dis记录的是顶点到集合的距离。
#include<bits/stdc++.h>
using namespace std;
const int maxv=1005; //最大顶点数
const int INF=0xfffffff; //设INF为很大的数
int N,M,G[maxv][maxv]; //N、M分别为顶点数跟边数
int dis[maxv]; //顶点与集合S的最短距离
bool visit[maxv]={false}; //标记是否访问的数组,初始化为未访问
int Prim(int s){ //参数s为源点
fill(dis,dis+maxv,INF); //初始化源点s到所有点的距离为INF
dis[s]=0; //源点到自己的距离为0
int sum=0; //记录边权之和
for(int i=0;i<N;i++){
int u=-1,MIN=INF;
for(int j=0;j<N;j++){
if(visit[j]==false&&dis[j]<MIN){
u=j;
MIN=dis[j];
}
}
if(u==-1)return -1;
visit[u]=true; //标记已访问
sum+=dis[u]; //计算边权
for(int v=0;v<N;v++){
//如果以u为中介能使dis[v]距离更短,则优化
if(visit[v]==false&&G[u][v]!=INF&&G[u][v]<dis[v]){
dis[v]=G[u][v]; //将G[u][v]赋给dis[v]
}
}
}
return sum; //返回最小生成树的边权之和
}
int main(){
cin>>N>>M;
fill(G[0],G[0]+maxv*maxv,INF); //初始化图中的所有边都为INF,即所有边都不存在
for(int i=0;i<M;i++){
int u,v,w; //u、v为顶点,w为边权
cin>>u>>v>>w;
G[u][v]=G[v][u]=w; //无向图
//G[u][v]=w; 有向图
}
cout<<Prim(0); //以0为源点
return 0;
}
/**输入**
6 10
0 1 4
0 4 1
0 5 2
1 2 6
1 5 3
2 3 6
2 5 5
3 4 4
3 5 5
4 5 3
*/
/**输出**
15
*/
最小生成树之Kruskal
Kruskal算法最耗时的部分是排序,所以适合用于边少的图(稀疏图)
算法思想:
1、对所有边按边权从小到大排序。
2、然后遍历所有边,依次判断该边的两个顶点是否在同一个集合中(利用并查集中的查找父亲函数Find()),如果不在同一个集合则将它们合并(利用并查集中的Union(a,b)函数)且统计合并的边数跟总边权。
3、遍历完后,判断边数是否是顶点数减一,是则是连通图,反之不是连通图。
#include<bits/stdc++.h>
using namespace std;
struct Node{
int u,v;
int cost;
};
const int Maxn = 10005; //最大顶点数
int N,M,cost=0,egen=0; //N、M分别为顶点数跟边数,cost为边权,egen为生成树的边
vector<Node >T;
int father[Maxn];
int Find(int x){
while(x!=father[x]){ //找树根
x=father[x];
}
return x;
}
void Union(int x,int y){ //并查集合并两个元素到一个集合中
x=Find(x);
y=Find(y);
if(x<y)
father[x]=y;
else
father[y]=x;
}
int cmp(Node a,Node b){
return a.cost<b.cost;
}
int Kruskal(){
for(int i=0;i<=N;i++) //初始化顶点的根为自己
father[i]=i;
sort(T.begin(),T.end(),cmp); //对边进行排序
for(int i=0;i<T.size();i++){ //遍历所有边
if(Find(T[i].u) != Find(T[i].v)){ //如果两个顶点不在一个集合中
Union(T[i].u,T[i].v); //合并一个集合中
cost+=T[i].cost; //统计边权
egen++; //统计边数
}
}
if(egen!=N-1)return -1; //如果边数 != 顶点数-1。说明该图不连通返回-1
else return cost;
}
int main(){
cin>>N>>M;
for(int i=0;i<M;i++){
Node temp;
cin>>temp.u>>temp.v>>temp.cost;
T.push_back(temp);
}
cout<<Kruskal();
return 0;
}
/**输入**
6 10
0 1 4
0 4 1
0 5 2
1 2 1
1 5 3
2 3 6
2 5 5
3 4 5
4 5 3
3 5 4
*/
/**输出**
11
*/