强连通分量
算法性质
本算法执行后
形成的多个分别以某个节点为根的树,同一棵树中节点在G中相互可达
不同棵树中节点p,q无法在G中相互可达。
这样,同一颗树中的节点集合构成了G的一个强连通分量
算法运行后,一次求出了G中所有强连通分量。
接口设计
template<typename Key, typename Value>
class StrongConnectGraph
{
public:
typename typedef DataStruct::GraphStruct::Graph<Key, Value> InnerGraph;
StrongConnectGraph(const InnerGraph& nGraph_);
~StrongConnectGraph();
DataStruct::Array::DynArray<typename DepthFirstVisit<Key, Value>::Node*> Run();
private:
StrongConnectGraph(const StrongConnectGraph& nSCG_) = default;
StrongConnectGraph& operator=(const StrongConnectGraph& nSCG_) = default;
private:
const InnerGraph& m_nGraph;
InnerGraph* m_pReverseGraph;
typename DepthFirstVisit<Key, Value>* m_pDepthFirstVisit;
};
实现
构造
template<typename Key, typename Value>
StrongConnectGraph<Key, Value>::StrongConnectGraph(const InnerGraph& nGraph_)
: m_nGraph(nGraph_), m_pReverseGraph(nullptr), m_pDepthFirstVisit(nullptr)
{
}
析构
template<typename Key, typename Value>
StrongConnectGraph<Key, Value>::~StrongConnectGraph()
{
if (m_pReverseGraph != nullptr)
{
delete m_pReverseGraph;
m_pReverseGraph = nullptr;
}
if (m_pDepthFirstVisit == nullptr)
{
delete m_pDepthFirstVisit;
m_pDepthFirstVisit = nullptr;
}
}
算法运行
template<typename Key, typename Value>
DataStruct::Array::DynArray<typename DepthFirstVisit<Key, Value>::Node*> StrongConnectGraph<Key, Value>::Run()
{
DepthFirstVisit<Key, Value> _alDepthFirstVisit(m_nGraph);
DataStruct::Array::DynArray<typename DepthFirstVisit<Key, Value>::Node*> _arrNodes = _alDepthFirstVisit.Run();
_arrNodes.Sort([](typename DepthFirstVisit<Key, Value>::Node* pNode1_, typename DepthFirstVisit<Key, Value>::Node* pNode2_)->int
{
int _nRet = pNode1_->GetLastVisitTime() - pNode2_->GetLastVisitTime();
if (_nRet > 0)
{
return -1;
}
else if (_nRet < 0)
{
return 1;
}
else
{
return 0;
}
});
DataStruct::Array::DynArray<Key> _arrKeys;
for (int _i = 0; _i < _arrNodes.GetSize(); _i++)
{
typename InnerGraph::Pair _nPair = _arrNodes[_i]->GetGraphNode()->GetPair();
_arrKeys.Add(_nPair.m_nKey);
}
ReverseGraph<Key, Value> _alReverseGraph(m_nGraph);
if (m_pReverseGraph != nullptr)
{
delete m_pReverseGraph;
m_pReverseGraph = nullptr;
}
if (m_pDepthFirstVisit == nullptr)
{
delete m_pDepthFirstVisit;
m_pDepthFirstVisit = nullptr;
}
try
{
m_pReverseGraph = new InnerGraph(_alReverseGraph.Run());
m_pDepthFirstVisit = new DepthFirstVisit<Key, Value>(*m_pReverseGraph);
}
catch (...)
{
throw "out of memory";
m_pReverseGraph = nullptr;
m_pDepthFirstVisit = nullptr;
}
return m_pDepthFirstVisit->Run(_arrKeys);
}
算法目标&算法的性质证明
强连通分量算法:
1.对图G执行深度优先搜索,
2.对节点按结束时间递减排序,组成图中节点一个排列A
3.对图G的转置图G^t,按排列A中节点顺序,进行深度优先搜索
算法可能具备的性质&算法目标分析:
我们知道,若算法处理后,节点p的结束时间 > 节点q的结束时间
则,要么 p,q在主循环某个节点t的visit中被访问
此时,
a.1要么在访问链p->x1->...->xn->q中被访问
a.2要么在
t->x1->...->xk->y1->...->yn->q
t->x1->...->xk->z1->...->zm->p
其中,对xk可达节点按顺序执行访问时,先访问y1,后z1
中被访问
b.要么p,q分别在主循环某节点t1,t2的visit中被访问
且t1在t2之后执行
算法运行后形成的访问链上任意两节点在G中是相互可达的
通过主循环,对节点t执行visit
此时图中节点只有状态unvisit和visited
对t执行完visit后
将完成在访问前状态为unvisit节点集合中,所有对t可达节点的访问
对于访问前状态为unvisit节点集合中,任意对t可达节点的k访问
设访问后形成的任意一条路径为:
t->x1->x2->...->xn->k
分析:t,x1
首先
易于知道,t.m_nEndTime > x1.m_nEndTime
然后,易于知道G中存在,x1->t的边
因为t.m_nEndTime > x1.m_nEndTime
所以,
在G中
1.要么 t,x1在主循环某个节点x的visit中被访问
此时,
a.1要么在访问链t->y1->...->yn->x1中被访问
此时,知道t~x1
综合,此种情况下,G中t~x1,又可x1~t
可知两个节点在G中相互可达。
a.2要么在
x->y1->...->yk->a1->...->an->x1
x->y1->...->yk->b1->...->bm->t
其中,对yk可达节点按顺序执行访问时,先访问a1,后b1
中被访问
下面证明不可能是这种情况
采用反证法
如果是这种情况,
则,G深度搜索访问到x1时,由于t未被访问且可由x1到达,故接着会访问t,这与现实不符和。
故x1,t不会属于这种情况
b.要么x1,t分别在主循环某节点t1,t2的visit中被访问
且t2在t1之后执行
下面证明不可能是这种情况
采用反证法
如果是这种情况
则,G深度搜索访问到x1时,由于t未被访问且可由x1直接到达,故接着会访问t,这与现实不符合。
综合
在G中存在
t~x1,x1~t
分析t,x2
首先
易于知道,t.m_nEndTime > x2.m_nEndTime
然后,易于知道G中存在,x2->x1的边
由于t,x1在G中相互可达。
故,G中存在x2->x1~t
因为t.m_nEndTime > x2.m_nEndTime
所以,
在G中
1.要么 t,x2在主循环某个节点x的visit中被访问
此时,
a.1要么在访问链t->y1->...->yn->x2中被访问
此时,知道t~x2
综合,此种情况下,G中t~x2,又可x2~t
可知两个节点在G中相互可达。
a.2要么在
x->y1->...->yk->a1->...->an->x2
x->y1->...->yk->b1->...->bm->t
其中,对yk可达节点按顺序执行访问时,先访问a1,后b1
中被访问
下面证明不可能是这种情况
采用反证法
如果是这种情况,
则,G深度搜索访问到x2时,
已知G存在路径
x2->x1->t
若此时,
x1已经被访问,
由于此时t尚未被访问,
若x1已经被访问。
则在访问x1时候,t对于x1是其直接可达且未被访问节点,
故,t会随后被访问。
这与t尚未被访问冲突。故此时x1尚未被访问。
若此时,
x1尚未被访问,x2访问时,
由于x1是其直接可达且尚未被访问节点,故随后会访问x1。
对x1访问时,由于t是其直接可达且尚未被访问节点。
故随后会访问t。
这和a.2.上述访问链冲突。
在假设a.2上述访问链存在下,将产生解释上的冲突。
故,a.2.上述访问链不可能存在。
b.要么x2,t分别在主循环某节点t1,t2的visit中被访问
且t2在t1之后执行
下面证明不可能是这种情况
采用反证法
如果是这种情况,
则,G深度搜索访问到x2时,
已知G存在路径
x2->x1->t
若此时,
x1已经被访问,
由于此时t尚未被访问,
若x1已经被访问。
则在访问x1时候,t对于x1是其直接可达且未被访问节点,
故,t会随后被访问。
这与t尚未被访问冲突。故此时x1尚未被访问。
若此时,
x1尚未被访问,x2访问时,
由于x1是其直接可达且尚未被访问节点,故随后会访问x1。
对x1访问时,由于t是其直接可达且尚未被访问节点。
故随后会访问t。
这和b.上述访问链冲突。
在假设b.上述访问链存在下,将产生解释上的冲突。
故,b.上述访问链不可能存在。
综合
在G中存在
t~x2,x2~t
分析t,x3
首先
易于知道,t.m_nEndTime > x3.m_nEndTime
然后,易于知道G中存在,x3->x2的边
由于t,x2在G中相互可达。
故,G中存在x3->x2~t
因为t.m_nEndTime > x3.m_nEndTime
所以,
在G中
1.要么 t,x3在主循环某个节点x的visit中被访问
此时,
a.1要么在访问链t->y1->...->yn->x3中被访问
此时,知道t~x3
综合,此种情况下,G中t~x3,又可x3~t
可知两个节点在G中相互可达。
a.2要么在
x->y1->...->yk->a1->...->an->x3
x->y1->...->yk->b1->...->bm->t
其中,对yk可达节点按顺序执行访问时,先访问a1,后b1
中被访问
下面证明不可能是这种情况
采用反证法
如果是这种情况,
则,G深度搜索访问到x3时,
已知G存在路径
x3->x2->x1->t
若此时,
x1已经被访问,
由于此时t尚未被访问,
若x1已经被访问。
则在访问x1时候,t对于x1是其直接可达且未被访问节点,
故,t会随后被访问。
这与t尚未被访问冲突。故此时x1尚未被访问。
若此时,
x2已经被访问,
由于此时x1尚未被访问,
若x2已经被访问。
则在访问x2时候,x1对于x2是其直接可达且未被访问节点,
故,x1会随后被访问。
进一步,x1访问时,会访问t。
这与t尚未被访问冲突。故此时x2尚未被访问。
若此时,
x1,x2尚未被访问,x3访问时,
由于x2是其直接可达且尚未被访问节点,故随后会访问x2。
对x2访问时,由于x1是其直接可达且尚未被访问节点。故随后会访问x1。
对x1访问时,由于t是其直接可达且尚未被访问节点。故随后会访问t。
这和a.2.上述访问链冲突。
在假设a.2上述访问链存在下,将产生解释上的冲突。
故,a.2.上述访问链不可能存在。
b.要么x3,t分别在主循环某节点t1,t2的visit中被访问
且t2在t1之后执行
下面证明不可能是这种情况
采用反证法
可类似证明b访问链不可能存在
故,b.上述访问链不可能存在。
综合
在G中存在
t~x3,x3~t
分析t,x4
...
分析t,xn
...
分析t,k
...
综合
对算法处理后,形成的一条关系链,
关系链上任意两个节点在G中均是相互可达的。
下面接着证明
若G中存在,相互可达的节点p,q,则,在运行本算法后,p,q间必然存在对应的链将两者连接
证明:
G中存在p~q,q~p
则,G^T中也存在p~q,q~p
对G^T中执行深度访问时,
每次对节点t执行访问
会访问到所有G^T中状态为unvisit状态节点中所有从t可达的。
假设连接q~q的任一路径上首个被访问节点为k
则k是可以连接p,q的路径上首个被访问的节点
则此时p,q属于G^T中状态为unvisit状态节点中所有从k可达的。
所以会被k访问到。
所以,执行后p,q将率属于同一颗树。
算法性质总结
综合上述两个证明过程
可知,本算法执行后
形成的多个分别以某个节点为根的树,同一棵树中节点在G中相互可达
不同棵树中节点p,q无法在G中相互可达。
这样,同一颗树中的节点集合构成了G的一个强连通分量
算法运行后,一次求出了G中所有强连通分量。