首页 > 其他分享 >数学建模-图论

数学建模-图论

时间:2023-09-10 23:23:20浏览次数:32  
标签:图论 遍历 递归 建模 DFS 访问 数学 low 节点

写在前面

在学习数据结构的图论时,有一类问题是很容易在现实生活中找到对应情况的问题,即最小路径问题,而对于其他的问题算法,如最小生成树等,我常常会困惑于其会应用于何种实际情况的求解,后面脱离了算法学习之后,这个问题算是搁置了下来。在接触到数学建模之后,我逐渐理解到为什么要在数学学科之外增设一个数学建模的课程,我逐渐明白之前对于算法应用的问题的产生背后是对算法工具认知的不足,当我对数学工具的认知能抽象到一定程度的时候,数学与应用的问题往往不攻自破,而这也是数学与数学建模之间的关系。

图与网络的发明是为了能够抽象的表示现实生活中的路线或者网络问题,但点与边的设计并不一定需要表示实体,在解决问题的过程中,一些规划问题或者可以对应到图论中的特征的问题都可以尝试用图论的知识来进行建模和求解,所以在图论建模上,抽象思维很重要。

图论与代数结构

图的基础概念在这里我们不多加叙述,这里我想谈一下图与树的关系。

树是一种特殊的图,它没有回路(即无环),而且有且只有一个根节点。因此,对于树的深度优先遍历(Depth First Search, 简称DFS)可以用递归实现。

在遍历的过程中,我们首先访问根节点,并将其标记为已访问。然后递归地访问所有子节点,直到遍历完整个子树;递归返回之后,再访问其其他未访问过的子节点。

这种实现方式非常简单易懂,易于理解和实现。通过递归调用函数,可以轻松地实现树的深度优先遍历,并且不需要使用复杂的数据结构来维护遍历过程中的状态,并且代码简洁易懂。而且,在树中,由于每个节点都只有一个父节点,因此不会存在重复访问的问题。因此,使用递归实现树的深度优先遍历是可行的。

但是需要注意,如果树的深度非常大(比如超过递归深度限制),或者树的度很大(即每个节点有很多个孩子),那么使用递归的方式实现遍历可能会导致栈溢出或者性能问题,建议使用非递归实现方式来进行树的遍历。

当使用递归遍历树的深度优先遍历时,系统会为每个递归函数调用创建一个栈帧(stack frame)。每个栈帧包含了函数的局部变量、参数以及返回地址等信息。

当树的深度很大或者树的度很大时,递归调用的次数也会很多,导致系统的栈空间被充满。如果此时还有更多的函数调用请求,就无法在栈空间中分配更多的空间了,从而导致栈溢出。

换句话说,如果树的深度比较大,递归的调用次数也会较多。而每一次递归调用都会占用一定的系统栈空间,当递归深度增大时,系统栈空间会被逐步占满,从而导致栈溢出。

为避免栈溢出问题,我们可以使用非递归的方式实现树的深度优先遍历。在非递归实现方式中,我们可以使用栈数据结构,手动模拟压栈和出栈的过程,而不是依赖系统的栈空间。这样,就可以避免递归过程中频繁的函数调用,从而减少栈空间的占用,避免栈溢出的问题。

也就是说,我们可以总结出一个结论:对图使用DFS,就可以得到它的强连通分量或者连通块。

如果对一个图使用深度优先搜索(DFS),将会遍历它的每一个连通块,并且会访问每个节点。在搜索的过程中,我们会从一个起点开始搜索,访问所有与其直接或间接相连的节点,将已经访问过的节点标记为已访问并添加到一个已访问的集合中。如果搜索到一个节点的所有邻居节点都被访问过,并且该节点没有其他未访问的邻居节点,那么该节点的搜索过程就结束了。

通过DFS,我们可以得到以下几个信息:

  1. 图中每个连通块的顶点集合;
  2. 以给定节点为起点的遍历顺序;
  3. 判定图是否为一棵树(无环连通图),如果不是则可以找到环(因为DFS遍历过程中会维护每一个节点的访问状态,如果在访问过程中某个节点的邻居节点已经被访问而且不是该节点的父节点,那么就说明存在环);
  4. 基于DFS的搜索算法(如迷宫问题和拓扑排序)。

基于此,我们可以一个简单的求图中的所有强连通分量的算法——Tarjan算法

Tarjan算法是一种常用于寻找有向图强连通分量的算法。算法的核心思想是用DFS遍历图,并在遍历过程中记录每个节点的追溯值(low-link值),判断是否存在强连通分量。追溯值是DFS的副产品,用于判断当前节点是否属于某个强连通分量。

在Tarjan算法中,对于图中的每个节点,都会分配一个唯一的追溯值,并在追溯过程中进行更新。具体而言,当我们访问一个节点时,Tarjan算法会先将该节点的追溯值和DFS序号设置为相同的值,并将其标记为访问过。然后,对于该节点的每一个未访问的邻居节点,我们会递归地调用它们的DFS过程,并在这个过程中更新邻居节点的追溯值。在返回时,如果邻居节点的追溯值小于当前节点的追溯值,那么我们将当前节点的追溯值更新为邻居节点的追溯值。

最终,当遍历完整个图之后,一个强连通分量中的所有节点的追溯值应该相同,也就是说,它们属于同一个强连通分量。如果一个节点的追溯值等于自己的DFS序号,那么说明该节点是一个强连通分量的根节点,它所在的连通分量中的所有节点的追溯值都等于自己的DFS序号。

因此,Tarjan算法的追溯值起到了判断当前节点是否属于某个强连通分量的作用。并且,Tarjan算法的追溯值可以非常高效和精确地计算出所有强连通分量的信息。

值得注意的是,追溯值只是一个标号,而不是一个具体计算出来的值,在程序设计的时候,我们通常使用搜索的时候根节点的标号作为追溯值。

给定一张 n 个点 m 条边的无向图,点的编号从 1到n ,可能存在重边和自环,不保证是一张连通图。现在,请你求出这张图所有的块。
以下是一个使用C++实现的求解无向图所有块的示例代码。该代码使用深度优先搜索(DFS)算法,采用邻接表来表示无向图,时间复杂度为O(n+m)。

```c++
#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 100010;
int n, m;
vector<int> g[MAXN];  // 邻接表表示无向图
int vis[MAXN], order[MAXN], low[MAXN];
int tot = 0, idx = 0;

void dfs(int u) {  // DFS搜索函数
    vis[u] = 1;
    order[u] = idx++;
    low[u] = order[u];
    for (int i = 0; i < g[u].size(); i++) {  // 枚举u的各个邻居节点
        int v = g[u][i];
        if (!vis[v]) {  // v尚未被访问过
            dfs(v);  // 递归搜索v
            low[u] = min(low[u], low[v]);
            if (low[v] >= order[u]) {  // u是块的起点
                printf("Block %d: ", ++tot);
                while (1) {
                    int w = stk.top();
                    stk.pop();
                    printf("%d ", w);
                    if (w == v) break;
                }
                printf("\n");
            }
        } else {  // v已被访问过
            low[u] = min(low[u], order[v]);
        }
    }
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 0; i < m; i++) {
        int u, v;
        scanf("%d %d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for (int i = 1; i <= n; i++) {  // 枚举所有节点
        if (!vis[i]) {  // 如果i尚未被访问过
            dfs(i);  // 从i开始搜索
            printf("Block %d: %d\n", ++tot, i);
        }
    }
    return 0;
}

该代码使用了以下数组和变量:

  • g:邻接表,用vector数组实现,其中g[i]表示与节点i相邻的所有节点。
  • vis:标记数组,标记每个节点是否被访问过。未被访问的节点标记为0,已被访问的节点标记为1。
  • order:记录DFS序,它表示每个节点第一次被访问的时间。在DFS的过程中,如果一个节点u第一次被搜索到,那么我们就将它自增,并把当前DFS序赋值给order[u]。
  • low:记录追溯值,以检测块。在DFS的过程中,如果一个节点u第一次被搜索到,那么我们将low[u]初始化为order[u]。当v为u的一个邻居,且v尚未被访问时,我们递归地搜索v,并更新low[u]为min(low[u], low[v])。如果v已被访问,我们更新low[u]为min(low[u], order[v])。如果low[v] >= order[u],则说明u的所有子孙节点都无法回到u的祖先节点,u是一个块的起点。
  • tot:记录块的数量。
  • idx:记录DFS顺序,初始化为0。

在这个示例代码中,我们通过邻接表来存储整个无向图。对于每个未被访问的节点,我们从它开始进行DFS搜索,并识别块的起点。最后,我们输出所有块的编号及包含的节点。这个算法的时间复杂度是O(n+m),其中n为节点数,m为边数,因为每条边和每个节点只会被遍历一次。

标签:图论,遍历,递归,建模,DFS,访问,数学,low,节点
From: https://www.cnblogs.com/xiaohoulaoyue/p/17692265.html

相关文章

  • 数学二考试大纲
    1、高等数学(1)函数、极限、连续函数的概念及表示法、函数的有界性、单调性、周期性和奇偶性、复合函数、反函数、分段函数和隐函数、基本初等函数的性质及其图形、初等函数函数关系的建立.数列极限与函数极限的定义及其性质、函数的左极限与右极限、无穷小量和无穷大量的概念......
  • 图论合集
    最短路算法全源最短路问题给出一张图\(G\),询问任意两点之间的最短路径。Floyd算法我们令\(f_{k,i,j}\)为只能经过\(1\simk\)的节点的最短路。那么初始化\(f_{0,i,j}\)为两点之间边权或者极大值。容易得到\(f_{k,i,j}=\min(f_{k,i,j},f_{k-1,i,x}+f_{k-1,x,j})\)......
  • 【高等数学】第五章 常微分方程
    1常微分方程的基本概念引入概念:求解过程:[1]根据题目可以写出以下关系式:[2]对导数式两端同时积分:[3]根据曲线过点(1,2)得:概念定义:【1】将方程中含有未知函数、未知函数的导数(或微分)和自变量的方程式叫做微分方程。【2】常微分方程:未知方程是一元函数。偏微分方程:未知函数是多元......
  • 2023年全国大学生数学建模竞赛赛题思路分析
    今年的数模难度和去年差不多,只是赛题的类型有所调整,粗略扫了一眼每个赛题,简单讲一下C题的思路吧。C题问题1:这道题其实考察的是最基础的数学知识,这道题可以拆解成两个小问。1.1求解蔬菜各品类及单品销售量的分布规律1)采用Excel等绘制品类销售量的直方图,利用Minitab等分析分布规律。......
  • 离散数学笔记——集合
    离散数学笔记——集合集合的概念集合是由一些确定的元素所组成的整体,其中的元素可以是任何事物定义:A={a1,a2,a3,...,an}表示集合的名称,{}表示集合的符号。a1,a2,a3,...an表示集合中的元素x∈A表示元素x属于集合A集合的特点集合没有重复元素集合......
  • 【高等数学】第四章 曲线积分与曲面积分
    1对弧长的曲线积分(第一类曲线积分)1.1对弧长的曲线积分的概念与性质定义实际意义可以理解为:性质:ds是有小弧段的长度Δs_i转化而来,是曲线弧L的弧微分。【1】【2】如果k为常数【3】若积分弧段L被分为L_1和L_2两段;即L=L_1+L_2,则有:【4】变换积分弧段L的起点和终点,对弧长的曲线积分的值......
  • 2023-09-05 图论专项训练(五)
    我TM但凡有点水平也不至于一点水平没有吧。——每日感想T1距离/P4162[SCOI2009]最长距离这道题本质上是一道十分弱智的搜索题,无论是开DFS还是开BFS还是开BDFS都能做。本人在这里不建议使用使用deque进行BFS,理由是运行速度比较慢,稍有不慎就见祖宗了。我在这里使用DFS,但是纯......
  • 【230908-15】求证南宋数学家秦九韶发现的求三角形面积的“三斜公式”并求值
    ......
  • 统一建模语言UML
    “统一建模语言UML”课程教学大纲UnifiedModellingLanguageCourseOutline32学时2学分一、本课程的性质、目的、任务本课程以介绍面向对象的统一建模语言UML为主,目的是了解面向对象技术的基本概念,掌握面向对象的分析和设计方法,以及与面向对象技术相关的一些软件开发技术,同......
  • 2数学建模
    数学建模是一种数学的思考方法,是运用数学的语言和方法,通过抽象和简化,建立能近似刻画并解决实际问题的模型的一种强有力的数学手段 模型准备:模型假设:迭代模型建立:模型求解:模型分析:模型检验:迭代模型应用:可能需要多轮的迭代 模型的合理性分析:最佳、适中、满意等模型的......