(图结构——广度优先搜索,深度优先搜索,学习资料为自清华大学邓俊辉老师的《数据结构》课程) 广度优先搜索(Breadth-First Search)
思路: 化繁为简,通过遍历可以将半线性结构的Tree转化为线性结构的序列sequence,因此可以类推将非线性结构的图Graph通过遍历转化为tree结构。
策略: 访问顶点s;依次访问s所有尚未访问的邻接顶点;并依次访问邻接顶点的所有尚未访问邻接顶点;(注意:同一等价类内部顶点之间的连接不会被采纳,只有相邻等价类之间的部分边被采纳)(是一个极大的无环图,又叫spanning tree,支撑树,相当于树的层次遍历)
template <typename Tv, typename Te> //顶点类型 边类型
void Graph<Tv,Te>::BFS(int v,int & clock){
Queue <int> Q; //构建队列,一般需要自己实现这种结构
status(v)=DISCOVERED; //将图的顶点赋值为已被发现
Q.enqueue(v); //借助队列结构实现图的访问,并进行初始化
while(!Q.empty){ //以队列是否为空为循环的判断条件
int v=Q.dequeue(); //访问队列的第一个节点
dTime(v)=++clock; //记录取出v时间
for(int u=firstNBr(v);-1<u;u=nextNbr(v,u))//考察v的每一个邻居
if(UNDISCOVERED ==status(u){ //u未被发现则发现该点
status(u)=DISCOVERED;
status(v,u)=TREE;parent(u)=v; //引入树边
}else
status(v,u)=cross; //在深度优先算法中要将DISCOVERED和VISITED节点进行区分,这里都统一视为跨边不采纳
status(v)=VISITED; //节点v访问完毕
}
}
改进: 并非每一幅图都只存在一个连通域,当存在多个连通域时上述算法可能不能完全遍历。
template <typename Tv,typename Te>
void Graph<Tv, Te>::bfs(int s){
reset();
int clock=0;
int v=s;
do //逐一检查所有顶点,若未被发现
if(UNDISCOVERED == status(v)
BFS(v, clock); //则启动一次BFS
while(s!=(v=(++v % n))); //按序号访问,不漏不重
}//
该算法最多对每个连通域的第一个顶点使用该算法,故时间复杂度不是很高。
BFS算法的时间复杂度分析: while和for两个循环;dequeue执行O(n)次;for循环需要执行的次数为O(n^2),但是for循环内层的O(n)在常系数意义下很小,因为连续,规则和紧凑的组织行式有利于高速缓冲机制发挥作用。
最短距离性:
树结构,任何一个节点对于顶点都有位移一条路径,该路径长度叫做该节点深度,因此按照深度大小可以对树进行层次划分,树的层次遍历就是按照这一指标的非降顺序遍历所有节点。
图结构,不同节点之间存在不同通路,则取其中最短的一条通路记为dist(v,s),当起始节点s相对固定时可以省略s,记为顶点v对应的距离。
深度优先搜索(Depth-First Search)
思路 访问顶点s;若s尚有未被访问的邻接节点,任选其一u,递归执行DFS(u),否则返回结束算法;若节点的邻接点已经被访问,则不会采用该边,并对该边进行标记。若节点的邻接点都已经被访问过,则回溯到该节点的上一节点。
实现
template <typename Tv, typename Te>
void Graph<Tv, Te>::DFS(int v, int & clock){
dTime(v) = ++clock;status(v) = DISCOVERED; //发现当前节点
for(int u=firstNbr(v); -1<u; u=nextNbr(v,u)) //枚举v的所有邻节点
switch( status(u)){ //根据u的状态对节点分别处理
case UNDISCOVERED: //节点u未被发现
status(v,u)=TREE; parent(u)=v; DFS(u,clock);break;//递归
case DISCOVERED: //被发现但未被访问完毕,应属被后代指向的祖先
status(v,u) = BACKWARD;break;
defult: //访问完毕(VISITED,有向图),则视承袭关系分为前向边或跨边
status(v,u) = dTime(v) < dTime(u)?FORWARD :CROSS;break;//父节点的开始访问时间早于子节点,用FORWARD,反之用CROSS
}
status(v)=VISITED; fTime = ++clock; //记录节点被访问结束的时间
}
改进: 同样对于深度优先算法也存在多连通域问题,可以类似的用while循环遍历节点,若存在未发现的节点,使用DFS遍历。
括号引礼(嵌套引理) 顶点的活动期:
active[u]=(dTime[u], fTime[u])
引理:在给定的有向图G=(V,E)及其任意一DFS森林中,则有
u是v的后代:iff active[u]包含于 active[v];
u是v的祖先:iff active[u]包含 active[v];
u与v无关:iff active[u]与 active[v]交集为空;
拓扑排序 任给有向图G(不一定是DAG有向无环图),尝试将所有顶点排列成一个线性序列,使其次序与原图相容(每一个顶点都不会通过边指向前驱顶点)
零入度排序算法: 任何有向无环图G,必须存在一个零入度顶点m,无画图肯定存在一个这样一个顶点m,将这个点取出,存入线性列表中,并将这个顶点和边略去,继续寻找零入度顶点。
零出度排序算法: 通过DFS寻找零出度的点,记下所有点BACKTRACK的顺序,逆序该序列即为排序结果。