最近研究了一下图的深度优先遍历,尝试写了一下递归和非递归的算法,从而得到一个有向无环图的拓扑序列, 在教材书中,很多都是利用的递归方法来完成图的深度优先遍历,这里可以给大家介绍一下非递归的遍历实现。
对于有向图的深度优先遍历和拓扑序,需要明确下面几点:
1)只有有向无环图才可以生成拓扑序,如果有环,无法生成拓扑序
2)深度优先遍历可以检查出是否有环
3)深度优先遍历的非递归实现应该使用栈来实现。
4)拓扑序如果存在,可能不止一个,这里只要求出一个就可以了。
下面就简单利用C++来实现递归和非递归的基于深度优先的拓扑序。
- 前置条件: 不失一般性,我们可以用0…n-1 来表示n个顶点,其使用vector<int> verticalState来表示顶点的访问状态 (0, 表示未访问,1 表示正在访问,2 表示访问完成,实际的项目中我们更应该用枚举来实现)。可以利用map<int, vector> adjList 的map数据结构来表示邻接表,map的 key值代表的是顶点的id, value代表的是顶点的邻接表,这样我们就可以表示一张有向图了。
- 递归实现, 递归实现相对来说简单一点,代码如下:
// 生成拓扑序,如果没有,就生成空的Vector
// n 表示顶点个数,adjList 表示图的邻接表
vector<int> generateOrder (int n, map<int, vector<int>> &adjList)
{
//初始化顶点的状态
vector<VerticalState> verticalState (n, NoneVisit);
vector<int> empty; // 返回值当图为有环图的时候
vector<int> ans;
// 一个一个的顶点进行访问
for ( int i = 0; i < n; i++ ){
bool success = dfs (adjList, i, verticalState, ans);
if ( !success ){ return empty; } // 有环就直接返回空
}
// 处理哪些无边的顶点
for ( int i = 0; i < n; i++ ){
if ( std::find (ans.begin (), ans.end (), i) == ans.end () ){
ans.push_back (i);
}
}
return ans;
}
// 递归函数用于访问当前节点的深度优先遍历图
// 返回false如果有环,有环的唯一情况是:深度优先遍历时候,访问到的邻接点是正在访问状态
// 无环的访问true
// adjList 表示图的邻接表
// verticalId 表示当前的顶点id
// verticalState 表示顶点的访问状态
// order 表示顶点访问完成的顺序,用于生成拓扑序
bool dfs (map<int, vector<int>> &adjList, int verticalId, vector<VerticalState> &verticalState, vector<int>&order)
{
if ( verticalState[verticalId] == NoneVisit ){
bool success = true;
verticalState[verticalId] = Visiting; // is visiting
vector<int> afterVec = adjList[verticalId];
for ( int index = 0; index < afterVec.size (); index++ ){
if ( verticalState[afterVec[index]] == Visiting ) {
return false;
}
else if ( verticalState[afterVec[index]] == NoneVisit)
{
success = success && dfs (adjList, afterVec[index], verticalState, order);
if ( !success ) return false;
}
}
// 顶点访问完成,可以将其放入完成队列中
verticalState[verticalId] = Visited;
order.push_back (verticalId);
}
return true;
}
- 非递归实现: 和通常的树的访问一样,深度优先遍历的非递归实现需要用栈来实现。
enum VerticalState
{
NoneVisit,
Visiting,
Visited,
};
// 生成拓扑序,如果没有,就生成空的Vector
// n 表示顶点个数,adjList 表示图的邻接表
vector<int> generateOrder (int n, map<int, vector<int>> &adjList)
{
//初始化顶点的状态
vector<VerticalState> verticalState (n, NoneVisit);
vector<int> empty; // 返回值当图为有环图的时候
vector<int> ans;
// 一个一个的顶点进行访问
for ( int i = 0; i < n; i++ ){
***if ( verticalState[i] != NoneVisit ) { continue; } // 顶点已经访问过了,无需访问
stack<int> verticalStack;
verticalStack.push (i);
while ( !verticalStack.empty () ){
int verticalId = verticalStack.top ();
if (verticalState[verticalId] == Visiting ) //这是顶点弹出,应该是访问已经完成
{
verticalState[verticalId] = Visited; // 节点访问完成
verticalStack.pop ();
ans.push_back (verticalId);
continue;
}
verticalState[verticalId] = Visiting; // 设置顶点访问标志
bool finished = true; // 是否有未访问到的临接点,如果没有,表示该节点已经访问完成。
vector<int> afterVec = adjList[verticalId];
for ( int index = 0; index< afterVec.size (); index++ ){
// 有环,直接返回
if (verticalState[afterVec[index]] == Visited) {
return empty;
}
// 代表有临接点,压栈
else if (verticalState[afterVec[index]] == NoneVisit )
{
verticalStack.push (afterVec[index]);
finished = false;
}
}
if (finished ){
verticalState[verticalId] = Visited; // 顶点返回完成
verticalStack.pop ();
ans.push_back (verticalId);
}
}***
}
// 处理哪些无边的顶点
for ( int i = 0; i < n; i++ ){
if ( std::find (ans.begin (), ans.end (), i) == ans.end () ){
ans.push_back (i);
}
}
return ans;
}
上面的map可以换成hashmap以提高访问的效率。