这两天学习了一下求强连通分量的另一个算法Tarjan算法,相比于之前的Kosaraju算法,Tarjan算法只需要进行一次深搜即可,两个算法的时间复杂度都是O(n+m);
算法基本思路是:从任一顶点开始进行深搜,强连通分量是一棵搜索子树,在搜索是将每一个未处理的点弹入栈中,然后在回溯时判断以该节点为根的搜索子树是否为一个强连通分量,若是,则将栈顶至该节点的元素弹出,这些元素即构成一个强连通分支,然后继续回溯,遇到强连通分量则弹出,最终便可获得有强连通分量。
算法的关键在于如何判断以u为根的搜索子树是否为一个强连通分量,算法用一个DFN[u]记录节点u在深搜时的次序,并用一个Low[u]记录u所能回溯到的栈中节点的次序号,判断是否为强连通分量只需判断DFN[u]是否等于Low[u],Low[u]<=DFN[u],如果Low[u] != DFN[u](即小于),则说明u可以通过其子节点连接到栈中更早的祖先,而该祖先又能达到u,所以形成一个环,所以该环应是强连通分支的一部分,所以u必然不是该强连通分支的根节点。
对于u进行搜索时,初始化Low[u] = DFN[u],然后对其子节点进行深搜并不断更新Low[u];以下分三种情况:
1.若子节点v未遍历过,则继续Tarjan(v)深搜过程,完成后更新Low[u] = min{low[u], low[v]}; 说明u通过其子节点v可以达到u的某一个栈中的祖先,此时Low[u] != DFN[u,]所以u必然不是强连通分支的根节点
2.若子节点v已遍历过且v在栈中,那么Low[u] = min{Low[u], DFN[v]};
3.若子节点v已遍历过且不在栈中,不作处理;
具体的过程课参考,博主说得很清晰:https://www.byvoid.com/blog/scc-tarjan/
以下是我C语言实现代码,用的是邻接矩阵(也可用其他实现):
#include <stdio.h> #include <cstring> const int MAXN = 1010; bool map[MAXN][MAXN]; int DFN[MAXN], Low[MAXN]; int Stack[MAXN], top, index; bool ins[MAXN], vis[MAXN]; int n; int min(int x, int y) { if (x < y) return x; return y; } void Tarjan(int u) { DFN[u] = Low[u] = index++; vis[u] = 1; //标记为遍历过 Stack[top++] = u; //将未处理的点弹入栈中 ins[u] = 1; //将该点标记为在栈中 //printf("%d\n", u); for (int i = 0; i < n; i++) { if (map[u][i] && !vis[i]) { //子节点未遍历过,进行tarjan,并更新u的Low Tarjan(i); Low[u] = min(Low[u], Low[i]); } else if (map[u][i] && ins[i]) //子节点遍历过且在栈中,更新Low Low[u] = min(Low[u], DFN[i]); } // printf("%d %d %d\n", u, DFN[u], Low[u]); if (DFN[u] == Low[u]) { //若相等,说明u为强连通分量的根节点,将u及u以上的元素弹出栈 while (Stack[top-1] != u) { printf("%d ", Stack[--top]); //将同一个强连通分量的元素在同一行输出 ins[Stack[top]] = 0; //标记为不在栈中 } printf("%d\n", Stack[--top]); ins[Stack[top]] = 0; } } int main() { int i, j, m, u, v; while (scanf("%d%d", &n, &m) != EOF) { memset(map, 0, sizeof(map)); memset(ins, 0, sizeof(ins)); memset(vis, 0, sizeof(vis)); for (i = 0; i < m; i++) { scanf("%d%d", &u, &v); map[u][v] = 1; } index = 0; top = 0; for (i = 0; i < n; i++) { if (!vis[i]) { Tarjan(i); } } } return 0; }
接下来是POJ1236,
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 9279 | Accepted: 3689 |
Description
You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school.
Input
Output
Sample Input
5 2 4 3 0 4 5 0 0 0 1 0
Sample Output
1 2
题目要求输出两个任务的结果,第一个任务是求最少需要给几个学校分批软件就可以让所有学校均获得,其实只要算出每个强连通分支,然后入度为0的强连通分支数即可;
第二个任务其实是给图加边过程,需要加上几条边便可使整个图强连通,只需计算入度为0的分支数cnt1和出度为0的分支数cnt2,输出max{cnt1, cnt2}; 但严格的证明我还是不太懂,就只是这么感觉(ORZ,希望知道的能指导一下,谢谢~~)
代码如下(用tarjan实现):
#include <stdio.h> #include <vector> #include <string.h> using namespace std; const int MAXN = 101; vector<int>vec[MAXN]; vector<int>pre[MAXN]; bool indre[MAXN], outdre[MAXN]; int DFN[MAXN], Low[MAXN], Stack[MAXN], instack[MAXN], belong[MAXN]; int cnt, n, top, index; int min(int x, int y) { if (x < y) return x; return y; } void Tarjan(int u) { DFN[u] = Low[u] = index++; Stack[top++] = u; instack[u] = 1; for (int j = 0; j < vec[u].size(); j++) { int v = vec[u][j]; if (!DFN[v]) { Tarjan(v); Low[u] = min(Low[u], Low[v]); } else if(instack[v]) Low[u] = min(Low[u], DFN[v]); } if (DFN[u] == Low[u]) { cnt++; do { belong[Stack[--top]] = cnt; instack[Stack[top]] = 0; }while(Stack[top] != u); } } int main() { int i, j, n, v; while (scanf("%d", &n) != EOF) { for (i = 1; i <= n; i++) { while (scanf("%d", &v) && v != 0) { vec[i].push_back(v); pre[v].push_back(i); } } memset(DFN, 0, sizeof (DFN)); memset(instack, 0, sizeof(instack)); top = 0; index = 1; cnt = 0; for (i = 1; i <= n; i++) { if (!DFN[i]) Tarjan(i); } memset(indre, 0, sizeof (indre)); memset(outdre, 0, sizeof(outdre)); for (i = 1; i <= n; i++) { for (j = 0; j < vec[i].size(); ++j) { v = vec[i][j]; if (belong[i] != belong[v]) { outdre[belong[i]] = 1; break; } } for (j = 0; j < pre[i].size(); ++j) { v = pre[i][j]; if (belong[i] != belong[v]) { indre[belong[i]] = 1; break; } } } int cnt1 = 0, cnt2 = 0; for (i = 1; i <= cnt; i++) { if (!outdre[i]) cnt1++; if (!indre[i]) cnt2++; } printf("%d\n", cnt2); if (cnt == 1) printf("0\n"); else printf("%d\n", cnt1>cnt2? cnt1 : cnt2); for (i = 0; i <= n; i++) { vec[i].clear(); pre[i].clear(); } } return 0; }
学算法之路任重而道远~~。