数据结构复习之图的遍历及生成树

软考相关总结:软考考点之图的遍历时间复杂度

图的遍历

从某个顶点出发,沿着某条搜索路径对图中每个顶点做且仅做一次访问。

深度优先搜索

思想

深度优先搜索(Depth First Search,DFS)遍历类似于树的前序(先根)遍历。从图G中任选一顶点V为初始出发点,首先访问出发点V,并将其标记为已访问过;然后依次从V出发搜索V的每个邻接点W,若W未曾访问过,则以w作为新的出发点出发,继续进行深度优先遍历,直到图中所有和V有路径相通的顶点都被访问到;若此时图中仍有顶点未被访问,则另选一个未曾访问的顶点作为起点,重复上述过程,直到图中所有顶点都被访问到为止。

邻接矩阵深度优先算法

int visited[20];

void DFS(MGraph G, int i, int n)
{  //从顶点Vi出发,深度优先搜索遍历图G(邻接矩阵结构)
    int j;
    printf("V%d→", i);       //假定访问顶点vi以输出该顶点的序号代之
    visited[i] = 1;          //标记vi已访问过
    for (j = 0; j < n; j++)  //依次搜索vi的每个邻接点
        if (G.arcs[i][j] == 1 && !visited[j])
            DFS(G, j, n);  //若(Vi,Vj)∈(G),且Vj未被访问过,则从开始递归调用
}

算法的时间复杂度为O(n2)

邻接表DFS算法

int visited[20];  //全局量数组,用以标记某个顶点是否被访问过

void DFSl(ALGraph G, int i)
{  //从顶点Vi出发,深度优先搜索遍历图G(邻接表结构)
    EdgeNode *p;
    int j;
    printf("V%d→", i);  //假定访问顶点vi以输出该顶点的序号代之
    visited[i] = 1;     //标记vi已访问过
    p = G[i].link;      //取Vi邻接表的表头指针
    while (p != NuLL)   //依次搜索vi的每个邻接点
    {
        j = p->adjvex;  // j为vi的一个邻接点序号
        if (!visited[j])
            DFSl(G, j);  //若(vi,vj)∈E(G),且vj未被访问过,则从开始递归调用
        p = p->next;     //使p指向vi的下一个邻接点
    }                    // End-while
}

该算法的时间复杂度为O(n+e)。

广度优先搜索遍历

思想

类似于树的按层次遍历。首先访问出发点Vi,接着依次访问Vi的所有未被访问过的邻接点Vi1,Vi2,…,Vit,并均标记为已访问过,然后再按照Vi1,Vi2,…,Vit的次序,访问每一个顶点的所有未曾访问过的顶点并均标记为已访问过,依次类推,直到图中所有和初始出发点Vi有路径相通的顶点都被访问过为止。

邻接矩阵BFS算法

int visited[20];

void BFS(MGraph G, int i, int n)
{                //从顶点Vi出发,广度优先搜索遍历图G(邻接矩阵结构)
    cirQueue Q;  //定义一个队列
    int k, j;
    InitQueue(&Q);           //初始化队列
    printf("v%d→", i);       //假定访问顶点vi用输出该顶点的序号代之
    visited[i] = 1;          //标记Vi已访问过
    EnQueue(&Q, i);          //将已访问的顶点序号i入队
    while (!QueueEmpty(&Q))  //当队列非空时,循环处理vi的每个邻接点
    {
        k = DeQueue(&Q);         //删除队头元素
        for (j = 0; j < n; j++)  //依次搜索Vk的每一个可能的
        {
            if (G.arcs[k][j] == 1 && !visited[j]) {
                printf("V%d→", j);  //访问未曾访问过的顶点vj
                visited[j] = 1;     //标记Vi已访问过
                EnQueue(&Q, j);     //顶点序号j入队
            }                       // End_if
        }                           // End_for
    }                               // End_while
}

该算法的时间复杂度为O(n2)

邻接表BFS算法

Void BFSl(ALGraph G, int i, int n)
{                //从顶点Vi出发,广度优先搜索遍历图G
    CirQueue Q;  //定义一个队列指针
    int j, k;
    InitQueue(&Q);  //初始化队列
    EdgeNode *p;
    int visited[20];
    printf("v%d→", i);       //假定访问顶点vi以输出该顶点的序号代之
    visited[i] = 1;          //标记vi已访问过
    EnQueue(&Q, i);          //将已访问的顶点序号i入队
    while (!QueueEmpty(&Q))  //循环处理vi的每个邻接点
    {
        k = DeQueue(&Q);   //删除队头元素
        p = G[k] .link;    //取vk邻接表的表头指针
        while (p != NULL)  //依次搜索vk的每一个可能的邻接点
        {
            j = p->adjvex;    // Vj为Vk的一个邻接点
            if (!visited[j])  //若vj未被访问过
            {
                printf("V%d→", j);  //访问未曾访问过的顶点vj
                visited[j] = 1;     //标记vj已访问过
                EnQueue(&Q, j);     //顶点序号j入队
            }                       // End-if
            p = p->next;            //使p指向Vk邻接表的下一个邻接点
        }                           // End_while
    }                               // End_while
}

算法的时间复杂度为O(n+e)。

图的应用

图的生成树

对于具有n个顶点的连通图,包含了该图的全部n个顶点,仅包含它的n-1条边的一个极小连通子图被称为生成树。一个图的生成树为一个无回路的连通图。一个连通图的生成树不一定是唯一的。

例子

从V0开始的深度优先搜索所得的生成树,图(c)是图(a)从V0开始的广度优先搜索的生成树。
从V0开始的深度优先搜索序列:V0,V1,V2,V5,V4,V6,V3,V7,V8。
从V0开始的广度优先搜索序列:V0,V1,V3,V4,V2,V6,V8,V5,V7。

在这里插入图片描述

最小生成树

对于连通的带权图(网)G,其生成树也是带权的。把生成树各边的权值总和称为该树的权,把权值最小的生成树称为图的最小生成树(Mininum Spanning Tree,MST)。

普里姆(Prim)算法

思想

从G(原始集合)中选择一个顶点仅在V中,而另一个顶点在U(生成树的集合)中,并且权值最小的边加入集合TE中,同时将该边仅在V中的那个顶点加入集合U中。重复上述过程n-1次,直到U=V,此时T为G的最小生成树。

实现

如下图所示:在这里插入图片描述
计算机内部实现过程:
在这里插入图片描述

邻接矩阵实现:

typedef int VRType;
typedef struct {
    ertexType Ver;//依附于哪条边
    VRType lowcost;//最小花费
} minedge[MaxVertexNum];  //从顶点集u到V-U的代价最小的边的辅助数组
void Prim(MGraph G, VertexType u, int n)
{  //采用邻接矩阵存储结构表示图
    int k, v, j;
    k = vtxNum(G, u);        //取顶点u在辅助数组中的下标
    for (v = 0; v < n; v++)  //辅助数组初始化
        if (v != k) {
            minedge[v].ver = u;
            minedge[v].lowcost = G.arcs[k][v];
        }
    minedge[k].lowcost = 0;  //初始,U={u}
    for (j = 1; j < n; j++)  //选择其余的n-1个顶点
    {
        k = min(minedge[j]);
        // 1≤j≤n-1,找一个满足条件的最小边(u,k),u∈u,k∈V-u
        printf(minedge[k].ver, G.vexs[k];
        //输出生成树的边
        minedge[k].lowcost = 0;  //第k个顶点并入u
        for (v = 0; v < n; v++)
            if (G.arcs[k][v] < minedge[v] .lowcost)
            //重新选择最小边
            {
                minedge[v].ver = G.vexs[k];
                mindege[v].lowcost = G.arcs[k][v];
            }
    }
}

普里姆算法的时间复杂度是O(n2)

克鲁斯卡尔(Krtskal)算法

思想

U的初值等于V,即包含有G中的全部顶点。T的初始状态是只含有n个顶点而无边的森林T=(V,φ)。
将图G中的边按权值从小到大的顺序依次选取E中的边(u,v),若选取的边使生成树T不形成回路,则把它并入TE中,保留作为T的一条边;若选取的边使生成树T形成回路,则将其舍弃,如此进行下去直到TE中包含n-1条边为止,此时的T即为最小生成树。

实现

Kruskal(G) {  //求连通网G的一棵MST
    T = (v, φ);
    //初始化T为只含有n个顶点而无边的森林
    //按权值升序对边集E中的边进行排序,
    //   结果存入E[0…e - 1] 中
    for (i = 0; i < e; i++)  // e为图G中边总数
    {
        //取第i条边(u, v);
        if (u和v分别属于两棵不同的树)
            then T = T ∪{(u, v)};
        if (T已经是一棵树)
            then return T;
    }
    return T;
}

克鲁斯卡尔算法的时间复杂度为O(eloge)。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guangod

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值