数据结构 第七章 图

1.图的基本概念

图(graph)并不是指图形图像(image)或地图(map)。通常来说,我们会把图视为一种由“顶点”组成的抽象网络,网络中的各顶点可以通过“边”实现彼此的连接,表示两顶点有关联。注意上面图定义中的两个关键字,由此得到我们最基础最基本的2个概念,顶点(vertex)和边(edge)。
在这里插入图片描述

如上图所示,节点(vertex)用红色标出,通过黑色的边(edge)连接。
1.

与结点关联的边数,在有向图中为入度与出度之和。

出度:在有向图中以这个结点为起点的有向边的数目。(可形象的理解为离开这个结点的边的数目)

入度:在有向图中以这个结点为终点的有向边的数目。(可形象的理解为进入/指向这个结点的边的数目)

任意一个图的总度数等于其边数的2倍

2.连通

如果在同一无向图中两个结点存在一条路径相连,则称这两个结点连通。
(1)连通图

如果无向图中任意两个结点都是连通的,则称之为连通图。
(2)强连通/强连通图

如果有向图中任意两个结点之间存在两条路径(即(i,j)两点中,既从i到j有一条路径,j到i也有一条路径),则两点是强连通的。当一个图中任意两点间都是强连通的,则该图称之为强连通图。

在强连通图中,必定有一条回路经过所有顶点。

强连通分量:非强连通图有向图中的最大子强连通图。
3.回路
起点与相同的路径,又叫“环”。
4.完全图
任意两点间都存在边使其相连的无向图或任意两点间都存在两条不同边的有向图称作完全图

N个顶点的完全图:

有向 有n(n-1)条边
无向 有n(n-1)/2条边

完全图:任意两个点都有一条边相连

无向完全图:n个结点,一共有 C ( n , 2 ) C(n,2) C(n,2)条边

有向完全图:n ( n − 1 ) n(n-1) n(n−1)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.图的存储结构

2.1邻接矩阵表示法

所谓邻接矩阵存储结构就每个顶点用一个一维数组存储边的信息,这样所有点合起来就是用矩阵表示图中各顶点之间的邻接关系。所谓矩阵其实就是二维数组。

int g[N][N];
int main() {
	int n, m; //n个点 m条边 
	scanf("%d%d", &n, &m);
	int u, v; //从u到v
	for (int i = 0; i < m; ++i) {
		scanf("%d%d", &u, &v);
		g[u][v] = 1; 
		//g[v][u] = 1;//无向图要建双边 
		//g[u][v] = w; //带权图
	} 
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2邻接表表示法

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
邻接表的抽象数据类型:

#define MVNum 100                        	//最大顶点数 
typedef struct ArcNode{                		//边结点 
    int adjvex;                          		//该边所指向的顶点的位置 
    struct ArcNode * nextarc;          	//指向下一条边的指针 
    OtherInfo info;                      	              //和边相关的信息 
}ArcNode; 
typedef struct VNode{ 
    VerTexType data;                    	//顶点信息 
    ArcNode * firstarc;                	//指向第一条依附该顶点的边的指针 
}VNode, AdjList[MVNum];               	//AdjList表示邻接表类型 
typedef struct{ 
    AdjList vertices;                 		//邻接表 
    int vexnum, arcnum;              		//图的当前顶点数和边数 
}ALGraph; 


2.3邻接矩阵和邻接表区别

在这里插入图片描述

3.图的遍历

3.1图的遍历基础

定义:从图的任意指定顶点出发,依照某种规则去访问图中所有顶点,且每个顶点仅被访问一次,这一过程叫图的遍历。
方式:

深度优先遍历方法(Depth_First Search——DFS)按边一直搜索下去
广度优先遍历法(Breadth_Frist Search——BFS)按层一直搜索下去

3.2图的深度优先搜索方法

int a[N],cnt;
int dfs(int u){//深度优先搜索算法
     a[++cnt] = u;//用a数组存DFS序
     vis[u] = 1;
     for(int i = head[u]; i;i = nex[i]){
        int v = ver[i];
        if(vis[v])
            continue;
        dfs(v);
     }
     a[++cnt] = u;
}

在这里插入图片描述
深度优先搜索顺序:0 2 6 5 1 4 7 3

3.3图的广度优先搜索方法

void bfs(){//广度优先搜索算法
    memset(d,0,sizeof d);
    queue<int>q;
    q.push(1);
    d[1] = 1;
    while(q.size()){
        int u = q.front();
        q.pop();
        for(int i = head[u];i;i = nex[i]){
            int v = ver[i];
            if(d[v])continue;
            d[v] = d[u]+1;
            q.push(v);
        }
    }
}

广度优先遍历是一种按照层次顺序访问的方法。
它具有两个重要的性质:

  • 在访问完所有的第i层结点后,才会访问第i+1层结点。
  • 任意时刻,队列中只会有两个层次的结点,满足“两段性”和“单调性”。

上图广度搜索顺序:0 2 1 6 5 4 3 7

4.图的应用

4.1最小生成树

极小连通子图:该子图是G 的连通子图,在该子图中删除任何一条边,子图不再连通。
生成树:包含图G所有顶点的极小连通子图(n-1条边)。
在这里插入图片描述

  • 使用不同的遍历图的方法,可以得到不同的生成树
  • 从不同的顶点出发,也可能得到不同的生成树。
  • 按照生成树的定义,n 个顶点的连通网络的生成树有 n 个顶点、n-1 条边。

在网的多个生成树中,寻找一个各边权值之和最小的生成树,即为最小生成树

4.2Prim算法

每次选择当前点所连的边的最小值,然后把它连起来
有些类似 D i j k s t r a Dijkstra Dijkstra就是一个
普通版本的时间复杂度为 O ( n 2 ) O(n^2) O(n2)
堆优化的算法时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
在这里插入图片描述

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>

#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const int N = 4e5+7;

int ver[N],nex[N],edge[N],head[N],tot;
int n,m,ans;
int dis[N];
int vis[N],cnt;
void add(int u,int v,int val){
    ver[++tot] = v;
    edge[tot] = val;
    nex[tot] = head[u];
    head[u] = tot;
}

priority_queue<PII,vector<PII>,greater<PII> >q;

void prim(){
    dis[1] = 0;
    q.push({0,1});
    while(q.size()&&cnt != n){
        int d = q.top().first,u = q.top().second;
        q.pop();
        if(vis[u])continue;
        cnt++;
        ans += d;
        vis[u] = 1;
        for(int i = head[u];i;i = nex[i]){
            int v = ver[i];
            if(edge[i] < dis[v])
                dis[v] = edge[i],q.push({dis[v],v});
        }
    }
}

int main()
{
    memset(dis,0x3f,sizeof dis);
    scanf("%d%d",&n,&m);
    over(i,1,m){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    prim();
    printf("%d\n",ans);
    return 0;
}


4.3 K r u s k a l Kruskal Kruskal算法

每次选择权值最小的边,若该边两点没有加入集合,就将他加入。
起初每个点的都是一个独立的集合,把边权从小到达排序,按照边权枚举边,用并查集判断两个是否在同一个集合,如果在一个集合就跳过当前边,反之就联通这两个集合。
时间复杂度: O ( m l o g m ) O(mlogm) O(mlogm)
在这里插入图片描述

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<vector>

#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const int N = 2e5+7;

struct node{
    int x,y,z;
    bool operator<(node &t)const{
        return z < t.z;
    }
}edge[N];

int fa[N],n,m,ans;

int Find(int x){
    if(x == fa[x])return x;
    return fa[x] = Find(fa[x]);
}

int main()
{
    cin>>n>>m;
    over(i,1,m)
    scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);
    sort(edge + 1,edge + 1 + m);
    over(i,1,n)
    fa[i] = i;
    over(i,1,m){
        int x = Find(edge[i].x);
        int y = Find(edge[i].y);
        if(x == y)continue;
        fa[x] = y;
        ans += edge[i].z;
    }
    printf("%d\n",ans);
}

4.4Dijkstra算法

经典的最短路算法,基于贪心思想的,适用于非负权值图的经过优先队列或者线段树优化后的 O ( m l o g n ) O(mlogn) O(mlogn)的优秀算法。(m是边数,n是点数)

其实也超级简单,就是从起点开始,用一个dis数组存从起点到每一个点的最短距离,每次在当前点更新dis数组(可能经过当前点u到达的v点的总距离dis[u]+edge[v]是小于dis[v]就更新),然后往下走。
最后得到一个dis数组。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define debug(x) cout<<"#  "<<x<<" "<<endl;
typedef long long ll;
const ll mod=2147483647000;
const ll N=500007;
struct Edge
{
    ll v,w,next;//v:目的地,w:距离,next:下一个节点
}G[N];
ll head[N],cnt,n,m,s;
ll dis[N];//存距离
inline void addedge(ll u,ll v,ll w)//链式前向星存图
{
    cnt++;
    G[cnt].w=w;
    G[cnt].v=v;
    G[cnt].next=head[u];
    head[u]=cnt;
}
struct node
{
    ll d,u;//d是距离u是起点
    bool operator<(const node& t)const//重载运算符
    {
        return d>t.d;
    }
};
inline void Dijkstra()
{
    for(register int i=1;i<=n;++i)dis[i]=mod;//初始化
    dis[s]=0;
    priority_queue<node>q;//堆优化
    q.push((node){0,s});//起点push进去
    while(!q.empty())
    {
        node tmp=q.top();q.pop();
        ll u=tmp.u,d=tmp.d;
        if(d!=dis[u])continue;//松弛操作剪枝
        for(register int i=head[u];i;i=G[i].next)//链式前向星
        {
            ll v=G[i].v,w=G[i].w;
            if(dis[u]+w<dis[v])//符合条件就更新
            {
                dis[v]=dis[u]+w;
                q.push((node){dis[v],v});//沿着边往下走
            }
        }
    }
}
int main()
{
    scanf("%lld %lld %lld",&n,&m,&s);
    for(register int i=1;i<=m;++i)
    {
        ll x,y,z;
        scanf("%lld %lld %lld",&x,&y,&z);
        addedge(x,y,z);//建图
    }
    Dijkstra();
    for(register int i=1;i<=n;++i)
        printf("%lld ",dis[i]);
    printf("\n");
    return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值