在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
网上关于强联通分量的理论与分析十分的多,这里不再赘述。
强联通分量在循环子结构的识别中尤其的重要,对于一个自然循环(可规约),我们尚可通过迭代的方法找到循环中所有的块,但是对于不可规约流图,自然就不是这么简单的了。对于这种图,一种方法是寻找图的强联调分量。
Tarjan算法实现:
template<typename BBTy>
class SCCDiscovery{
public:
using scc_set = set<BBTy*>;
private:
unordered_map<BBTy*, unsigned> Dfn;
unordered_map<BBTy*, unsigned> LowLink;
vector<BBTy*> NodeStack;
unsigned DfnIndex = 1;
unordered_map<BBTy*, scc_set> SCCs;
// Tarjan
void findStrongComponent(BBTy* root) {
Dfn[root] = ++DfnIndex;
LowLink[root] = DfnIndex;
NodeStack.push_back(root);
for (auto i:llvm::make_range(succ_begin(root),succ_end(root))) {
// 未被遍历过,进行向后遍历
// 并在遍历完成后,更新当前节点的lowlink
if (Dfn[i] == 0) {
findStrongComponent(i);
LowLink[root] = min(LowLink[i], LowLink[root]);
}
// 如果已经遍历过了,并且dfs序小于当前节点,并且这个节点还在栈中
// 则进一步更新当前节点
else if (Dfn[i] < Dfn[root] &&
(find(NodeStack.begin(), NodeStack.end(), i) != NodeStack.end())) {
LowLink[root] = min(LowLink[root], Dfn[i]);
}
}
// 如果回边指向当前节点或者是单个节点
if (LowLink[root] == Dfn[root]) {
scc_set allNodeInSCC;
// 将所有属于当前连通分量的节点放入到容器中
while (NodeStack.size()) {
auto top = NodeStack.back();
if (Dfn[top] < Dfn[root]) {
SCCs[root] = allNodeInSCC;
return;
}
NodeStack.pop_back();
allNodeInSCC.insert(top);
}
// 整个CFG是一个大连通分量的情况
SCCs[root] = allNodeInSCC;
}
}
public:
void calculate(BBTy* entry) {
findStrongComponent(entry);
}
scc_set getSccByHeader(BBTy* bb) {
return SCCs[bb];
}
};
流图一般是单入口的,所以这里没有去处理不可达节点的情况。
参考资料:
- 高级编译器设计与实现。
(由于博主水平有限,如有讲述错误之处,请留言指正。)