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

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

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

        1. 第一次DFS遍历:从任意节点开始进行深度优先搜索,并对遍历过的节点进行标记。搜索结束后,可以得到一个节点的逆后序序列。
        2. 第二次DFS遍历:根据逆后序序列,从后往前对每个节点进行深度优先搜索,并标记属于同一个强连通分量的节点。

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

#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. 重复上述步骤,直到所有节点都被访问过。

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

#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算法则利用时间戳和栈来实现更高效的求解。在实际应用中,可以根据图的特点和具体需求选择合适的算法。

### 回答1: 我可以用C给你写一个Tarjan算法,它是一种用于图算法的深度优先搜索(DFS)算法,可以用来检测图中的强连通分量(SCC),并找出给定图的最小桥(给定两个节点之间的最小边)。具体的实现可以参考下面的代码:// Tarjan算法 // 参数: // g: 图 // v: 从图中获取节点 // adj: 从节点获取邻接顶点 // t: 时间戳 // scc: 强连通分量 // stack: 栈 int tarjan(Graph &g, int v, std::vector<std::vector<int>> &adj, int t, std::vector<std::vector<int>> &scc, std::vector<int> &stack) { // 设置节点v的时间戳 g.timestamp[v] = t; // 设置节点v的最小时间戳 g.min_timestamp[v] = t; // 将节点v加入栈中 stack.push_back(v); // 对节点v的每一个邻接顶点w进行搜索 for (int w : adj[v]) { if (g.timestamp[w] == -1) { // 如果顶点w的时间戳未被设置,则递归搜索 tarjan(g, w, adj, t+1, scc, stack); // 设置节点v的最小时间戳 g.min_timestamp[v] = std::min(g.min_timestamp[v], g.min_timestamp[w]); } else if (std::find(stack.begin(), stack.end(), w) != stack.end()) { // 如果顶点w已经在栈中,则设置节点v的最小时间戳 g.min_timestamp[v] = std::min(g.min_timestamp[v], g.timestamp[w]); } } // 如果节点v的最小时间戳等于其时间戳,说明搜索结束 if (g.min_timestamp[v] == g.timestamp[v]) { // 将节点v以及其之前的节点加入一个强连通分量 std::vector<int> component; int w; do { w = stack.back(); stack.pop_back(); component.push_back(w); } while (w != v); scc.push_back(component); } return 0; } ### 回答2: Tarjan算法是一种用于在有向图中查找强连通分量(Strongly Connected Component,简称SCC)的算法。以下是用C语言实现的Tarjan算法的代码: ```c #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX 100 typedef struct Node { int data; struct Node* next; } Node; typedef struct Stack { int top; int array[MAX_VERTEX]; } Stack; Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; newNode->next = NULL; return newNode; } void addEdge(Node* graph[], int src, int dest) { Node* newNode = createNode(dest); newNode->next = graph[src]; graph[src] = newNode; } void DFS(int u, int disc[], int low[], int stackMember[], Stack* stack, Node* graph[], int* count) { static int time = 0; disc[u] = low[u] = ++time; stack->array[++stack->top] = u; stackMember[u] = 1; Node* i; for (i = graph[u]; i != NULL; i = i->next) { int v = i->data; if (disc[v] == -1) { DFS(v, disc, low, stackMember, stack, graph, count); low[u] = (low[u] < low[v]) ? low[u] : low[v]; } else if (stackMember[v] == 1) { low[u] = (low[u] < disc[v]) ? low[u] : disc[v]; } } int popVertex = 0; if (low[u] == disc[u]) { while (stack->array[stack->top] != u) { popVertex = stack->array[stack->top]; printf("%d ", popVertex); stackMember[popVertex] = 0; stack->top--; (*count)++; } popVertex = stack->array[stack->top--]; printf("%d\n", popVertex); stackMember[popVertex] = 0; (*count)++; } } void tarjanSCC(int vertices, Node* graph[]) { int disc[MAX_VERTEX], low[MAX_VERTEX], stackMember[MAX_VERTEX], count = 0; Stack* stack = (Stack*)malloc(sizeof(Stack)); stack->top = -1; for (int i = 0; i < vertices; i++) { disc[i] = -1; low[i] = -1; stackMember[i] = 0; } for (int i = 0; i < vertices; i++) { if (disc[i] == -1) { DFS(i, disc, low, stackMember, stack, graph, &count); } } free(stack); } int main() { int vertices = 8; Node* graph[MAX_VERTEX]; for (int i = 0; i < MAX_VERTEX; i++) { graph[i] = NULL; } addEdge(graph, 0, 1); addEdge(graph, 1, 2); addEdge(graph, 2, 0); addEdge(graph, 2, 3); addEdge(graph, 3, 4); addEdge(graph, 4, 5); addEdge(graph, 5, 3); addEdge(graph, 5, 6); addEdge(graph, 6, 7); printf("Strongly Connected Components:\n"); tarjanSCC(vertices, graph); return 0; } ``` 以上是一个使用C语言编写的Tarjan算法的示例代码。该代码首先定义了节点和栈的数据结构,然后实现了创建节点和添加边的函数。在Tarjan算法函数中,使用DFS进行遍历,并将每个节点的发现时间和最低发现时间进行更新,从而找到强连通分量。最后,打印出所有的强连通分量。 注意:以上代码仅为示例,实际使用中可能需要根据具体情况进行调整和优化。 ### 回答3: Tarjan算法是一种常见的图算法,用于查找图中的强连通分量。以下是一个用C语言写的Tarjan算法的示例: ```c #include <stdio.h> #include <stdlib.h> #define MAX_NODES 100 // 图的最大节点数 int index = 0; // 全局索引计数 int lowlink[MAX_NODES]; // 每个节点在遍历过程中的最低链接值 int visited[MAX_NODES]; // 每个节点是否已被访问 int stack[MAX_NODES]; // 记录遍历过程中访问的节点 int onStack[MAX_NODES]; // 每个节点是否在栈中 int stackIndex = 0; // 栈索引计数 int min(int a, int b) { return a < b ? a : b; } void Tarjan(int node, int graph[MAX_NODES][MAX_NODES]) { visited[node] = 1; // 标记节点已访问 lowlink[node] = index; index++; stack[stackIndex] = node; onStack[node] = 1; // 将节点入栈 stackIndex++; for (int i = 0; i < MAX_NODES; i++) { if (graph[node][i] == 1) { // 有连边到另一节点 if (!visited[i]) { // 未访问过的节点 Tarjan(i, graph); // 递归遍历 lowlink[node] = min(lowlink[node], lowlink[i]); } else if (onStack[i]) { // 已在栈中的节点 lowlink[node] = min(lowlink[node], lowlink[i]); } } } if (lowlink[node] == index - 1) { // 当前节点是强连通分量的根节点 printf("Strongly Connected Component: "); int w; do { w = stack[--stackIndex]; printf("%d ", w); onStack[w] = 0; // 将节点出栈 } while (w != node); printf("\n"); } } int main() { int graph[MAX_NODES][MAX_NODES] = { {0, 1, 0, 0, 0}, {0, 0, 1, 0, 0}, {1, 0, 0, 1, 1}, {0, 0, 0, 0, 0}, {0, 0, 0, 1, 0} }; for (int i = 0; i < MAX_NODES; i++) { visited[i] = 0; onStack[i] = 0; } for (int i = 0; i < MAX_NODES; i++) { if (!visited[i]) { Tarjan(i, graph); } } return 0; } ``` 此示例中,我们定义了一个`Tarjan`函数来进行强连通分量的查找。我们通过输入一个图的邻接矩阵来调用此函数。在`Tarjan`函数中,我们使用一个全局索引计数来记录每个节点在遍历过程中的访问次序,使用`lowlink`数组来记录每个节点在遍历过程中的最低链接值。我们还使用了一个栈来辅助实现遍历过程。在函数中,我们首先标记节点为已访问,并且将其加入栈中,然后遍历与其相邻的节点,继续递归地遍历未访问过的节点。对于已访问的节点,我们通过更新`lowlink`数组来找到其最低链接值。当某个节点的`lowlink`值等于其在遍历过程中的索引值时,该节点以及所有其之前入栈的节点构成一个强连通分量。我们将这些节点出栈,输出强连通分量的内容。最后,在`main`函数中,我们创建一个示例图的邻接矩阵,并调用`Tarjan`函数来查找强连通分量。 请注意,这只是一个简单的示例,并且仅适用于使用邻接矩阵表示的有向图。实际应用中,可能需要根据具体情况进行修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈工程师Linda

感恩您的鼓励,我会继续创作作品

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值