有关图的定义,与代码程序的定义在上个章节中:传送们,图的定义与遍历
依旧是这个图,我们可以看到图是有环的。
那代码中我们如何去判断是否有环呢?图的环跟点链表是不同的,不能采用“时钟法” 来判断.那我们用什么办法呢,其实很简单,原理就是DFS遍历思想,在遍历的时候记录visited ,每个节点是false,遍历到了该成true ,遍历到下个节点是true就表明是环,但是如上图,当遍历1 2 5 8 的时候都为true, 但是当遍历1 3 5的时候5是true了,但是 这个不是有向有环图,于是我们加入onStack 来记录从顶点到终点的值(是否为true)。比如我们遍历 1 2 5 8的时候,遍历完了,有回到1点的时候把onStack全变false。于是我们遍历 1 3 5遇到5了,发现5在visited 中是true,表示5刚才遍历过了,但是onStack中是false 表明不是当前遍历的,可以知道,图中不是环。于是采用这个方法就可以遍历出图是不是有环的了,有环的情况就是 visited 为 true, onStack同时也true。
有关DFS遍历是上一章的知识点,不知道的同学们请看前一章。
程序部分:
//判断是否有环
template<typename T>
bool Graph<T>::HasCycle()
{
bool *visited = new bool[n];
bool *onStack = new bool[n]; //由顶点索引的数组,以标记递归调用栈上的所有顶点
int *edgeTo = new int[n]; //edgeTo[i]存放指向i的边的点
ClearCycle();
for (int i = 0; i < n; ++i) {
visited[i] = false;
onStack[i] = false;
edgeTo[i] = -1;
}
for (int j = 0; j < n; ++j) {
if (!visited[j]) {
DFSForCycle(j, visited, onStack, edgeTo);
}
}
delete[] visited;
delete[] onStack;
delete[] edgeTo;
if (hasCycle) {
return true;
}
else {
return false;
}
}
ClearCycle()
//清空栈cycle中的记录
template<typename T>
void Graph<T>::ClearCycle()
{
while (!cycle.empty())
{
cycle.pop(); //有的话就去掉
}
}
DFSForCycle()
template<typename T>
void Graph<T>::DFSForCycle(int v, bool * visited, bool * onStack, int * edgeTo)
{
onStack[v] = true;
visited[v] = true;
for (ENode<T> *w = enodes[v]; w; w = w->next) {
if (hasCycle) {
return;
}
else if (!visited[w->adjVex]) {
edgeTo[w->adjVex] = v;
DFSForCycle(w->adjVex, visited, onStack, edgeTo);
}
else if (onStack[w->adjVex]) { //此顶点已经在递归调用的栈上,再次访问说明出现环了
//用栈cycle将环上的点都保存起来
for (int i = v; i != w->adjVex; i = edgeTo[i]) {
cycle.push(i);
}
cycle.push(w->adjVex);
cycle.push(v);
hasCycle = true;
}
}
onStack[v] = false; //消除在此递归调用栈上的记录,因为已经递归结束了
}
上面的代码是查找是否有环,并把他们环放到栈中,然后采用出栈的方式来获取环状的环数据。
//返回环
template<typename T>
stack<int> Graph<T>::GetCycle()
{
stack<int> tmp(cycle);
return tmp;
}
判断图中有没有环,其主要思想就是利用DFS遍历的思想,当下个节点是之前遍历过的数据,说明有环。