数据结构第六章图部分知识点

《数据结构》知识点汇总+算法代码总结【全】 写在前面:本文写于吴签时期,在家备考时刷完数据结构王道书之后想着把书中重点梳理汇总一下。本文内容包括但不局限于王道数据结构每章的知识点及其课后习题所涵盖的知识点。本人曾在大三期间打过一些程序设计类比赛,所以本文所涉及到的代码不一定局限于王道书,但思想都一样。 期末复习和备考408均可使用第一章:绪论(不在考研大纲但很重要)第二章:线性表第三章:栈和队列第四章:串第五章:树与二叉树第六章:图第七章:查找 第一章:绪论(不在考研大纲但很重要) 数据结构三要素:逻辑结构、存储结构、数据的运算;其中逻辑结构包括线 阅读详情

图的存储结构

后面测试用例示例

邻接矩阵法——空间复杂度很高,顺式存储

// 邻接矩阵法
#define MaxVertexNum 100
typedef struct{
    char vex[MaxVertexNum];//顶点表
    int edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边数
    int vexnum,arcnum;//图的当前顶点数和边数/弧数
}MGraph;
  • 无权图
    有对应的边则数组的值为1,没有对应的边数组值为0。
    对于无向图,二维数组一定是一对称矩阵,因为两点顶点相邻,无方向,当求其结点的度的时候只需要遍历数组的一行或者一列看有多少个1便可以了,时间复杂度为O(n)。
    对于有向图,二维数组不一定是对称矩阵,因为边是有方向的。如果要求有向图结点的入度,需要遍历结点对应的列,如果要求结点的出度,需要遍历结点对应的行,结点的度=入度+出度,时间复杂度为O(n)。

  • 带权图
    对应边的数组值为权值,没有对应边的数组值为0或者∞

  • 性能分析
    空间复杂度:存储n个顶点:O(n);存储其对应的边:O(n^2)
    适合存储稠密图,有向图可以采用对称矩阵压缩存储的方法进一步压缩矩阵

  • 邻接矩阵法的性质——离散数学内容
    得到的结果表示各个结点长度为n的路径信息

在这里插入图片描述

邻接矩阵的创建
  • 直接按照前面给出的实例写死的一个矩阵,无实际意义,仅仅是为了后面写代码测试方便
// 初始化邻接矩阵
void initMGraph(MGraph *mGraph){
    (*mGraph).vexnum=0;
    (*mGraph).arcnum=0;
}
// 不严谨的邻接矩阵法定义一个无向图
void createMGraph1(MGraph *mGraph){
    (*mGraph).vexnum=5;
    (*mGraph).arcnum=6;
    for(int i=0;i<(*mGraph).vexnum;i++){
        (*mGraph).vex[i]=65+i;//输入的是字母的ASCII码,会转化的
    }
    //对称矩阵
    // 结构体定义之后再对数组进行初始化,是不能用{}进行赋值的。下面注释是会报错的代码
    // (*mGraph).edge[vexnum][vexnum]={{0,1,1,1,1},{1,0,1,0,0},{1,1,0,1,0},{1,0,1,0,0},{1,0,0,0,0}};
    int temp[5][5]={{0,1,1,1,1},{1,0,1,0,0},{1,1,0,1,0},{1,0,1,0,0},{1,0,0,0,0}};
    for(int i=0;i<(*mGraph).vexnum;i++){
        for(int j=0;j<(*mGraph).vexnum;j++){
            (*mGraph).edge[i][j]=temp[i][j];
        }
    }
}
// 创建一个有向图的邻接矩阵
void createMGraph2(MGraph *mGraph){
    (*mGraph).vexnum=5;
    (*mGraph).arcnum=6;
    for(int i=0;i<(*mGraph).vexnum;i++){
        (*mGraph).vex[i]=65+i;//输入的是字母的ASCII码,会转化的
    }
    int temp[5][5]={{0,1,1,1,1},{0,0,1,0,0},{0,0,0,1,0}};
    for(int i=0;i<(*mGraph).vexnum;i++){
        for(int j=0;j<(*mGraph).vexnum;j++){
            (*mGraph).edge[i][j]=temp[i][j];
        }
    }
    
}
// 打印邻接矩阵定义的图
void printfMGraph(MGraph mGraph){
    printf("邻接矩阵的顶点集……\n");
    for(int i=0;i<mGraph.vexnum;i++){
        printf("[%d]-[%c]\n",i,mGraph.vex[i]);
    }
    printf("邻接矩阵的边集……\n");
    for(int i=0;i<mGraph.vexnum;i++){
        for(int j=0;j<mGraph.vexnum;j++){
            printf("%d  ",mGraph.edge[i][j]);
        }
        printf("\n");
    }
    // printf("\n");
}

按照无向图到有向图顺序打印结果

在这里插入图片描述

在这里插入图片描述

邻接表——顺序+链式存储

// 领接表法——和树的孩子表示法类似
// ‘边、弧’
typedef struct arcNode{
    int adjvex;//指向哪一个结点
    struct arcNode *next;//指向下一条弧的指针
}arcNode;
// ‘顶点’
typedef struct vNode{
    vertexType data;//顶点信息
    arcNode *first;//第一条弧
}vNode,adjList[MaxVertexNum];
// 用邻接表存储的图
typedef struct{
    adjList vertices;//表征的是顶点的集合
    int vexnum,arcnum;
}ALGraph;
实现从邻接矩阵存储图转化为邻接表存储图
// 初始化邻接表
void initALGraph(ALGraph *alGraph){
    (*alGraph).vexnum=0;
    (*alGraph).arcnum=0;
}

// 给出顶点的值,寻找邻接矩阵中顶点值的对应下标并返回
int indexof(MGraph mGraph,vertexType vex){
    for(int i=0;i<mGraph.vexnum;i++){
        if(mGraph.vex[i]==vex)
            return i;
    }
    return -1;//表示下标不存在
}
// 根据邻接矩阵图创建图的邻接表法表示
void creatALGraph(MGraph mGraph,ALGraph *alGraph){
    (*alGraph).vexnum=mGraph.vexnum;//修改顶点数量和边的数量
    (*alGraph).arcnum=mGraph.arcnum;


    for(int i=0;i<mGraph.vexnum;i++){
        // 处理顶点
        vNode *vex=(vNode *)malloc(sizeof(vNode));//申请一个顶点空间
        vex->data=mGraph.vex[i];
        vex->first=NULL;

        //处理边
        int index=indexof(mGraph,vex->data);//寻找对应的顶点的对应行坐标
        arcNode *p=vex->first;//指针指向
        for(int j=0;j<mGraph.vexnum;j++){
            if(mGraph.edge[index][j]==1){//表示有数据
                // 申请一个边结点
                arcNode* arc=(arcNode*)malloc(sizeof(arcNode));
                arc->adjvex=j;
                arc->next=NULL;

                if(p==NULL){//说明是第一个节点边
                    vex->first=arc;
                    p=arc;
                }else{
                    p->next=arc;
                    p=p->next;
                }
            }
        }
        (*alGraph).vertices[i]=*vex;
    }

    printfALGraph(*alGraph);
}

// 打印邻接表边
void printfArc(arcNode *arc){
    arcNode *p=arc;
    while(p!=NULL){
        printf("%d->",p->adjvex);
        p=p->next;
    }
    printf("\n");
}
// 打印邻接表定义的图
void printfALGraph(ALGraph alGraph){
    printf("邻接表打印,数字表示的是结点对应下标……\n");
    for(int i=0;i<alGraph.vexnum;i++){
        printf("|%c|->",alGraph.vertices[i].data);
        printfArc(alGraph.vertices[i].first);
    }
}

在这里插入图片描述

图的基本操作

Adjacent(G, x, y):判断图 G 是否存在边 <x, y> 或 (x, y)
Neighbors(G, x):列出图 G 中与结点 x 邻接的边
InsertVertex(G, x):在图 G 中插入顶点 x
DeleteVertex(G, x):从图 G 中删除顶点 x
AddEdge(G, x, y):若无相边 (x, y) 或有向边 <x, y> 不存在,则向图 G 中添加该边
RemoveEdge(G, x, y):若无相边 (x, y) 或有向边 <x, y> 存在,则向图 G 中删除该边
FirstNeighbor(G, x):求图 G 中顶点 x 的第一个邻接点,若有则返回顶点号。若 x 没有邻接点或图中不存在 x,则返回 -1

NextNeighbor(G, x, y):假设图 G 中顶点 y 是顶点 x 的一个邻接点,返回除 y 之外顶点 x 的下一个临界点的顶点号,若 y 是 x 的最后一个邻接点,则返回 -1
Get_edge_value(G, x, y):获取图 G 中边 (x, y) 或 <x, y> 对应的权值
Set_edge_value(G, x, y, v):设置图 G 中边 (x, y) 或 <x, y> 对应的权值v

#include "graph.h"

// 求图中顶点x的第一个邻接点,有就返回顶点号——邻接矩阵示例
int firstNeighbor1(MGraph mGraph,vertexType x){
    int index=indexof1(mGraph,x);
    for(int i=0;i<mGraph.vexnum;i++){
        if(mGraph.edge[index][i]==1)
            return i;
    }
    return -1;//表示没有第一个邻接点
}
// 给出邻接表的一个顶点,返回其下标表示
int indexof2(ALGraph alGraph,vertexType vex){
    for(int i=0;i<alGraph.vexnum;i++){
        if(alGraph.vertices[i].data==vex)
            return i;
    }
    return -1;
}
// 邻接矩阵示例
int firstNeighbor2(ALGraph alGraph,vertexType x){
    int index=indexof2(alGraph,x);
    if(alGraph.vertices[index].first!=NULL)
        return (alGraph.vertices[index].first)->adjvex;
    else
        return -1;
}
// 返回图顶点x的除了y之外的下一个邻接点的顶点号,如果y是最后一个就返回-1——邻接矩阵示例
int nextNeighbor1(MGraph mGraph,vertexType x,vertexType y){
    int index=indexof1(mGraph,x);
    int j=indexof1(mGraph,y);//y对应的下标值

    int i=j+1;

    for(;i<mGraph.vexnum;i++){
        if(mGraph.edge[index][i]==1){
            // printf("%d",i);
            break;//找到对应的内容,就结束循环
        }
    }
    
    if(i==mGraph.vexnum)//刚好是最后一个邻接点
        return -1;
    return i;//不是最后一个邻接点,直接返回其下标,在本测试用例中所有结点按照字母顺序排序,对于不按照字母排序的图,只要其顶点表和边表的结点下标是对应的(应该没有不对应的),就可以用直接返回下标实现返回结点下标
}
// 返回图顶点x的除了y之外的下一个邻接点的顶点号,如果y是最后一个就返回-1——邻接表示例
int nextNeighbor2(ALGraph alGraph,vertexType x,vertexType y){
    int index=indexof2(alGraph,x);
    int j=indexof2(alGraph,y);
    arcNode *p=alGraph.vertices[index].first;
    while(p!=NULL){
        if(p->adjvex==j){
            if(p->next==NULL)//表示没有下一个邻接点
                return -1;
            else
                return p->next->adjvex;
        }
        p=p->next;
    }
}

图的遍历

下面遍历的伪代码中 FirstNeighbor(G,v);w>=0和 NextNeighbor(G,v,w)实现有点不同,但是实际上,图的存储结点不一定就是int类型数据或者char类型数据,所以还是要根据具体要求来实现

BFS

bool visited[MAX_VERTEX_NUM];//访问标记数组

//对于非连通图,也可以实现BFS
void BFSTraverse(Graph G){
    for(int i=0;i<G.vexnum;i++){//初始化标记数组
        visited[i]=false;
    }
    InitQueue(Q);//初始化辅助队列
    for(int i=0;i<G.vexnum;i++){
        if(!visited[i])
            BFS(G,i);//从顶点i开始进行BFS
    }
}
//从顶点v出发,广度优先遍历图G
void BFS(Graph G,int v){
    visit(v);//访问初始顶点v
    visited[v]=true;
    EnQueue(Q,v);
    while(!isEmpty(Q)){
        DeQueue(Q,v);
       for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
            if(!visited[w]){
                visit(w);
                visited[w]=true;
                EnQueue(Q,w);
            }
        }
    }
}

DFS

bool visited[MAX_VERTEX_NUM];//访问标记数组

void DFSTraverse(Graph G){
    for(int i=0;i<G.vexnum;i++){//初始化标记数组
        visited[i]=false;
    }

    for(int i=0;i<G.vexnum;i++){
        if(!visited[i])
            DFS(G,i);//从顶点i开始进行BFS
    }
}
void DFS(Graph G,int v){
    visit(v);
    visited[v]=true;
    for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
        if(!visited[w]){
            DFS(G,w);
        }
    }
}

复杂度分析

广度优先搜索需要借助辅助队列,而深度优先遍历需要借助递归栈,所以无论是邻接矩阵还是邻接表,都需要O(|V|)的空间复杂度

采用邻接表法存储图进行搜索时,每个顶点需要搜索一遍,时间复杂度是O(|V|),遍历时每条边也需要遍历一遍,时间复杂度为O(|E|),所以总的时间复杂度为O(|V|+|E|)

采用邻接矩阵法存储图进行搜索时,需要遍历全部的顶点,在遍历每个顶点时,查找每个顶点的邻接点需要再遍历一遍结点,所以时间复杂度为O(|V|^2)

实际上时间复杂度的要求,取决于其使用的存储结构

最小生成树MST

生成树:包含图的全部顶点的一个极小连通子图
包括广度优先生成树和深度优先生成树以及最小生成树,最小生成树可能有多个

  • 无权图或者权值相同的图
    对于无权图或者权值相同的图,无论怎么选边,最小生成树权值之和都是边的权值*(边数-1)

prim算法(普里姆)

从某一个顶点开始构建生成树,每次都将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止——先选点,适合点少边多的边稠密图,时间复杂度为O(|V|^2)
同一个图可能有多个最小生成树

算法实例

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Kruskal算法(克鲁斯卡尔)

每次选择一条权值最小的边,使得边的两头连通,原本就连通的就不选,直到左右的点都连通——先选边,适合边少点多的边稀疏图,时间复杂度为O(|E|log2|E|)(一共有e的边,每次都需要用并查集判断两个顶点是否属于同一个集合,需要long2E的时间复杂度)

算法示例

在这里插入图片描述

在这里插入图片描述

在检查是否连通的这点上,使用到了并查集,并查集采用的是树的双亲表示法。

在这里插入图片描述

在这里插入图片描述

最短路径问题

单源最短路径问题

  • 无权图——使用广度优先算法求无权图单源最短路径问题
void BFS_MIN_Distance(Graph G,int u){
    //新增一个数组,包含两个部分d[i]表示从u到i的最短路径,path[i]表示最短路径是从哪个顶点过来的
    for(i=0;i<G.vexnum;i++){
        //初始化数组
        d[i]=无穷大;
        path[i]=-1;
    }
    d[u]=0;//把u顶点放到队列中,因为是第一个顶点所以路径是0
    visited[u]=true;
    EnQueue(Q,u);
    while(!isEmpty(Q)){
        DeQueue(Q,u);
        for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w)){
            if(!visited[w]){
                d[w]=d[u]+1;//路径长度++
                path[w]=u;//最短路径从u到w
                visited[w]=true;//标记已访问
                EnQueue(Q,w);//顶点w入队
            }
        }
    }
}

通过新添加的数组中d[w]可以快速查找u到w的最短路径长度,通过path[w]字段可以快速查找所需路径的前一个结点,通过查找前一个结点的方式循环查找前一个结点直到找到根节点,由此可以找到其最短路径。

通过广度优先遍历得到的广度优先树的高度一定是最小的。


  • 有权图——Dijkstra算法(迪杰斯特拉)
    每次选择的都是权值最小的那个对应顶点开始计算
    并且每次被选中的结点在后面的循环中其最小距离不会发生改变,根据这个特性可以求迪杰斯特拉算法求的最小路径的的结点的顺序
    带权路径长度:当图带全图时,一条路径上所有边的权值之和,称为该路径的带权路径长度
    Dijkstra算法(迪杰斯特拉):也适用于无权图或者权值相同的图

算法实例

在这里插入图片描述

初始化信息的备注:v0到v0肯定是可达的,且距离就是0。

因为v0到v1和v4都是可达的,所以路径信息可以直接初始化

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

时间复杂度

遍历每一个顶点的时候还要遍历其他的顶点,所以时间复杂度为O(|v|^2)

  • 带负权值的带权图
    如果权值带负数,迪杰斯特拉算法可能会失效,找不到最优路径

顶点间求最短路径——Floyd算法

Floyd算法:适合于带权图或者无权图——动态规划

动态规划:将问题求解分为多个阶段,每个阶段都有一定的递进关系

在这里插入图片描述

算法示例:

算法初始化的时候,各个顶点的最短路径长度为邻接矩阵的形式,因为不允许中转

在这里插入图片描述

使用V0作为中转,V2存在经过V0到达V1的路径,长度为11,前一个结点为V0,因此需要修改对应的两个值

在这里插入图片描述

对于允许以V0和V1作为中转点的实现,对于V0到V2的路径中可以有V0-V1-V2的路径,长度为10,因此需要修改两个值。

在这里插入图片描述

允许所有结点中转,从V1到V0可以中转V2,有对应路径长度为9

在这里插入图片描述

经过|V|论递推实现,最终得到的两个二维数组就是动态规划得到的最终结果

在这里插入图片描述

//核心代码
for(int k=0;k<n;k++){//考虑以Vk作为中转
    for(int i=0;i<n;i++){//遍历整个矩阵
        for(int j=0;j<n;j++){
            if(A[i][j]>A[i][k]+A[k][j]){//表示路径更短
                A[i][j]=A[i][k]+A[k][j];
                path[i][j]=k;
            }
        }
    }
    
}

算法时间复杂度为O(|V|3),

空间复杂度为O(|V|2)

这个算法整合中转的时候,只需要整合在邻接矩阵(修改前后都可以)中对应顶点的行在中转顶点所在的列中是否有非0或者非∞的数据即可

可以解决带负权值的图,不能解决带有负权回路的图

在这里插入图片描述

有向无环图

若一个有向图中不存在环,称为有向无环图,简称DAG

在这里插入图片描述

正确答案是A

在这里插入图片描述

对于有向无环图的做题:

顶点中不可能出现重复的操作数

在这里插入图片描述

拓扑排序-顶点表示活动

AOV网:用DAG(有向无环图)表示一个工程,顶点表示活动

拓扑排序:找到做事的先后顺序,可能有多个拓扑排序

拓扑排序的实现:

①从AOV网中选择一个没有前驱(入度为0)的顶点并输出。

②从网中删除该顶点和所有以它为起点的有向边。

③重复①和②直到当前的AOV网为空或当前网中不存在无前驱的顶点为止。后者说明图中有回路,不是DAG

  • 使用邻接表存储实现拓扑排序

indegree[i]:表示i结点的入度是多少

print[i]:用来记录拓扑排序序列,当有结点出栈的时候,printf数组就会记录一次,初始化时都是-1

bool TopologicalSort(Graph G){
    InitStack(S);//初始化栈,存储入度为0的顶点
    for(int i=0;i<G.vexnum;i++){
        if(indegree[i]==0)
            Push(S,i);//将所有入度为0的顶点入栈
    }
    int count=0;//计数,记录当前已经输出的顶点数
    while(!isEmpty(S)){//存在入度为0的顶点
        Pop(S,i);//栈顶元素出栈
        print[count++]=i;//输出顶点元素i
        //for:将所有i指向的顶点的入度减1(主要是修改indegree数组,图邻接表本身没变),并且将入度为0的顶点入栈
        for(p=G.vertices[i].firstarc;p;p=p->nextarc){
            v=p->adjvex;
            if(!(--indegree[v]))
                Push(S,v);//入度为0,出栈
            
        }
    }
    if(count<G.vexnum)
        return false;//拓扑失败,说明有向图中存在回路
    else
        return true;//拓扑成功
    
    
    
}

时间复杂度:O(|V|+|E|)

如果用邻接矩阵实现,时间复杂度为O(|V|2)

逆拓扑排序

对一个AOV网,如果采用下列步骤进行排序,则称之为逆拓扑排序:

①从AOV网中选择一个没有后继(出度为O)的顶点并输出。

②从网中删除该顶点和所有以它为终点的有向边。

③重复①和②直到当前的AOV网为空。

用邻接矩阵实现比用邻接表实现具有更少的时间复杂度,或者可以采用逆邻接表法,也就是邻接表记录的是每个顶点的入度边

用DFS实现拓扑排序

bool visited[MAX_SIZE];
int finishTime[MAX_SIZE];
void DFSTraverse(Graph G){
    for(v=0;v<G.vexNum;v++){
        visited[v]=false;
}
    int time=0;
    
    for(v=0;v<G.vexNum;v++){
        if(!visited[v]){
            DFS(G,v);
}
void DFS(Graph G,int v){
    visited[v]=true;
    visit(v);
    for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
        if(!visited[w]){
            DFS(G,w);
}
}
    time=time+1;
    finishTime[v]=time;
 }

用DFS实现逆拓扑排序

在这里插入图片描述

只是多一个输出代码而已

bool visited[MAX_VERTEX_NUM];//访问标记数组

void DFSTraverse(Graph G){
    for(int i=0;i<G.vexnum;i++){//初始化标记数组
        visited[i]=false;
    }

    for(int i=0;i<G.vexnum;i++){
        if(!visited[i])
            DFS(G,i);//从顶点i开始进行BFS
    }
}
void DFS(Graph G,int v){
    visit(v);
    visited[v]=true;
    for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
        if(!visited[w]){
            DFS(G,w);
        }
    }
    printf(v);//输出顶点***在DFS基础上新增的
}

现在在这个基础上,需要判断图中是都有回路问题,方法是:邻接点如果已经被访问了且存在于栈中,说明存在环路,可采用一个数组来表示当前一次循环中入栈的元素,这样就可以实现快速查找是否当前下一个结点是被访问且已经存在于栈中的。

bool visited[MAX_VERTEX_NUM];//访问标记数组
bool isStack[MAX_VERTEX_NUM];//当前结点是否在栈中
bool balance=true;//判断是否拓扑成功,初始化认为是拓扑成功的

void DFSTraverse(Graph G){
    for(int i=0;i<G.vexnum;i++){//初始化标记数组和isStack数组
        visited[i]=false;
        isStack[i]=false;
    }

    for(int i=0;i<G.vexnum;i++){
    	if(balance==false)
    		break;//拓扑失败,可以直接结束
        if(!visited[i])
            DFS(G,i);//从顶点i开始进行BFS
    }
}
void DFS(Graph G,int v){
    visit(v);
    visited[v]=true;
    isStack[v]=true;
    for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
        if(visited[w]&&isStack[w]){//被访问且已经存在栈中
        	balance=false;//拓扑失败
            printf("存在回路,拓扑失败");
            return;
        }
        if(!visited[w]){
            DFS(G,w);
        }
    }
    printf(v);//输出顶点
    isStack[v]=false;//每次出栈,都需要修改数组
}

关键路径-边表示活动

AOE网:带权有向图中,顶点表示事件,有向边表示活动,边的权值表示完成活动的开销,这种用边表示活动的网络叫做AOE网

AOE的两个性质

1、只有在某个顶点所代表的事件发生后,各个顶点触发的各有向边所代表的活动才能开始

2、只有在进入某顶点的各有向边代表的活动都已经结束,该顶点表示的事件才能发生

另外有一些活动是可以并行进行的

在AOE中仅有一个入度为0的顶点表示为开始顶点(源点),它表示整个工程的开始,也仅有一个出度为0的顶点,叫做结束顶点(汇点),它表示整个工程的结束

关键路径:从源点到汇点的有向路径可能有多条,所有路径中,具有最大路径长度的称为关键路径,关键路径上的活动叫做关键活动

完成整个工程的最短时间就是关键路径长度,若关键路径活动不能按时完成,则整个工程的完成时间就会延长——有很重要的现实意义

事件Vk的最早发生事件ve(k)——决定了所有从Vk开始的活动能够开工的最早时间

事件Vk的最迟发生事件vl(k)——它是说在不推迟整个工程完成的前提下,该事件最迟必须发生的时间

时间余量:ve(k) - vl(k),表示的是在不延后整个工程的前提下,活动k可以拖延的时间,如果一个活动的时间余量为0,说明这个活动必须如期完成,这个活动就是关键活动

  • 求关键路径

在这里插入图片描述

  • 求所有事件的最早发生时间

    先求拓扑排序 1-3-2-5-4-6

    源点的最早发生时间一定是0

    后继结点的最早发生时间=前序结点的最早发生时间+对应的边的权值,取最大值(因为结点的所有前驱发生了它才能发生)

    V1V2V3V4V5V6
    ve(k)032668
  • 书上有一句话说求完ve的过程就可以知道关键路径,但是没有给具体解释,我对这句话的理解是,当我们在取这个最早发生时间的时候,对于多路径需要选择最早发生时间最大的那个值,我们在取这个最大的值的过程(这个最大值所经过的路径),就是找关键路径的过程(就是关键路径),这是有定义决定的,不理解的可以参考这个博文,过程写的十分详细

  • 求所有事件的最迟发生时间

    求逆拓扑排序 6-5-4-2-3-1

    汇点最迟发生时间一定是它的最早发生时间

    后继结点的最迟发生时间-对应边的权值,取最小值

    V1V2V3V4V5V6
    vl(k)042678
  • 所有活动的最早发生时间——和前向事件的最早发生时间是一样的

    a1a2a3a4a5a6a7a8
    e(k)00332266
  • 所有活动的最迟发生时间——后继结点的最迟发生时间减去边的权值就是活动的最迟发生时间

    a1a2a3a4a5a6a7a8
    l(k)10442567
  • 求所有活动的时间余量:活动最早 - 活动最晚

    a1a2a3a4a5a6a7a8
    d(k)10110301

d(k)=0,表示的关键活动,关键活动连成的边是关键路径。
关键活动时间被压缩,是可以缩短工程时间的,但是当关键活动被压缩到一定程度的时候,关键活动可能转化为非关键活动,这个时候关键路径已经改变。
关键路径可能不仅仅只有一条,这种特性下还是要缩短工程就要缩短所有关键路径的工程的时间。

算法与数据结构复习 第六章 图(详解) 文章目录第六章 图书面作业一、判断题二、单选题三、填空题 第六章 图 书面作业 一、判断题 1、如果无向图G必须进行两次广度优先搜索才能访问其所有顶点,则G一定有2个连通分量。 (T) 解析: 进行一次广度优先搜索,则与之连通的结点都会遍历到。 2、在任一有向图中,所有顶点的入度之和等于所有顶点的出度之和。 (T) 解析: 一个结点的出度是相连结点的入度; 3、用邻接矩阵法存储图,占用的存储空间数... 阅读详情

相关推荐

数据结构(C语言第2版) 课后习题答案之 第六章 图

目录 第六章 图 1.选择题 2.应用题 (1)已知如下图所示的有向图,请给出: (2)已知如图6.28所示的无向网,请给出: ​ (3)已知图的邻接矩阵如6.29所示。试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。 (4)有向网如图6.30所示,试用迪杰斯特拉算法求出从顶点a到其他各顶点间的最短路径,完成表6.9。 (5)试对图6.31所示的AOE-网: 3.算法设计题 (1)分别以邻接矩阵和邻接表作为存储结构,实现以下图的基本操作: (2...

Yummy的博客 9519

看博客总结出来的图【数据结构】

写在开头: 文章借鉴于他人博客,加上了一些细节 1.图的定义 图(Graph)G由两个集合V和E组成,记为G=(V,E),其中V是顶点的有穷非空集合,E是V中顶点的边的有穷集合。 有向图: 顶点之间的边有方向,有向边也叫做弧,起点叫做弧尾,终点叫做弧头。 无向图: 顶点之间的边没有方向。 2.图的基本术语 无向完全图: 无向图中,任意两个顶点之间都存在边,有n(n-1) 条边。 有向完全图: 有向图中,任意两个顶点之间都存在方向互为相反的两条弧,有n(n-1) 条弧。 权 每条边上具有的含有某种特殊含

liudachu的博客 570

数据结构第六章(一) -图

就是说了一下图的一些常用的概念,这些概念在之后也会用到。要注意的就是连通图最少n-1条边,非连通图最多C2n-1条边,这些都是无向图。有向图的话强连通图最少有n条边,是一条回路。

weixin_45270383的博客 1381

数据结构之图

图的存储 邻接矩阵存储 存储实现 #define MaxVertexNum 100 //结点数 typedef char VertexType; typedef int ElemType; typedef struct { VertexType Vex[MaxVertexNum];//顶点表 ElemType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表 int vexnum,arcnum;//结点数 边数 }MGraph;

Stillboring的博客 1363

408《数据结构》——第六章:图 (Graph)

图G由两个集合V(顶点集, Vertex) 和E(边集, Edge) 组成,记作G = (V, E)。其中E是顶点对的集合(边可以有权重)。基本术语:图中的数据元素。顶点之间的连接关系。边没有方向,(v, w)等价于(w, v)。边有方向,<v, w>表示从v指向wv是弧尾 (Tail)w是弧头 (Head)。不存在重复边,且不存在顶点到自身的边(无自环)。完全图:任意两个顶点之间都存在边。边数。任意两个顶点之间都存在方向相反的两条弧。弧数。是G = (V, E)的子图,需满足。

jiaomongjun的博客 1242

数据结构知识点总结11-(第六章.图)-图的基本概念

数据结构知识点总结-(第六章.图)-图的基本概念。图G由顶点集V和边集E组成,记为G=(V,E),其中V(G)表示图G中顶点的有限非空集;E(G)表示图G中顶点之间的关系(边)集合。若V = v1,v2,v3,…,vn,用|V|表示图G中顶点的个数,也称为图G的阶,E=(u,v)|u∈V,v∈V,用|E|表示图G中边的条数。注意:线性表可以是空表,树可以是空树,但图不可以是空图。也就是说,图中不能一个顶点也没有,图中顶点集V一定非空,但是边集E可以为空,此时图中只有顶点而没有边。

随风浪仔的专栏 314

C++数据结构,判断图中是否存在边x,y

判断图中是否存在边<x,y> 更多内容请访问个人主页 1. 存储结构为邻接表时 typedef char VertexType; typedef int EdgeType; typedef struct { VertexType Vex[MAXSIZE]; //点表 EdgeType Edge[MAXSIZE][MAXSIZE]; //边表 }MGraph; 1.算法描...

weixin_41546300的博客 1163

【清华大学软院】15-19年推免试题历年整理

整理的题目为清软预推免机试(外校)硕士机考题目。 年代 题目1 题目2 题目3 2019 质因数分解 二叉搜索树 区间DP(折叠字符串) 2018 日期转换 高精度模拟 区间DP(戳气球) 2017 进制素数 被遗漏的数字 DP(收集苹果) 2016 大数运算 DP DFS 2015 进制素数: http://acm.hdu.edu.cn/showproblem.php?pid=1230 2017年题目(这个是找的最准确的题目): 1题见【链接】 2题见【链接】

云端筑梦师 2256

第12周项目3图深度优先遍历算法实现

问题及代码: /* * * * * *烟台大学计算机与控制工程学院 *作者:刘熠 *时间:2015年11月16日 * * * * *问题描述:操作用邻接表存储的图 * */ #ifndef GRAPH_H_INCLUDED #define GRAPH_H_INCLUDED #define MAXV 100

liuyi6666的博客 552

自动驾驶系统架构解析:从传感器到决策控制的完整链路(附实际案例)

本文深入解析了自动驾驶系统从感知、决策到控制的完整架构链路。通过对比摄像头、毫米波雷达、激光雷达等核心传感器的特性与选型策略,阐述了多传感器融合的必要性。文章结合行为决策、运动规划及控制执行的实际案例,揭示了系统集成、数据闭环与功能安全等工程化落地的关键挑战,为理解自动驾驶技术提供了清晰的蓝图。

weixin_29051811的博客 339

Win10自动更新怎么永久关闭?有效的Win10强制更新关闭方法

之前小编为大家分享过一些Win10彻底关闭Windows Update自动更新的方法,主要是通过一些如设置流量计费或借助一些专门的小工具来实现,但往往会发现,Win10自动更新就像打不死的小强,不管怎么关闭,之后还是会自动更新,让不少小伙伴颇为不爽。今天小编带来了这篇改进型教程,通过全方位设置,彻底关闭Win10自动更新,感兴趣的小伙伴不妨试试吧。 ...

jianai0602的博客 108万+

学习数据结构--第五章:图(图的基本操作)

第五章:图(图的基本操作) 1.Adjacent(G,x,y) Adjacent(G,x,y) 判断图G是否存在边<x,y>或(x,y) 如上 无向图 的邻接矩阵和邻接表表示方法,判断方法分别为: 邻接矩阵:判断该边是否存在直接判断对应邻接矩阵中的值即可,如果是1存在否则不存在 邻接表:判断对应顶点的边表是否存在这样一条边的边表结点 因为无向图的邻接矩阵和邻接表我们会存储两遍,所以无论第一个结点x做行号,还是y做行号都可以进行查找 如上 有向图 的邻接矩阵和邻接表表示方法,判断方法分

理木客 3435

【PyTorch项目实战】之ResNet系列:resnet18、resnet34、resnet50、resnet101、resnet152

ResNet (Residual Network) :是一种深度卷积神经网络(CNN)架构,其核心思想是引入残差学习(Residual Learning)机制,通过跳跃连接(skip connections)来解决深层网络训练中的退化问题。

📷 图像处理践行者 | 传统算法 · 深度学习 · 多模态 | 分享实战经验与技术洞见,探索从像素到智能的无限可能。 6378

《数据结构》第六章 知识点结构导图

《数据结构》第六章 知识点结构导图

wjy_papapa的专栏 943

YOLO26涨点改进 | 检测头Head创新改进篇 | 利用DynamicConv高效动态卷积改进YOLO26检测头,DyHead通过增强检测头操作,轻量化改进、提高模型目标检测的精度

YOLO26作为新一代轻量化目标检测模型的代表,凭借精简的Backbone架构、高效的特征融合 Neck 设计,在实时检测场景(如嵌入式设备、边缘计算)中展现出优异的性能,成为工业落地与科研入门的优选模型。但在实际应用中,YOLO26原生检测头仍存在核心瓶颈:采用固定参数卷积核,无法根据输入特征的目标尺度、纹理差异动态调整卷积权重,导致多尺度目标适配不足、复杂背景下特征利用率低,且传统改进方案往往难以平衡“精度提升”与“轻量化”,要么精度提升有限,要么大幅增加参数量、拖累推理速度。

qq_36130719的博客 376

Qt代码-无人机地面站系统

无人机地面站系统是无人机与操作员交互的核心枢纽,需实现**无人机状态监控**(位置、姿态、电池等)、**实时通信**(基于MAVLink协议)、**任务规划**(航点编辑、航线生成)、**地图显示**(无人机位置与航迹)、**参数配置**(传感器校准、飞行模式设置)等核心功能。地面站作为`MAV_COMP_ID_GCS`(地面控制站组件ID=255),无人机通常为`MAV_COMP_ID_AUTOPILOT1`(组件ID=1)。// 航点/盘旋指令。// 北京坐标(示例)

寒寒豆豆软件公司的博客 325

《数据结构》第六章 图 教学设计

第六章图,是很重要也是最难的一章了,当然有前面的基础,学习也不会困难。本章节主要是概念多,图结构复杂。本设计也属于迟到的了。抱歉。 本章学习总时问分为2周,共四次课来来学习。两次上课(13周和14周),一次实验(14周,实验不给课内时间,请大家自己找时间完成,我只检查大家的实验)。以下是教学设计。 第六章 图   教学设计 一、课前预习任务 学习资料    资料1. 教材第六

明哥的专栏 2982

大模型的实践应用31-基于通义千问大模型改进台风和海浪预测研究,利用机器学习与通义千问结合的思路实现对台风和海浪预测分析

通义千问大模型是阿里巴巴云推出的一款大规模语言模型,旨在通过强大的自然语言处理能力提供更广泛的服务。从最初的构想到如今的广泛应用,通义千问的发展见证了人工智能领域尤其是自然语言处理技术的巨大进步。自发布以来,通义千问不断迭代升级,不仅在规模上实现了飞跃式的增长,在性能方面也得到了显著提升。本次研究尝试将通义千问强大的自然语言处理能力与经典的时间序列预测算法相结合,旨在提高对未来天气状况预测的精度。具体来说,首先使用通义千问生成高质量的文字描述来增强特征表达力;

微学AI的博客 936

408数据结构知识点——第六章 图

图G由顶点集V和边集E组成,记为G=(V,E),其中V(G)表示图G中顶点的有限非空集;E(G)表示图G中顶点之间的关系(边)集合。若Vv1v2vnVv1​v2​...vn​,则用|V|表示图G中顶点的个数,也称图G的阶,Euv∣u∈Vv∈VE{(uv∣u∈Vv∈V,用|E|表示图G中边的条数。注:图的顶点集V一定非空,边集E可以为空。图逻辑结构的应用基于邻接矩阵和邻接表进行基本操作的复杂性讨论。

tridoron6的博客 1407

华硕 NUC 对比 Mac mini:跑 OpenClaw 谁性价比更高?

在选择本地 AI 服务器硬件时,华硕 NUC 和 Mac mini 是两个热门选择。本文将从硬件规格、性能、成本、功耗、易用性等多个维度对比这两款设备,帮助您决定哪款更适合运行 OpenClaw。

博客 1023

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

王道数据结构课代表 - 考研数据结构 第六章 图 究极精华总结笔记

数据结构与算法 笔记导航🚥🚥🚥 🥬 第一章 绪论 🥕 第二章 线性表 🥪 第三章 栈和队列 🍊 第四章 串-KMP(看毛片算法) 🍒 第五章 树和二叉树 🍀 第六章 图 🍚 第七章 查找(B树、散列表) 🧄 第八章 排序 (内部排序:八大排序动图演示与实现 + 外部排序) 🍔 数据结构与算法 复试精简笔记 🎨 408 全套初复试笔记汇总 传送门 🏃‍🏃‍🏃‍ 如果本篇文章对大家起到帮助的话,跪求各位帅哥美女们,求赞👍 、求收藏、求关注!

clzZ2的博客 8840

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

【数据结构】第六章:图

【数据结构】第六章:图 一、图的定义 1. 基本概念 2. 特殊的图 二、图的存储结构 1. 邻接矩阵 2. 邻接表 3. 十字链表 4. 邻接多重表 5. 四种存储结构对比 三、图的基本操作 四、图的遍历算法 1. 广度优先遍历 2. 深度优先遍历 五、图的应用 1. 最小生成树 2. 最短路径问题 3. 有向无环图描述表达式 4. 有向无环图拓扑排序 5. 关键路径

iFulling的博客 1581

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

数据结构 第六章(图)【上】

(1)图(Graph)G由两个集合V和E组成,记为G=(V, E),其中V是顶点的有穷非空集合,E是V中顶点偶对的有穷集合。V(G)和E(G)通常分别表示图G的顶点集合和边集合,E(G)可以为空集,若E(G)为空,则图G只有顶点而没有边。(2)对于图G,若边集E(G)为有向边的集合,则称该图为有向图;若边集(G)为无向边的集合,则称该图为无向图。

Zevalin的博客 2443

《数据结构》-第六章 图(知识点总结)(上)-图的基本概念与遍历

第六章 图 本章作为非线性结构中的另一类结构,以多对多的数据结构形式存在,图中结点之间关系可以是任意的,任意两个元素之间都可能相关。相比于树型结构,有更广泛的应用。 本章知识点和考点较为集中,因本章涉及较多算法,所占考分比重较大,常以选择题、综合应用题的形式出现。 【考点】①图的基本概念; ②图的存储及基本操作; ③图的遍历; ④图的基本应用。 【本章大纲】 【目录】 第六章 图 一、...

大家一起进步叭 4887

数据结构知识点总结

数据结构知识点总结(参考王道)

uiguhgd的博客 7759

数据结构第六章——图

数据结构——图

qq_54122504的博客 1121
上一篇: 数据结构应用题第五章树代码c
下一篇: 单片机——显示方式
@@老胡
博客等级 码龄7年 291粉丝 184原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值