对于图的描述,已经把最基本的结构定义以及操作算法说的差不多了,其余的当做补充在后序中说吧。一个图,不管是有向的无向的,是基于什么结构存储的,这些差异都体现在存储上,对于上层的业务逻辑来说,都是一样的。所以现在来讨论一下有关图的遍历算法的实现。
图的遍历有深度优先算法,以及广度优先遍历算法。
我们作为教材学习的是着重于 顶点描述一个图,所以对图的遍历也是针对顶点的搜索与判断,查询等。之前学的树在遍历的时候有非常简单的三种不同的递归遍历算法,那是因为树本来是一种层次性的结构,树本身带有层序关系,所以实现起来不是那么的难,针对树的非递归遍历算法也是用队列去实现层遍历,用栈区实现三种递归遍历的。对于图来说,就没有那么简单了,因为图是一种毫无顺序的结构,然而计算机工作的最大的特点之一就是执行的必须是有序的指令才行。所以如何把图的上层逻辑无序性转换为底层存储的有序性呢?这是遍历算法要解决的最大的问题了。
最开始定义这个遍历算法的时候在想是用友元的形式呢还是用全局函数呢?最后发现图只是一种存储结构,针对在图上的各种操作算法是很多的,而图的遍历更多的是对图的某些操作,所以为了降低算法与图结构的耦合性,以便更好的去重用这个算法,选择了全局函数的方式来实现。
一:
先来看针对于图的深度优先(递归)算法:
#pragma once
#include"GraphAdjList.h"
#include<queue>
#include<stack>
using std::stack;
using std::queue;
//深度优先图遍历算法(递归)
template<class T, class E>
void DepthFirstSearch(GraphAdjList<T,E> &targetG, const T &begigVertex)
{
int begin_idx = targetG.GetVertexPos(begigVertex); //遍历起点,在这里都是通过索引值对应到顶点值的
int verticesNum = targetG.GetVerticesNum(); //获取图中的定点数目
bool *visited = new bool[verticesNum]; //在这里为了更好的判断经过的当前点是否被访问 用了一个标志数组,如果被访问置为true 反之false
for(int i = 0; i < verticesNum; i++)
{
visited[i] = false; //初始化 标志数组
}
Sub_DPS(targetG, begin_idx, visited); //用于递归的子函数
delete []visited; //最后释放标志数组空间
}
template<class T, class E>
void Sub_DPS(GraphAdjList<T,E> &targetG, int local_idx, bool * &visited)
{
//在这里本来想用传递函数指针的方式增加对图中顶点的可操作性,结果出了点问题,后来补上吧
cout<<targetG.GetVertexValue(local_idx)<<endl;
//opt(targetG.GetVertexValue(local_idx)); 只能先注释掉了
visited[local_idx] = true; //置当前顶点为true
T local_vtx = targetG.GetVertexValue(local_idx);
T firstAdj_vtx = targetG.GetFirstNeighbor(local_vtx);
int firstAdj_idx = targetG.GetVertexPos(firstAdj_vtx); //获取当前顶点的第一个邻接点的索引值
while(firstAdj_idx != targetG.GetNullIdx()) //如果这个当前(注意当前的意思 每一个递归层都会不一样)邻接点存在
{
if(visited[firstAdj_idx] == false) //递归到当前层后,判断是否顶点被访问过
{
Sub_DPS(targetG, firstAdj_idx, visited); //没有的话以此邻接点为起始继续往下递
}
//这是一个顶点值与索引值之间的转换,因为接口都是自定义类型的对象参数,所以不得不用转换
//这步很重要,是能够以此顶点往下一个邻接点发展的函数
firstAdj_idx = targetG.GetVertexPos(targetG.GetNextNeighbor(targetG.GetVertexValue(local_idx), targetG.GetVertexValue(firstAdj_idx)));
}
}
下面是VS2010编译器默认编译方式Debug情况下的结果:
下面再来看对于图的非递归深度优先遍历算法,这个就类似于之前的迷宫算法吧,就是不断的试探与回溯,其中涉及到进栈与出栈的时机问题,这点很重要,当时就是在这遇到坎了。
源码:
//深度优先遍历图(非递归)
template<class T, class E>
void DepthFirstSearchNoRecursion(GraphAdjList<T,E> &targetG, const T &beginVertex)
{
int verticesNum = targetG.GetVerticesNum(); //定点数目
bool *visited = new bool[verticesNum]; //标志数组
for(int i = 0; i < verticesNum; i++)
{
visited[i] = false; //初始化
}
int current_idx = targetG.GetVertexPos(beginVertex); //将遍历起点置为当前
stack<int> stk; //递归算法改用非递归一般是要用到栈,这里用到的是STL里的
stk.push(current_idx); //将这个当前入栈
while(!stk.empty()) //栈不为空,就说明有顶点未被访问
{
current_idx = stk.top(); //获取栈顶
stk.pop(); //弹出栈顶
//如果栈顶元素未被访问就访问掉,这里的访问是最简单的输出
if(visited[current_idx] == false)
{
cout<<"被访问: "<<targetG.GetVertexValue(current_idx)<<endl;
visited[current_idx] = true; //将其置为已访问状态
}
//--------------------------------------
//这个不同于迷宫算法的是,迷宫每一次都可以记录所走过的路径,但是图不行,一旦发生回退,接下来图是不知道接下来该往哪里走的
//所以下面的代码:找到一个可以继续往前进的路,找到的话flag = true 然后跳出循环
int currentAdj_idx = targetG.GetVertexPos(targetG.GetFirstNeighbor(targetG.GetVertexValue(current_idx)));
bool flag = false;
while(currentAdj_idx != targetG.GetNullIdx())
{
if(visited[currentAdj_idx] == false)
{
flag = true;
//为什么又把当前顶点进栈了呢?为了记录之前走过的路,便于回溯
//因为在访问的时候做了一个判断,不会一个顶点被访问两次
stk.push(current_idx);
break; //如果找到一个合适的出口就跳出循环
}
else
{ //这里是遍历当前顶点的边链表查找是否还有路
currentAdj_idx = targetG.GetVertexPos(targetG.GetNextNeighbor(targetG.GetVertexValue(current_idx),targetG.GetVertexValue(currentAdj_idx)));
}
}
//---------------------------------------
int temp_current_idx = currentAdj_idx; //这里又有两个索引值,和之前的注意区分
if(flag == true) //这里只有上面找到了可走的下一条路才会执行
{
bool flagOther; //这里又有一个标志,好吧我说不清楚了
do
{ //循环的目的是一直顺着一条可走的路走下去,直到无路可走。
//无路可走就是标志为false
flagOther = false;
int temp_Adj_idx = targetG.GetVertexPos(targetG.GetFirstNeighbor(targetG.GetVertexValue(temp_current_idx)));
//下面这段代码也是为了找到一个可走的路
while(temp_Adj_idx != targetG.GetNullIdx())
{
if(visited[temp_current_idx] == false)
{
stk.push(temp_current_idx);
flagOther = true;
break;
}
else
{
temp_Adj_idx = targetG.GetVertexPos(targetG.GetNextNeighbor(targetG.GetVertexValue(temp_current_idx),targetG.GetVertexValue(temp_Adj_idx)));
}
}
//这里有一个索引值转换,就是将邻接点置为当前,接下去走
temp_current_idx = temp_Adj_idx;
}while(flagOther == true);
}
}
}
以上代码写的时候确实是参考了走迷宫的思路,但是还是有一定的不同的
二:
下面是广度优先遍历图算法
就是一个队列的事
//广度优先遍历图的改造
template<class T, class E>
void BreadthFirstSearchOther(GraphAdjList<T,E> &targetG, const T &beginVertex)
{
int verticesNum = targetG.GetVerticesNum();
bool *visited = new bool[verticesNum];
for(int i = 0; i < verticesNum; i++)
{
visited[i] = false;
}
int local_idx = targetG.GetVertexPos(beginVertex);
queue<int> qu;
qu.push(local_idx);
while(!qu.empty())
{
local_idx = qu.front();
qu.pop();
cout<<"被访问: "<<targetG.GetVertexValue(local_idx)<<endl;
visited[local_idx] = true;
int localAdj_idx = targetG.GetVertexPos(targetG.GetFirstNeighbor(targetG.GetVertexValue(local_idx)));
while(localAdj_idx != targetG.GetNullIdx())
{
if(visited[localAdj_idx] == false)
{
qu.push(localAdj_idx);
}
localAdj_idx = targetG.GetVertexPos(targetG.GetNextNeighbor(targetG.GetVertexValue(local_idx),targetG.GetVertexValue(localAdj_idx)));
}
}
}