众所周知常用的图遍历方式有深度优先遍历和广度优先遍历两种,那么我首先来看看这两种算法的具体实现,我们用G[Max][Max]表示图的邻接矩阵。
//三个全局变量
bool Visited[Max];//访问标志
void(*VisFunction)(int Vertex);//访问顶点
bool(*IsEdgeFuncion)(int G[][Max],intv,int u);//判断连边
深度优先递归版本:
void Depth_First(intG[][Max],void(*VisFunc)(int),bool(*IsEdgeFun)(int G[][Max],int, int),intBeginVer)
{
inti;
for(i=0;i<Max;i++)Visited[i]=false;
VisFunction=VisFunc;
IsEdgeFuncion=IsEdgeFun;
if(BeginVer<0||BeginVer>=Max)BeginVer=0;
DFS(G,BeginVer);
}
void DFS(int G[][Max],int BeginVer)
{
Visited[BeginVer]=true;
VisFunction(BeginVer);
for(inti=0;i<Max;i++)
if(IsEdgeFuncion(G,BeginVer,i)&&!Visited[i]){
DFS(G,i);
}
}
深度优先非递归版本:
void Depth_FirstVer(intG[][Max],void(*VisFunc)(int),bool(*IsEdgeFun)(int G[][Max],int, int),intBeginVer)
{
usingstd::stack;
stack<int>MyStack;
inti;
for(i=0;i<Max;i++)Visited[i]=false;
VisFunction=VisFunc;
IsEdgeFuncion=IsEdgeFun;
if(BeginVer<0||BeginVer>=Max)BeginVer=0;
MyStack.push(BeginVer);//Initialization
Visited[BeginVer]=true;
//++Number;//改进
VisFunction(BeginVer);
int Vertex;
while(!MyStack.empty()){
Vertex=MyStack.top();
int j=0;
for(;j<Max;j++){
if(IsEdgeFuncion(G,Vertex,j)&&!Visited[j]){
MyStack.push(j);
Visited[j]=true;
VisFunction(j);
//if(++Number==Max)return;//改进
break;
}
}
if(j>=Max)MyStack.pop();
}
}
深度优先的时间复杂度分析
我们用非递归版本来分析(比较清楚),我们知道在遍历过程中,每个顶点都要进一次栈且仅仅一次,n个顶点就会有n次进栈,现在我们在分析一下,每一次进栈后做什么呢?
当顶点u进栈后,就要以u为当前点继续遍历,也就说要寻找它的下一个邻接点v,时间为o(n),当沿着v这个分支遍历结束后又会回到u这点,找下一个没访问的邻接点v’(如果有的的话)。然后沿着v’分支继续遍历,因此对每次进栈可能需要o(cn)次寻找下一个元素。因此总的时间复杂度为0(n2)=c1n+c2n+…cnn;
从这个分析我们可以改进上面的算法,用一个变量(Number)来记录已经访问过的元素个数,一点Number等于顶点个数时就可以直接退出了,而不必返回去访问一些没用的分支了。
当图用邻接表储存时,那么当当顶点u进栈后,要寻找它的下一个邻接点v,时间为o(e1),其中e1为u的邻接边个数, 此总的时间复杂度为0(n+e)=n+e1+e2+…+en;其中e= e1+e2+…+en为无向图中边数,有向图弧数。O(n)是初始化访问标志等所耗时间。
广度优先:
void BFS(intG[][Max],void(*VisFunc)(int),bool(*IsEdgeFun)(int G[][Max],int, int),intBeginVer)
{
usingstd::queue;
queue<int>Myqueue;
inti;
for(i=0;i<Max;i++)Visited[i]=false;
VisFunction=VisFunc;
IsEdgeFuncion=IsEdgeFun;
if(BeginVer<0||BeginVer>=Max)BeginVer=0;
Myqueue.push(BeginVer);
Visited[BeginVer]=true;
VisFunction(BeginVer);
intVertex;
while(!Myqueue.empty()){
Vertex=Myqueue.front();
Myqueue.pop();
for(intj=0;j<Max;j++){
if(IsEdgeFuncion(G,Vertex,j)&&!Visited[j]){
Myqueue.push(j);
Visited[j]=true;
VisFunction(j);
}
}
}
}
广度优先时间复杂度分析:
在广度优先的遍历中每个顶点都要进(出)一次列队且仅仅一下(类似于深度优先遍历的进栈),对于每一个顶点u出列队后,要访问的所有邻接点,时间为o(n),因此我们可知广度优先遍历和深度优先遍历总的时间复杂度是一样的为o(n2)或o(n+e)
对比深度优先和广度优先
他们都能实现对图的遍历,且时间复杂度也是一样的。但对于同一个图他们的访问顺序是不一样的。这样的不一样可能会影响他们的具体应用。
从上面的算法(都看非递归本版)可以看出,深度优先遍历时使用的stack,stack的原则是先进后出,且每一次进栈后,马上跳出当前的for循环。以当前进栈元素为起点继续往下遍历,如果没能找到一个合适的元素(j>=Max)那么就将当前元素出栈,这样保证了能沿着分支回到父节点。
而在广度优先使用的是queue,queue的原则是先进先出,且每一出列队后,以出队元素为起点,遍历它所有的邻接点。