基本的图算法

2023-11-15

基本的图算法主要是两个方面:图的表示和图的搜索。我们主要通过邻接链表和邻接矩阵对图进行表示,但是在图算法更重要的是图的搜索,图的搜索指的是系统化的跟随图中的边来访问图中的每个节点,我们可以通过图的搜索算法发现图的结构。或者换个方面想图的算法就是通过图的搜索得到图的结构,所以图的算法就是对基本的搜索加以优化,因此图的搜索技巧是整个图算法的核心。

图的表示

对于一个图,我们有两种标准表示方法表示。一种表示法将图作为邻接链表的组合,另一种表示法则将图作为邻接矩阵来看待,两种方法既可以表示有向图也可以表示无向图。

两种方法的主要区别在于,邻接链表在表示系稀疏图时非常紧凑而成为通常的选择,但是在稠密图的情况下,我们更倾向于使用邻接矩阵表示法,此外如果要快速判断任意两个节点之间是否有边相连,使用邻接矩阵表示法更方便。

由上图我们就能清晰的看到邻接矩阵与邻接链表的结构,临界矩阵通过建立点的个数个链表储存图的结构,每条链表代表与其相连的节点,而邻接矩阵就更浅显易懂了,通过01的表示储存两个节点间是否存在联系,我们更直接的能看到,对于无向图邻接矩阵是一个对称矩阵,而有向图则不然。图上我们看到,对于邻接链表所需的存储空间为(V+E),而对于邻接矩阵,我们需要V^2的空间存储矩阵。

如果要表示权重图,对于邻接链表,我们需要将权值存储在邻接链表中即可,而对于邻接矩阵,我们将权重存放于对应位置即可,不存在的边存储为NIL。

对图的多数算法需要维持图中结点或边的某种属性,我们在邻接链表需要使用额外的数组来表示节点属性。

广度优先搜索(BFS)

广度优先搜索时最简单的图搜索算法之一,也是许多重要的图算法的模型。给定一个源节点s,广度优先搜索可以发现从源节点s能够到达的所有节点,算法能够计算出到每个节点的最短距离,同时生成一棵广度优先搜索树。

广度优先搜索始终将已发现的节点与未发现节点的边界,沿其广度方向向外拓展,简而言之就是搜索的时候首先发现距离源节点为k的节点,再发现距离为(k + 1)的节点。

所以我们在搜索过程中就是建立一个搜索树的过程,最开始只有根节点,在向下发现节点时,每发现一个就将其加入树中,按照对于边(u , v)节点u为v的父节点,对于每个节点,他至多被发现一次,最多只有一个父节点。我们将未被发现的节点记为白色,处在发现队列中的点记为灰色,与其邻接的节点都被发现的节点记为黑色。

 

 从伪代码我们不难看出除了搜索的过程和树的建立,我们还需要一个优先队列先进先出管理刚被发现尚未检索完毕的节点。分析其运行时间可得,总时间为O(V + E).

这段BFS是从某道做过的题里扒出来的,将就着看把大伙。

void BFS(int i, int j,int n,int m) {
    queue<Node> que;                     //定义队列
    Node tep_node;
    
    tep_node.x = i,tep_node.y = j;
    que.push(tep_node);                      //入队
    inque[i][j] = true;                  //标记已经入队
    while(!que.empty()) {                //队列非空
        Node top = que.front();          //取出队首元素
        que.pop();                       //队首元素出队
        /*for(int i = 0; i < 4; ++i) {     //访问相邻的四个元素
        	int tepi = top.x;
        	int tepj = top.y;
            int nowI = top.x + X[i];
            int nowJ = top.y + Y[i];
            if(judge(nowI,nowJ,tepi,tepj,m,n)) {
                node.x = nowI,node.y = nowJ;
                que.push(node);          //入队
                inque[nowI][nowJ] = true;//标记已经入队
                count++;
            }*/
        }
    }
    if(count > tep)tep = count;
    count = 1;
}

深度优先搜索

深度优先搜索就像名字所说的,只要可能,就在图中尽量深入。深度优先搜索(DFS)总是对刚出现的节点的出发边进行搜索,直到所有出发边都被发现为止。同时,一旦所有出发边都被发现,那么搜索就回溯到节点的前驱节点,来搜索前驱节点的出发边。搜索将一直持续直到可以到达的节点都被发现,如果存在未被发现的节点,则取出一个节点作为新的源节点并重复上述过程,直至所有节点都被发现。

与上文所说BFS不同,广度优先搜索的前驱子图形成一棵树,而深度优先搜索的子图是由多棵树组成,因为搜索可能从多个源节点重复进行。因此广度优先搜索的过程将创建一颗广度优先树,深度优先搜索形成的是一个由多个深度优先树构成的深度森林。而且值得注意的是,每个节点仅在一颗深度优先树中出现,即所有深度优先树不相交。

此外,深度优先树要在节点上盖上时间戳:第一次被发现的时间和最后一次被发现的时间(涂灰和涂黑的时间)。

下面是伪代码:

 由伪代码可知,深度优先搜索的运行时间为o(V+E).

同理这个也是从题里面扒出来的。

int dfs(int p = s, int cur_flow = INF){
    if (p == t) return cur_flow;
    long long ret = 0;
    for (int i = cur[p]; i != 0; i = edges[i].next){
        cur[p] = i;
        long long v = edges[i].to, vol = edges[i].weight;
        if (level[v] == level[p] + 1 && vol > 0){
            int f = dfs(v, min(cur_flow - ret, vol));
            edges[i].weight -= f;
            edges[i^1].weight += f;
            ret += f;
            if (ret == cur_flow) return ret;
        }
    }
    return ret;
}

拓扑排序

拓扑排序是对图G中所有节点的一次线性排序,次序满足如下条件:如果图G包含边(u,v),则节点u在拓扑排序中处于节点v的前面(如果有环路,则无法排序),因此可以将一个图的拓扑排序看作是将图的所有结点在一条水平线上排开,图的所有有向边都从左指向右。借用老师的图就是这样的:

 拓扑排序很简单伪代码就三行,懒得放了,直接放点代码把。

这段代码写的是最大拓扑排序,先说拓扑排序应该怎么做吧。拓扑排序就是每次找入度为0的点进入优先队列,然后出队并把他的边去掉,再次进行循环,最后排成一列。

而最大拓扑排序写的是一个动态过程(就是黑心助教改题),每次找到一个入度为0的节点入队后,马上更新找下一个最大的入度为零的节点,因此就是一个动态的过程,相当于每次循环我们只做两件事,第一件事是找出最大的入度为零的点,第二件事是把与这个节点有关的边全都抹去,循环判断条件就是优先队列里没元素了就停止(是不是听起来很简单,就是很简单)。你问我为什么不放拓扑排序的代码,因为懒不想改了。

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

const int maxn = 4e5 + 5;
int head[maxn];
int ind[maxn];// 记录入度
int ans[maxn];//记录答案
int cnt;
int n, m;

struct EdgeNode{
	int to;
	int w;
	int next;
};
EdgeNode edge[maxn];

void init(){
	cnt = 0;
	memset(ans, 0, sizeof(ans));
	memset(head, -1, sizeof(head));
	memset(ind, 0, sizeof(ind));
}

void top(){
	priority_queue<int, vector<int>, less<int> > Q;
	for(int i = 1; i <= n; i++){
		if(!ind[i]){
			Q.push(i);
		}	
	}
	int num;
	while(!Q.empty()){
		num = Q.top();
		ans[cnt++] = num;
		Q.pop();
		for(int i = head[num]; i != -1; i = edge[i].next){
			int v = edge[i].to;
			ind[v]--;
			if(ind[v] == 0)
				Q.push(v);
		}
	}
	
    for(int i = 0; i < cnt; i++)
    {
        if(i)
            printf(" ");
        printf("%d",ans[i]);
    }
    printf("\n");
} 
 
int main(){
	cin >> n >> m;
	init();
	for(int i = 1 ; i <= m; i ++){
		int a,b;
		cin >> a >> b;
		edge[i].to = b;
		edge[i].next = head[a];
		head[a] = i;
		ind[b] ++; 
	}
	top(); 	
	
}

链式前向星

刚开始上手一脸懵逼,上手了之后都说好的东西。想了半天怎么解释,还是算了直接上代码。(聪明的人一看就知道我把上面的拓扑排序放的代码截了两段过来)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5 + 5;
int head[maxn];
int ind[maxn];// 记录入度
int ans[maxn];//记录答案
int cnt;
int n, m;

struct EdgeNode{
	int to;
	int w;
	int next;
};
EdgeNode edge[maxn];

int main(){
	cin >> n >> m;
	init();
	for(int i = 1 ; i <= m; i ++){
		int a,b;
		cin >> a >> b;
		edge[i].to = b;
		edge[i].next = head[a];
		head[a] = i;
		ind[b]++; 
	}	
	
}

说实话,就是链表本来是动态的,现在变成静态的罢了,存图可能更形而上学一点,解释一下各个变量含义,to是终点,w是边权,next是与这个边起点相同的上一条边的编号,同时也看到了定义的head数组,head[i]表示以i为起点的最后一条边的编号,这样就把整个图串成一串了。

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

output:
1 //以1为起点的边的集合
1 5 6
1 3 4
1 2 1
 
2 //以2为起点的边的集合
2 3 2
 
3 //以3为起点的边的集合
3 4 3
 
4  //以4为起点的边的集合
4 5 7
4 1 5
 
5 //以5为起点的边不存在

输入进来的过程:

对于1 2 1这条边:edge[0].to = 2;     edge[0].next = -1;      head[1] = 0;

对于2 3 2这条边:edge[1].to = 3;     edge[1].next = -1;      head[2] = 1;

对于3 4 3这条边:edge[2].to = 4;     edge[2],next = -1;      head[3] = 2;

对于1 3 4这条边:edge[3].to = 3;     edge[3].next = 0;       head[1] = 3;

对于4 1 5这条边:edge[4].to = 1;     edge[4].next = -1;      head[4] = 4;

对于1 5 6这条边:edge[5].to = 5;     edge[5].next = 3;       head[1] = 5;

对于4 5 7这条边:edge[6].to = 5;     edge[6].next = 4;       head[4] = 6;


遍历函数

for(int i = 1; i <= n; i++)//n个起点
    {
        cout << i << endl;
        for(int j = head[i]; j != -1; j = edge[j].next)//遍历以i为起点的边
        {
            cout << i << " " << edge[j].to << " " << edge[j].w << endl;
        }
        cout << endl;
    }

累了,后面的图算法可能得等更久才能憋出来了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基本的图算法 的相关文章

随机推荐

  • No2.7 前端面试题 1. token 2. 浏览器页面渲染的过程 3. SVG格式 4. 精灵图和base64

    1 token 什么是token token是验证身份的令牌 一般是用户通过账号密码登录后 服务端把这些凭证通过加密等一系列操作后得到的字符串 token都存在哪里 有什么区别 存localstorage里 后期每次请求接口时都需要把它当做
  • 索尼的hlg是什么_索尼HLG的使用方法

    近几年HLG的出现 让我们普通的摄影爱好者 可以直接的拍摄高动态范围的影像 尤其是小型的团队和独立的视频制作人 HLG能让我们在拍摄完素材够后 稍微调整就可以得到很不错的画面效果 能让我们省去很多后期调色的麻烦 这对于没有调色基础的摄影爱好
  • ABAP DIALOG 读取屏幕字段和tablecontrol内字段

    在POV事件中 因为没有经过PAI直接进入POV中的MODULE 所以直接调用屏幕中的字段并没有值 所以要 用DYNP VALUES READ读取屏幕字段的值 且读取的值格式为输入的格式 当使用DYNP VALUES READ时 所读取的屏
  • VMware vCenter Server远程代码执行漏洞复现 CVE-2021-21972

    文章来源 MS08067安全实验室 本文作者 Taoing WEB高级攻防班讲师 0x00 漏洞描述 CVE 2021 21972 vmware vcenter的一个未任意位置 然后执行webshell即可 0x01 影响版本 VMware
  • PyTorch学习之 torch.optim 的6种优化器及优化算法介绍

    import torch import torch nn functional as F import torch utils data as Data import matplotlib pyplot as plt import nump
  • 产品经理如何收集用户需求和痛点-新做市面上同类产品

    对于市面上已有同类产品 我们要做类似的产品 要使新做出来的产品有竞争力 首先需要深入了解客户需求和痛点 了解用户使用竞品的感受和痛点 在办公室冥想客户需求 并不靠谱 办公室做产品的结果 很多产品到客户那一用 才发现问题很多 很多实际情况没有
  • CiteSpace 的安装与使用 —— 入门

    下载 CiteSpace 是一种可视化的工具 在写论文的时候便于用来筛选对自己写文章有用的论文 CiteSpace 是一款免费的软件 可以直接到官网下载安装 注意 要配置 Java 环境才能使用 安装 下载后直接双击即可 双击打开应用 首先
  • Qt中使用QProcess备份和恢复Mysql数据库

    使用Qt做MySQL数据库开发 遇到需要备份 还原数据库的问题 MySQL中没有提供将数据库备份成 sql文件的SQL语句 而是提供了一个mysqldump exe工具来完成这个功能 没有SQL语句 QSqlQuery就用不成了 决定改用Q
  • 多线程四部曲之NSThread

    NSThread是什么 众所周知在iOS多线程开发主要有四种方式 NSThread就是其中一种 下面是apple官方给出的解释 可以看出NSThread是apple封装的一个线程类 开发人员可以对线程进行操作 并且可以监控线程状态 NSTh
  • flink 问题记录

    文章目录 1 Caused by java lang UnsatisfiedLinkError org apache hadoop util NativeCrc32 nativeComputeChunkedSums IILjava nio
  • Python每日一记125>>>pandas设置数字格式:小数位数、百分号、千位分隔符

    更改数字格式貌似用的比较多 直接上代码了 import numpy as np import pandas as pd data 2019 pd read excel C Users 02180085 Desktop 会员新旧离返 19年
  • UnityShader属性在属性面板的控制显示

    UnityShader属性面板的控制参数 HideInInspector 在显示面板隐藏属性 NoScaleOffset 材质面板不显示UV偏移 Normal 表明贴图为法线贴图 HDR 表示贴图是HDR贴图 Gamma 表示float v
  • VS2019写的代码无法在VS2022编译处理方法

    将VS2019 下面 netframework 依赖复制到 VS2022 下面 路径 C Program Files x86 Reference Assemblies Microsoft Framework NETFramework 重启V
  • 量化投资学习-33:MACD量化交易

  • 微信小程序(一):微信小程序与服务器的简单链接

    生活无趣且不易 一起找点乐子吧 欢迎评论 和文章无关的东西也没关系 关于小程序的有些问题 我搜索不到太有价值的东西 可能是我对关键字的理解不好 在这里我总结下遇到各种问题 可能看来会比较可笑 但对新手来说也许会有些帮助 我会尽量去注重具体的
  • 微众银行DSS部署单机-普通版

    DSS 普通版部署 我的服务器 我的配置 vim conf config sh vim conf db sh QA 我的服务器 centos 7 0 8C16G 100G机械硬盘 我的配置 bashrc文件内容 JDK export JAV
  • 基本稳压电路

    经过整流后的电源具有较大的电压纹波 单靠调节滤波电容不能明显改善输出电源纹波特性 因此需要采用稳压电路来减小输出电源的纹波 若直将稳压管接至负载输出 则稳压管的工作特性受负载影响较大 甚至会出现不能正常工作的情况 采用下图所示的稳压电路则能
  • 9种小程序赚钱方法!看懂的人已经在行动了

    小程序自上线以来 市面上出现了越来越多与小程序相关的行业 针对目前市面上已出现的小程序商业形式 微趋道今天整理出了以下9种小程序盈利模式分享给大家 微趋道 就是小程序 纯小程序创业 自小程序上线以来 不断有创业者加入到小程序创业中 小程序相
  • 【软件测试 #1】策略练习题

    软件测试策略习题 1 单选 根据软件需求规格说明书 在开发环境下对已经集成的软件进行的测试是 A 集成测试 B 单元测试 C 系统测试 D 验收测试 正确答案 C 2 单选 集成测试对系统内部的交互以及集成后系统功能检验了哪一种质量特性 A
  • 基本的图算法

    基本的图算法主要是两个方面 图的表示和图的搜索 我们主要通过邻接链表和邻接矩阵对图进行表示 但是在图算法更重要的是图的搜索 图的搜索指的是系统化的跟随图中的边来访问图中的每个节点 我们可以通过图的搜索算法发现图的结构 或者换个方面想图的算法