一篇学懂图(最简洁易懂代码模板)

本文详细介绍了图的基本术语,如顶点、边、子图,并对比了有向图和无向图的特性,包括度数计算、边的数量限制等。接着讨论了连通性和强连通的概念,以及最小生成树的Prim和Kruskal算法,以及单源最短路径的Dijkstra和Floyd算法。此外,还涵盖了拓扑排序及其在检测有向无环图(AOV网)中环的存在性。最后提到了图的深度优先搜索(DFS)算法及其应用实例。
摘要由CSDN通过智能技术生成

图中的一些基本术语:
顶点(Vertex) ,边(Edge),子图(Subgraph)

有向图和无向图关系比较:

1.度和边:
无向图度TD(vi)是边e的两倍
有向图TD(vi)=ID(vi)+OD(vi)

2.顶点n和边e:
无向图:0≤e≤n(n-1)/2
无向完全图(Undirected Complete Graph,UCG):e=n(n-1)/2 任意两个顶点之间均存在一条边
有向图:0≤e≤n(n-1)
有向完全图((Directed Complete Graph,DCG):e=n(n-1) 任意两个顶点之间存在方向相反的两条弧

3.无向图(u,v):u,v互为邻接点(Adjacent)
有向图<u,v>:顶点u的出边,顶点v的入边
无向边(va,vb);有向边<va,vb>

4.连通和强连通
无向图:
连通图(Connected Graph):有向图中,任意一对顶点 都连通
连通分量(Connected Component):非连通图的极大连通子图。连通图只有一个连通分量是本身
有向图:
强连通图(Srtongly Connected Graph):有向图中,每一对顶点之间都有一条互相到达的路径
强连通分量(Strongly Connected Component):非强连通图的极大强连通子图


权和网:
权(Weight):边上的数值
网(Network):带权的图


路径长度:
(不带权)图中指路径上经过边的数量
网中指路径上经过各边权之和


简单路径:
经过的顶点不重复,否则为非简单路径


回路/环(Cycle):
路径中第一个和最后一个顶点相同
简单回路(Simple Cycle):第一个顶点和最后一个顶点相同


连通图(Connected Graph)&连通分量(Connected Component):
连通图:有向图中,任意一对顶点 都连通
连通分量:非连通图的极大连通子图。连通图只有一个连通分量是本身


生成树和生成森林:
无向图:
生成树(Spanning Tree):具有G中全部顶点的一个极小连通子图。
有向图:
有向树:恰有一个顶点的入度为0,其余顶点入度为1
生成森林(Spanning Forest)由若干棵有向树组成


图片来源

最小生成树的两种算法

Prim 适合点少边多, Kruskal 适合边多点少。

1.Kruskal算法
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。

在这里插入图片描述
Kruskal代码如下:
时间复杂度为O(elog2e):因为sort()用的是快排思想O(nlog2n),e表示边数

先从小到大排序边,找到当前当前两顶点的父亲结点,在同一棵树上就丢弃,最后合并树

#include<iostream>
#include<algorithm>
using namespace std;
int f[200],n,m,k,len,Veru,Verv,vertex;
struct Edge{
    int u,v,w;
}a[200];
bool cmp(Edge u,Edge v){return u.w<v.w;}//规定排序
int find(int x){//作用:获取父亲顶点
    return f[x]=(f[x]==x?x:find(f[x]));
}
void Kruskal(){
    sort(a,a+m,cmp);//第一步:把所有边排序
    for(int i=1;i<=m;i++){
        Veru=find(a[i].u),Verv=find(a[i].v);//找当前这两顶点所在的父亲结点,即树
        if(Veru==Verv)  continue;//两顶点在同一颗树上就丢弃
        f[Veru]=Verv;//合并树
        len+=a[i].w;
        if(++vertex==n-1) break;//最后:此时所有顶点已经在同一棵树上,返回
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) f[i]=i;//刚开始顶点各自成树 例顶点1表示树1
    for(int i=1;i<=m;i++) cin>>a[i].u>>a[i].v>>a[i].w;//给所有边依次赋值
    Kruskal();
    cout<<len;
    return 0;
}

测试数据:

4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
7

2.Prim算法
Prim算法时间复杂度为O(n2)

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

在这里插入图片描述

Prim代码如下:

#include<iostream>
#include<cstring>
using namespace std;
#define N 20
bool vis[N];//记录走过的顶点
int a[N],G[N][N],n,m,len=0;
int Prim(){
    vis[1]=true;//从1号顶点开始
    for(int i=1;i<=n;i++) a[i]=G[1][i];
    for(int i=1;i<n;i++){
        int u=-1,t=0x3f3f3f;
        for(int j=1;j<=n;j++){//找到当前顶点最小代价的顶点
            if(t>a[j]&&vis[j]==false){
                t=a[j]; u=j;//u记录最小代价的顶点
            }
        }
        len+=a[u]; vis[u]=true;
        for(int i=1;i<=n;i++)
            a[i]=min(a[i],G[u][i]);//更新代价,若之前的代价比现在小,则不更新        
    }
    return len;
}
int main(){
    cin>>n>>m;
    memset(G,0x3f3f3f,sizeof(G));
    memset(a,0x3f3f3f,sizeof(a));
    for(int i=0;i<m;i++){
        int u,v,w;  cin>>u>>v>>w;
        G[u][v]=G[v][u]=w;//初始化
    }
    int t=Prim(); cout<<t;
    return 0;
}

测试数据:

6 10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6
15

最短路径的两种算法

Dijkstra求单源最短路径,Flyod求多源最短路径

1.Dijkstra算法
Dijkstra算法时间复杂度为O(n2)

先找到当前顶点到源顶点的最小距离dis[u],接着更新当前顶点周围顶点到源顶点的距离,然后依次往下找,直到找到最后一个顶点。

单源最短路问题可以看成对一个带权有向图构造最短路径树(SPT)的问题

在这里插入图片描述
图中绿色部分为当前访问过的顶点

Dijkstra代码如下:

#include<iostream>
#include<cstring>
using namespace std;
#define N 200
int n,m,G[N][N],dis[N];
bool vis[N];
void Dijkstra(){
    for(int i=1;i<=n;i++) dis[i]=G[1][i];
    vis[1]=true; dis[1]=0;//源顶点到自身为0
    for(int i=2;i<=n;i++){//遍历剩下n-1个顶点
        int u=0;
        for(int j=1;j<=n;j++){
            if(vis[j]==false&&dis[u]>dis[j])
                u=j;
        }
        vis[u]=true;
        for(int k=1;k<=n;k++)//这里是该算法的核心代码,看图理解即可
            dis[k]=min(dis[k],dis[u]+G[u][k]);//松弛操作
    }//dis[u]为当前标记顶点到源顶点的距离,dis[u]+G[u][k]为更新当前顶点周围点到源顶点的距离
    for(int i=1;i<=n;i++)   cout<<dis[i]<<" ";
}
int main()
{
    cin>>n>>m;
    memset(G,0x3f3f3f,sizeof G);
    memset(dis,0x3f3f3f,sizeof dis);
    for(int i=0;i<m;i++){
        int u,v,w; cin>>u>>v>>w;
        G[u][v]=w;
    }
    Dijkstra();
    return 0;
}

测试数据:

3 3
1 2 2
2 3 1
1 3 4
3

2.Floyd算法
Flyod时间复杂度为O(n3)
确定从点i到点j的代价,接着i经过k到j时有更小的代价就更新。例如1到3,1经过2到3,更小的话更新。1经过4到3,更小的话接着更新…

求每对顶点之间的最短路径是指带权有向图

理解Floyd核心代码部分是怎么推出的

Floyd代码如下:

#include<iostream>
using namespace std;
#define Inf 0x3f3f3f
int n,m,u,v,w,G[20][20];
int main()
{
    cin>>n>>m;
    //初始化
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(i==j) G[i][j]=0;
            else G[i][j]=Inf;
    //读入边
    for(int i=1;i<=m;i++){
        cin>>u>>v>>w;
        G[u][v]=w;
    }
    //执行Floyd核心代码
    for(int k=1;k<=n;k++)//中间顶点
        for(int i=1;i<=n;i++)//源顶点i
            for(int j=1;j<=n;j++)//目标顶点j
                G[i][j]=min(G[i][j],G[i][k]+G[k][j]);
    //输出任意两点间最短的代价
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)
            printf("%10d",G[i][j]);
        cout<<endl;
    }
    return 0;
}

测试数据:

4 4
1 2 4
2 3 7
2 4 1
3 4 6

拓扑排序算法
时间复杂度O(n+e)

采用邻接表存储
检测AOV网中是否存在环

拓扑排序代码如下:

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int n,m,in[100];
vector<int> v[100];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) v[i].clear();
    for(int i=1;i<=m;i++){
        int A,T; cin>>A>>T;//源顶点指向目标顶点
        v[A].push_back(T);
        in[T]++;//目标顶点的入度加1
    }
    priority_queue<int,vector<int>,greater<int> >q;//优先队列,设置顶点关系从小到大排序
    for(int i=1;i<=n;i++)
        if(in[i]==0) q.push(i);//1.把入度为0的点压入队列
    while(!q.empty()){
        int x=q.top(); q.pop();
        n--;//每次去掉一个点
        for(int i=0;i<v[x].size();i++){//依次删掉到当前度为0的这一条关系
            int y=v[x][i];//2.从入度0的顶点开始,找目标顶点
            in[y]--;//3.目标顶点的度少这一条
            if(in[y]==0) q.push(y);//4.如果当前顶点的入度变为0,则压入队列中
        }
    }
    if(n) cout <<"NO"<<endl;//如果有环的话节点数不会为0
    else cout <<"YES"<<endl;
    return 0;
}

测试数据:

3 3
1 2
2 3
1 3

染色法判定二部图算法代码暂时先不整理…


AOV和拓扑排序
(1) DAG(有向无环图):不含环的有向图,没有回路。
(2) AOV(顶点表示活动的网):顶点表示活动,有向边表示活动之间的优先制约关系。
(3) AOV网中不允许存在回路,回路的出现意味着某项活动的开工将以自身工作的完成作为先决条件,产生死锁。检测有向图中是否存在环的方法是进行拓扑排序。


AOE网与关键路径
(1) AOE(边表示活动的网),一个带权有向图,有向边表示活动,权值表示活动的持续时间,顶点表示时间。
(2) 特殊顶点:源点表示所有活动的开始,汇点表示整个活动的结束
(3) AOE网中不允许存在回路且唯一存在源点和汇点
(4) 关键路径:AOE网中最大长度的路径。关键活动:关键路径上的活动


图的2种遍历算法:
1.图的DFS算法
DFS算法代码如下:

#include<iostream>
using namespace std;
const int INF=0x3f3f3f;
int n,m,u,v,sum,a[20][20];
bool vis[20];
void Dfs(int U){
	sum++;
    cout<<U<<" ";//输出当前访问的顶点
	if(sum==n) return ; //已访问所有顶点,结束。 
	for(int i=1;i<=n;i++){
		if(vis[i]==false&&a[U][i]==1){ //访问未访问过的点 		
			vis[i]=true; //标记找到的点 
		    Dfs(i); //递归查找。 
		}
	}
	return ;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++){ //初始化 
		for(int j=1;j<=n;j++)
		if(i==j) a[i][j]=0; 
		else a[i][j]=INF;
	}
	for(int i=1;i<=m;i++){
		cin>>u>>v; //二维数组存储 
		a[u][v]=1;
	}
	vis[1]=true;
	Dfs(1);//从顶点1开始
	return 0;
}

测试样例 :

5 5
1 2
1 3
1 5
2 4
3 5

结果输出:

1 2 4 3 5 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值