数据结构学习记录

136 阅读3分钟

(图结构——广度优先搜索,深度优先搜索,学习资料为自清华大学邓俊辉老师的《数据结构》课程) 广度优先搜索(Breadth-First Search)

思路: 化繁为简,通过遍历可以将半线性结构的Tree转化为线性结构的序列sequence,因此可以类推将非线性结构的图Graph通过遍历转化为tree结构。

策略: 访问顶点s;依次访问s所有尚未访问的邻接顶点;并依次访问邻接顶点的所有尚未访问邻接顶点;(注意:同一等价类内部顶点之间的连接不会被采纳,只有相邻等价类之间的部分边被采纳)(是一个极大的无环图,又叫spanning tree,支撑树,相当于树的层次遍历)

image.png

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)?FORWARDCROSS;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]交集为空;

image.png

拓扑排序 任给有向图G(不一定是DAG有向无环图),尝试将所有顶点排列成一个线性序列,使其次序与原图相容(每一个顶点都不会通过边指向前驱顶点)

零入度排序算法: 任何有向无环图G,必须存在一个零入度顶点m,无画图肯定存在一个这样一个顶点m,将这个点取出,存入线性列表中,并将这个顶点和边略去,继续寻找零入度顶点。

零出度排序算法: 通过DFS寻找零出度的点,记下所有点BACKTRACK的顺序,逆序该序列即为排序结果。