首页 > 编程语言 >C++的算法:割点与割边

C++的算法:割点与割边

时间:2024-06-13 15:31:44浏览次数:13  
标签:int 割点 C++ 割边 dfn low 节点

        在图论中,割点与割边是图的重要性质,它们在图的连通性、网络流等问题中扮演着关键角色。在C++中,我们可以通过深度优先搜索(DFS)等算法来判定一个图中的割点与割边。

        割点,又称关节点或桥接点,是指在无向连通图中,如果删除某个顶点后,图的连通分量数增加,则称该顶点为割点。

        判定割点的一个常用方法是Tarjan算法。该算法在深度优先搜索的过程中,维护了一个时间戳和一个低值(low)数组。时间戳记录了节点被访问的顺序,而low数组则记录了节点及其子树中能够回溯到的最早访问时间。

        在DFS过程中,对于当前节点u,我们遍历其所有邻接节点v。若v是u的一个未被访问过的邻接节点,我们继续递归访问v,并更新u的low值为min(low[u], low[v])。若v是u的一个已访问过的邻接节点,并且v不是u的父节点(避免回溯到父节点造成的错误更新),我们也更新u的low值为min(low[u], dfn[v]),其中dfn数组存储的是节点的访问时间戳。

        若对于节点u,存在至少一个邻接节点v,使得low[v] >= dfn[u],则说明从v及其子树中无法回溯到u的父节点或更早的节点,因此u是一个割点。

        示例为寻找割点,代码如下。

#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 1005; // 假设最大节点数为1000
vector<int> adj[MAXN]; // 邻接表存储图
int dfn[MAXN], low[MAXN], index; // 时间戳和最低时间戳
bool isArticulation[MAXN]; // 标记是否为割点

void dfs(int u, int parent) {
    dfn[u] = low[u] = ++index;
    int children = 0; // 统计子节点数量
    for (int v : adj[u]) {
        if (!dfn[v]) { // 如果v未被访问过
            children++;
            dfs(v, u);
            low[u] = min(low[u], low[v]);
            if (parent != -1 && low[v] >= dfn[u]) {
                isArticulation[u] = true; // u是割点
            }
        } else if (v != parent) { // 如果v已被访问过且不是u的父节点
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (parent == -1 && children > 1) { // 根节点且有多于一个子节点时也是割点
        isArticulation[u] = true;
    }
}

void findArticulationPoints(int root, int n) {
    index = 0;
    fill(dfn, dfn + n, 0);
    fill(low, low + n, 0);
    fill(isArticulation, isArticulation + n, false);
    dfs(root, -1); // 从根节点开始DFS
}

int main() {
    // 构建图
    int n, m; // n为节点数,m为边数
    cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u); // 无向图,需要添加双向边
    }

    // 查找割点
    findArticulationPoints(1, n); // 假设根节点为1
    cout << "Articulation points: ";
    for (int i = 1; i <= n; ++i) {
        if (isArticulation[i]) {
            cout << i << " ";
        }
    }
    cout << endl;

    return 0;
}

        割边,又称桥,是指在无向连通图中,如果删除某条边后,图的连通分量数增加,则称该边为割边。

        割边的判定也可以在深度优先搜索的过程中完成。当我们访问到一个新的节点v时,如果它的low值大于它的父节点u的dfn值,则说明从v及其子树中无法回溯到u的其他邻接节点,因此u和v之间的边就是一条割边。

        以下是割边判定的示例代码。

#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 1005;
vector<pair<int, int>> adj[MAXN]; // 邻接表存储图,记录边的信息
int dfn[MAXN], index; // 时间戳
bool isBridge[MAXN << 1]; // 标记是否为割边,数组大小为边的两倍,因为每条边需要两个位置的标记


void dfs(int u, int parentEdge) {
    dfn[u] = ++index;
    for (const auto& edge : adj[u]) {
        int v = edge.first, id = edge.second;
        int edgeToParent = (v == parentEdge / 2 + 1) ? parentEdge : -1; // 判断是否是回溯到父节点的边
        if (!dfn[v]) {
            dfs(v, edgeToParent);
            if (dfn[u] < dfn[v]) { // (u, v) 是割边
                isBridge[id] = isBridge[id ^ 1] = true;
            }
        }
    }
}
void findBridges() {
    index = 0;
    fill(dfn, dfn + MAXN, 0);
    fill(isBridge, isBridge + MAXN * 2, false);
    dfs(1, -1); // 从节点1开始DFS,-1表示没有父边
}

int main() {
    // 构建图的示例(与前面割点判定使用相同的图)
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back({v, i * 2}); // 记录边的信息,每条边在数组中有两个位置
        adj[v].push_back({u, i * 2 + 1});
    }

    findBridges(); 
    cout << "Bridges: ";
    for (int u = 1; u <= n; ++u) {
        for (int i = 0; i < m * 2; i += 2) {
            if (isBridge[i] || isBridge[i + 1]) { // 判断是否为割边
            cout << "(" << adj[adj[i / 2].first][i / 2].first << ", "
                 << adj[adj[i / 2].first][i / 2].second << ") ";
        }
        }
    }
    cout << endl;

    return 0;
}

        割点与割边的概念在许多图论算法和网络分析中具有重要应用。例如,在网络可靠性分析中,通过识别割点和割边,我们可以确定网络的脆弱部分,以便进行有针对性的加固。另外,在图的最小割和最大流问题中,也常常利用割边的性质来求解。

        在实际应用中,你可能需要根据具体的业务需求调整图的构建方式,例如使用邻接矩阵代替邻接表,或者处理有向图的情况。但是,无论图的表示方式如何变化,割点与割边的判定方法都是基于深度优先搜索的。

标签:int,割点,C++,割边,dfn,low,节点
From: https://blog.csdn.net/winterling/article/details/139601371

相关文章

  • C++基础知识总结
    一.c++的初始化intmian(){inta=10;intb(10);//用()来初始化intc{10};//用{}来初始化,较统一标准return0;}二.c++语言输入与输出#include<iostream>//输入输出流usingnamespacestd;intmain(){inta{0};charch{'0'};cin>>a>>ch;//提取符cout<<&......
  • c/c++ 创建windows 服务程序
    1项目介绍本次的项目是设计windows服务程序监听系统时间,对误差的时间进行修改,解决不连网下的本地时间的误差问题。2程序设计当程序直接运行时为创建该程序为windows服务程序,创建的windows服务程序设置为开机自启且运行带参数"-krunservice"以进行区别为创建服务还是运行程序......
  • c++定义了类在main函数中使用的一个坑现象的解决,让我理解了栈,堆和内存之间关系。
    首先描述一下我的坑是啥?我的坑就是写了一个对集料颗粒进行角度计算的类,在main函数中使用采用了类定义申明,这样使用导致一个坑,这个类中对于集料的数目进行了宏定义,发现数据如果超过20个,编译就报错,当时没有太在意这个坑,没有思考什么原因。也就将就者用了。后来对接同事说,这个颗粒数......
  • [C++ Primer] 字符串、向量和数组
    [C++Primer]字符串、向量和数组标准库类型string标准库类型string表示可变长的字符序列,使用该类型需包含string头文件。作为标准库的i一部分,string定义在命名空间std中。拷贝初始化:使用等号(=)初始化一个变量直接初始化:不使用等号strings5="hiya"; //拷贝初始化s......
  • [C++ Primer] 变量和基本类型
    [C++Primer]变量和基本类型变量默认初始化如果定义变量时没有指定初值,则变量默认初始化,此时变量被赋予“默认值”。默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响。内置类型:其默认值由定义的位置决定。定义于任何函数之外的变量被初始化为0。绝大多数......
  • 【C++面向对象】重载操作符
    C++将运算符重载扩展到自定义的数据类型,它可以让对象操作更美观。例如字符串string用加号(+)拼接、cout用两个左尖括号(<<)输出。运算符重载函数的语法:返回值operator运算符(参数列表);运算符重载函数的返回值类型要与运算符本身的含义一致。非成员函数版本的重载运算符函数:形......
  • 2024年华为OD机试真题-围棋的气-C++-OD统一考试(C卷D卷)
     2024年OD统一考试(D卷)完整题库:华为OD机试2024年最新题库(Python、JAVA、C++合集) 题目描述:围棋棋盘由纵横各19条线垂直相交组成,棋盘上一共19x19=361个交点,对弈双方一方执白棋,一方执黑棋,落子时只能将棋子置于交点上。“气”是围棋中很重要的一个概念,某个棋子有几口气,是指......
  • c++ 实现优先队列
    优先队列底层是堆,下面给出大根堆代码Push是在新节点到根节点的路径上寻找合适的插入位置;Pop是删除根节点之后维护大根堆,顺便为最后一个元素寻找合适的位置;1#include<bits/stdc++.h>23usingnamespacestd;45template<classT>6classp_queue{7pri......
  • c++在什么情况下会发生拷贝?
    在C++中,对象拷贝通常会在以下情况下发生:传递参数给函数:当你将对象作为参数传递给函数时,如果参数是按值传递的,那么会发生拷贝。例如:voidfunc(MyClassobj);//obj会被拷贝从函数返回对象:当函数返回一个对象时,如果函数返回的是对象本身而不是引用或指针,会发生拷贝。例......
  • C++中的流
    目录字节流(ByteStreams)字符流(CharacterStreams)主要区别在C++中,字节流和字符流是两种处理输入输出(I/O)的操作方式,它们都属于iostream库的一部分。它们的主要区别在于处理数据的基本单元和适用场景。字节流(ByteStreams)字节流以字节(byte)为基本处理单位,每个字节包含......