图的广度优先搜索
算法性质
通过对指定源点s运行广度优先搜索,
可以分辨出图中所有从s可达的节点集合,所有从s不可达的节点集合。
对于图中所有从s可达的节点p,将得到一条s~p的最短路径信息。
接口设计
template<typename Key, typename Value>
class BreadthFirstVisit
{
public:
class Node;
typename typedef DataStruct::GraphStruct::Graph<Key, Value> InnerGraph;
typename typedef InnerGraph::Node InnerGraphNode;
typename typedef InnerGraph::Pair InnerGraphPair;
typename typedef DataStruct::Tree::SortedBalanceBinaryTree<Key, Node*> InnerTree;
class Node
{
public:
Node* GetPreNode()
{
return m_pBfvPathNode;
}
typename DataStruct::GraphStruct::Graph<Key, Value>::Node* GetGraphNode()
{
return m_pGraphNode;
}
int GetDistance()
{
return m_nBfvPathDistance;
}
private:
Node()
{
m_nBfvPathDistance = -1;
m_pGraphNode = nullptr;
m_pBfvPathNode = nullptr;
}
Node(InnerGraphNode* pGraphNode_)
{
m_pGraphNode = pGraphNode_;
m_nBfvPathDistance = -1;
m_pBfvPathNode = nullptr;
}
~Node()
{
}
void Reset()
{
m_pBfvPathNode = nullptr;
m_nBfvPathDistance = -1;
}
private:
InnerGraphNode* m_pGraphNode;
Node* m_pBfvPathNode;
int m_nBfvPathDistance;
friend class BreadthFirstVisit;
};
BreadthFirstVisit(const InnerGraph& nGraph_);
~BreadthFirstVisit();
DataStruct::Array::DynArray<Node*> Run(const Key& nSourceKey_);
private:
BreadthFirstVisit(const BreadthFirstVisit& nBFV_) = default;
BreadthFirstVisit& operator=(const BreadthFirstVisit& nBFV_) = default;
private:
InnerGraph m_nGraph;
InnerTree m_tKeyToNodeMap;
};
实现
构造
template<typename Key, typename Value>
BreadthFirstVisit<Key, Value>::BreadthFirstVisit(const InnerGraph& nGraph_)
: m_nGraph(nGraph_)
{
DataStruct::Array::DynArray<InnerGraphNode*> _arrpGraphNodes = m_nGraph.GetNodesArray();
for (int _i = 0; _i < _arrpGraphNodes.GetSize(); _i++)
{
Node* _pNode = nullptr;
if (_arrpGraphNodes[_i] == nullptr)
{
throw "graph node exception";
}
try
{
_pNode = new Node(_arrpGraphNodes[_i]);
}
catch (...)
{
_pNode = nullptr;
throw "out of memory";
}
InnerTree::Pair _nPair;
InnerGraphPair _nGraphPair = _arrpGraphNodes[_i]->GetPair();
_nPair.m_nKey = _nGraphPair.m_nKey;
_nPair.m_nValue = _pNode;
m_tKeyToNodeMap.Add(_nPair);
}
}
析构
template<typename Key, typename Value>
BreadthFirstVisit<Key, Value>::~BreadthFirstVisit()
{
DataStruct::Array::DynArray<InnerTree::Pair> _arrpNodes = m_tKeyToNodeMap.GetArray();
int _nSize = _arrpNodes.GetSize();
for (int _i = 0; _i < _nSize; _i++)
{
delete _arrpNodes[_i].m_nValue;
_arrpNodes[_i].m_nValue = nullptr;
}
}
算法运行
template<typename Key, typename Value>
DataStruct::Array::DynArray<typename BreadthFirstVisit<Key, Value>::Node*> BreadthFirstVisit<Key, Value>::Run(const Key& nSourceKey_)
{
// 数据重置
DataStruct::Array::DynArray<InnerTree::Pair> _arrPairs = m_tKeyToNodeMap.GetArray();
for (int _i = 0; _i < _arrPairs.GetSize(); _i++)
{
if (_arrPairs[_i].m_nValue == nullptr)
{
throw "unexpected error";
}
_arrPairs[_i].m_nValue->Reset();
}
// 1.算法目标
// 2.正确性证明
// 3.效率分析
DataStruct::Queue::DynQueue<Node*> _qpNodes;
InnerTree::Node* _pSourceNode = m_tKeyToNodeMap.Search(nSourceKey_);
if (_pSourceNode == nullptr
|| _pSourceNode->GetPair().m_nValue == nullptr
|| _pSourceNode->GetPair().m_nValue->m_pGraphNode == nullptr)
{
throw "source node is not exist";
}
Node* _pNode = _pSourceNode->GetPair().m_nValue;
_pNode->m_nBfvPathDistance = 0;
_qpNodes.In(_pNode);
while (_qpNodes.IsEmpty() == false)
{
Node* _pNode = _qpNodes.Out();
DataStruct::Array::DynArray<Key> _arrDestinations = _pNode->m_pGraphNode->GetDests();
int _nSize = _arrDestinations.GetSize();
for (int _i = 0; _i < _nSize; _i++)
{
Node* _pRelatedNode = nullptr;
m_tKeyToNodeMap.Search(_arrDestinations[_i], _pRelatedNode);
if (_pRelatedNode == nullptr)
{
throw "unexpected error";
}
if (_pRelatedNode->m_nBfvPathDistance == -1)
{
_pRelatedNode->m_pBfvPathNode = _pNode;
_pRelatedNode->m_nBfvPathDistance = _pNode->m_nBfvPathDistance + 1;
_qpNodes.In(_pRelatedNode);
}
}
}
_arrPairs = m_tKeyToNodeMap.GetArray();
DataStruct::Array::DynArray<Node*> _arrpNodes;
for (int _i = 0; _i < _arrPairs.GetSize(); _i++)
{
_arrpNodes.Add(_arrPairs[_i].m_nValue);
}
return _arrpNodes;
}
正确性证明
循环不变式:
节点p进入队列时,
节点p是从源点s可达的,
源点s到节点p的最短路径信息【前一节点,距离】是已知的。
初始时,
源点s进入队列,
我们认为,源点s从源点s可达,
原点s到源点s的最短路径信息已知【前一节点为null,距离为0】
循环不变式成立。
第k次迭代,
依据循环不变式,
第1,...,k-1次迭代后循环不变式均成立
则,
迭代过程,从队列取出的节点q,由于q是之前放入队列的,
故依据循环不变式,
源点s到节点q的最短路径信息[前一节点,距离]是已知的
我们的迭代处理过程,
取出从q直接可达的节点p
若节点p的m_nDistance != -1 意味着,
节点p之前已经进入过队列,
故源点s到节点p的最短路径信息[前一节点,距离]是已知的,我们对其略过。
若节点p的m_nDistance == -1,
意味这,节点p之前未进入过队列,
此时,迭代过程设置p的m_nDistance和m_pPreNode,然后将p放入队列
为了证明,循环不变式在此迭代处理后,仍然满足,
我们需要证明
1.s~q-p是s~p的一条最短路径
采用反证法证明:
假设,s~q-p不是s~p的一条最短路径
由于此时我们知道p是从s可达的,
故s~p的最短路径是必然存在的。
设s~x-p为s~p的一条最短路径。
x!=q
此时我们知道,节点x在此刻尚未出过队列
因为如果节点x此前已经出过队列,
则在节点x出队列时候,
因为此时,节点p满足,
节点p通过节点x直接可达,节点p的m_nDistance为-1,
则按我们的迭代处理,
此时会设置p的m_nDistance,并将p入队列,
这和,后面处理到q时,p的m_nDistance为-1不符合。
故得证。
所以,节点x必然在节点q之后出队列,
由于队列的性质,
我们知道,节点x必然在节点q之后入的队列。
由于节点进入队列时,
其m_nDistance,是在先进入队列节点上单调增加得到的。
可知,
【先进入队列节点的m_nDistance,必然小于或等于后进入队列节点的m_nDistance。】
故q->m_nDistance <= x->m_nDistance
则,(s~x-p)->m_nDistance <= (s~q-p)->m_nDistance <= (s~x-p)->m_nDistance
(s~q-p)->m_nDistance = (s~x-p)->m_nDistance
故,(s~q-p)是s~p的一条最短路径。
综合,循环不变式成立。
依据上述循环不变式,可以证明,
对于进入队列节点,我们在进入时已经知道其最短路径信息【前一节点,距离】
证明:
对于任意从s可达节点p,节点p必然会进入队列。
反证法:
假设存在节点p,通过s可达,但节点p在迭代处理中没有进入过队列。
不妨设存在一条这样的路径s-p1-p2-....-pn-p
因为p不会进入队列,故迭代过程,节点p的m_nDistance始终为-1
可以推的,
pn永远不会进入队列, 因为pn如果进入队列,在pn出队列时,p将被加入队列。
同理,可以堆得p(n-1)....p2,p1,s不会进入队列。
与已知相违背。
假设不成立。得证。
综合,循环不变式和上述证明
得到,算法处理中,
所有s可达节点均被处理,且处理后得到s到其最短路径信息【前一节点,距离】
所有s不可达节点,不会进入队列,不会被处理。
证明:【先进入队列节点的m_nDistance,必然小于或等于后进入队列节点的m_nDistance。】
循环不变式:
任意时刻,
队列尾部节点距离,大于等于所有在其前进入过队列的节点的距离
初始化时,
队列只有一个源节点,满足循环不变式
对第k次迭代,
依据循环不变式,第1,...,k-1次迭代后,循环不变式均成立。
迭代中,新加入队列的任意节点q,本次迭代出队列节点为t1
若节点q为本次迭代,加入的第一个节点,
则加入时,
队列尾部节点p为上次迭代出队列节点t2在迭代中最后加入队列的节点。
q的距离 = t1的距离+1
p的距离 = t2的距离+1
可知,t2先于t1进入队列,
依据循环不变式有 t2的距离 <= t1的距离
可以得到,
q的距离 >= p的距离
若节点q为本次迭代,非加入的第一个节点,
则加入时,队列尾部节点p为本次迭代前面加入节点
q的距离 = t1的距离+1
p的距离 = t1的距离+1
可以得到,
q的距离 >= p的距离
故,迭代操作后,循环不变式仍然满足。
得证。