C++的算法:Kosaraju算法与Tarjan算法

在图论中,强连通分量(Strongly Connected Components, SCC)是一个重要的概念。强连通分量是指在一个有向图中,一组顶点的任意两点之间都存在双向可达路径的顶点的集合。在实际应用中,如社交网络分析、网络流量分析等方面,强连通分量的计算具有广泛的应用。C++中,Kosaraju算法和Tarjan算法是两种常用的求解强连通分量的算法。

Kosaraju算法是一个基于深度优先搜索(DFS)的算法,其基本原理分为两步:

  1. 第一次DFS遍历:从任意节点开始进行深度优先搜索,并对遍历过的节点进行标记。搜索结束后,可以得到一个节点的逆后序序列。

  2. 第二次DFS遍历:根据逆后序序列,从后往前对每个节点进行深度优先搜索,并标记属于同一个强连通分量的节点。

示例:求出每个节点所属的强连通分量编号,代码如下。

cpp 复制代码
#include <iostream>
#include <vector>
#include <stack>
#include <cstring>
using namespace std;

vector<int> adj[100]; // 邻接表存储图
bool visited[100]; // 标记节点是否被访问过
int scc[100]; // 存储每个节点所属的强连通分量编号
int scc_count; // 强连通分量计数器
stack<int> stk; // 用于存储DFS遍历的节点

// 第一次DFS遍历,得到逆后序序列
void dfs1(int v) {
    visited[v] = true;
    for (int i = 0; i < adj[v].size(); i++) {
        int u = adj[v][i];
        if (!visited[u]) {
            dfs1(u);
        }
    }
    stk.push(v); // 将节点压入栈中,得到逆后序序列
}

// 第二次DFS遍历,标记强连通分量
void dfs2(int v, int id) {
    visited[v] = true;
    scc[v] = id; // 标记节点所属的强连通分量编号
    for (int i = 0; i < adj[v].size(); i++) {
        int u = adj[v][i];
        if (!visited[u]) {
            dfs2(u, id);
        }
    }
}

// Kosaraju算法主函数
void kosaraju() {
    memset(visited, false, sizeof(visited));
    for (int i = 0; i < 100; i++) { // 假设节点编号从0到99
        if (!visited[i]) {
            dfs1(i); // 第一次DFS遍历
        }
    }
    memset(visited, false, sizeof(visited));
    scc_count = 0;
    while (!stk.empty()) {
        int v = stk.top();
        stk.pop();
        if (!visited[v]) {
            dfs2(v, scc_count++); // 第二次DFS遍历,并更新强连通分量计数器
        }
    }
}

int main() {
    // 构建图(以邻接表形式)
    adj[0].push_back(1);
    adj[1].push_back(2);
    adj[2].push_back(0);
    adj[2].push_back(3);
    adj[3].push_back(3);

    kosaraju(); // 调用Kosaraju算法

    // 输出每个节点所属的强连通分量编号
    for (int i = 0; i < 4; i++) {
        cout << "Node " << i << " belongs to SCC " << scc[i] << endl;
    }

    return 0;
}

Tarjan算法是一种基于深度优先搜索和栈的算法,通过维护一个栈和一个访问时间戳数组来实现强连通分量的求解。其基本步骤如下:

  1. 初始化所有节点的访问状态为未访问,时间戳为0,最低访问时间戳为无穷大。

  2. 对每个未访问的节点进行深度优先搜索,同时更新访问状态和时间戳。

  3. 如果一个节点的后继节点已经在栈中且其最低访问时间戳小于当前节点的访问时间戳,则找到了一个强连通分量。

  4. 通过不断回溯和弹出栈中元素,直到不再满足上述条件为止,从而得到一个强连通分量。

  5. 重复上述步骤,直到所有节点都被访问过。

示例:求出每个强连通分量,代码如下。

cpp 复制代码
#include <iostream>
#include <vector>
#include <stack>
#include <cstring>
using namespace std;

vector<int> adj[100]; // 邻接表存储图
int index; // 时间戳计数器
int low[100]; // 节点能够回溯到的最早时间戳
bool instack[100]; // 节点是否在栈中
int scc_count; // 强连通分量计数器
stack<int> stk; // 用于DFS遍历的栈
vector<int> scc_list[100]; // 存储每个强连通分量的节点

void tarjan(int v) {
    int i;
    low[v] = index++;
    stk.push(v);
    instack[v] = true;
    for (i = 0; i < adj[v].size(); i++) {
        int u = adj[v][i];
        if (low[u] == -1) { // 如果u未访问过
            tarjan(u);
            if (low[u] < low[v]) {
                low[v] = low[u];
            }
        } else if (instack[u]) { // 如果u在栈中
            if (low[u] < low[v]) {
                low[v] = low[u];
            }
        }
    }
    if (low[v] == index - 1) { // 发现一个强连通分量
        int j;
        do {
            j = stk.top();
            stk.pop();
            instack[j] = false;
            scc_list[scc_count].push_back(j);
        } while (j != v);
        scc_count++;
    }
}

void tarjan_algorithm() {
    memset(low, -1, sizeof(low));
    memset(instack, false, sizeof(instack));
    index = 0;
    scc_count = 0;
    for (int i = 0; i < 100; i++) { // 假设节点编号从0到99
        if (low[i] == -1) {
            tarjan(i); // 对每个未访问的节点进行Tarjan算法
        }
    }
}

int main() {
    // 构建图(以邻接表形式)
    adj[0].push_back(1);
    adj[1].push_back(2);
    adj[2].push_back(0);
    adj[2].push_back(3);
    adj[3].push_back(3);

    tarjan_algorithm(); // 调用Tarjan算法

    // 输出每个强连通分量
    for (int i = 0; i < scc_count; i++) {
        cout << "SCC " << i << ": ";
        for (int j = 0; j < scc_list[i].size(); j++) {
            cout << scc_list[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

Kosaraju算法和Tarjan算法都是用于求解有向图强连通分量的经典算法。Kosaraju算法通过两次深度优先搜索来求解,而Tarjan算法则利用时间戳和栈来实现更高效的求解。在实际应用中,可以根据图的特点和具体需求选择合适的算法。

相关推荐
AI街潜水的八角7 分钟前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
q5673152324 分钟前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
白榆maple32 分钟前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少37 分钟前
数据结构——线性表与链表
数据结构·c++·算法
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar1 小时前
yelp数据集上识别潜在的热门商家
开发语言·python
此生只爱蛋2 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
blammmp2 小时前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧2 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++