图_考研笔记

第六章 图

代码基本来源:2020王道数据结构考研复习指导 (侵删)

6.2 图的存储结构

重点掌握邻接矩阵法和邻接表法

image-20201017155312580

6.2.1 邻接矩阵法存储带权图(网)

  • 存储核心
    • 一维数组存储图中顶点的信息
    • 矩阵存储图中边的关系(各顶点之间的邻接关系)
  • 存储思想
    • 利用矩阵元素下标[i] [j]表示无向边(vi,vj)或有向边<vi,vj>
    • 利用矩阵元素的值表示权值或是否连通(非带权图)
    • 利用Infinity宏定义无穷(“0”或∞)
  • 性能分析:空间复杂度O(|V|^2),适用存储稠密图、可压缩存储
#define MaxVertexNum 100 //顶点数目的最大值
#define INfinity 最大的int值 //宏定义常量“无穷”
typedef char VertexType;//顶点的数据类型
typedef int EdgeType; //带权图上边上权值的数据类型
// bool EdgeType; 非带权图用bool类型变量表示是否存在边
typedef struct{
    
    
    VertexType Vex[MaxVertexNum];//顶点
    EdgeType Edge[MaxVertexNum][MaxVertexNum];//边的权
    int vexnum,arcnum;//图的当前顶点数和弧数
}MGraph;

6.2.2 邻接表法

  • 存储核心:

    • 一维结构数组顺序存储顶点信息(边表的头指针和数据信息
    • 链式存储邻接自顶点的边表(出边
  • 存储思想

    image-20201017154525870

  • 性能分析

    • 空间复杂度O(|V|+|E|),适用存储稀疏图
    • 无向图每条边对应两份数据无向边的两端顶点
//用邻接表存储的图
typedef struct{
    
    
 	AdjList vertices;//AdjList 结构数组类型(AdjList中文:邻接表)
    int vexnum,arcnum;
}

//”边/弧”
typedef struct ArcNode{
    
    
    int adjvex;//边/弧邻接到的顶点
    struct ArcNode *next;//指向下一条弧的指针
    //INfo Type; //边权值
}ArcNode;

//“顶点”
typedef struct VNode{
    
    
    VertexType data;//顶点信息
    ArcNode *first;//第一条边/弧
}VNode;AdjList[MaxVertexNum];

6.3 图的两种遍历(透彻掌握)

  • 考虑时间复杂度:脱离代码考虑,考虑访问结点的时间+访问所有边的时间(访问所有邻接点的时间)

6.3.1 BFS(广度优先遍历_重点掌握)

  • 算法核心

    • 从v出发,依次访问v的所有各个邻接顶点(广度)

    • 需要一个辅助队列保存:v的所有未被访问的邻接顶点

    • 标记哪些顶点被访问过(防止重复访问

  • 性能分析:

    image-20201017163646329

bool visited[MaxVertexNum];//访问标记数组
void BFSTraverse(Graph G){
    
     
    for (int i = 0;i<G.vexnum;++i)
        visited[i]=false;//访问标记数组初始化
    InitQueue(Q);//初始化辅助队列Q
    for(int i = 0;i<G.vexnum;++i)
        if (!visited[i])//对每个连通分量调用一次BFS
            BFS(G,i);//vi未访问过,从vi开始BFS
}

//广度优先遍历
void BFS(Graph G,int v){
    
    //从顶点v出发,广度优先遍历图G
    visit(v);//访问初始顶点v
    visited[v] = true;//对v做已访问标记
    EnQueue(Q,v);//顶点v入队Q
    while (!IsEmpty(Q)){
    
    
        DeQueue(Q,v);//用v指向出队的队头元素
        for (w=FistNeighbor(G.v);w;w=NextNeighbor(G,v,w))//找邻接自v的所有结点 
            if (!visited[w]){
    
    //顶点w未被访问过
                visit[w];//访问顶点w
                visited[w] = true;//对w做以访问标记
                EnQueue(Q,w);//顶点w入队           
            }//if
    }//while
}

6.3.2 DFS(深度优先遍历_BFS变种)

  • 算法核心

    • 从v出发,找到与v邻接的任一未访问顶点,则继续调用深度优先遍历函数(可向下访问就向下访问)
    • 需要递归工作栈(保存最近访问结点,以方便回退)
      • 当不能继续向下访问时,依次回退到最近访问的顶点;
      • 若它还有邻接顶点未被访问过,则从该点开始深度优先遍历
    • 标记哪些顶点被访问过(防止重复访问
  • 性能分析:

    image-20201017164819175

  • 图的遍历与图的连通性image-20201017164918076

//DFS递归算法
bool visited[MaxVertexNum];//访问标记数组
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])//对每个连通分量调用一次BFS
            DFS(G,i);//vi未访问过,从vi开始BFS
}

//深度优先遍历
void DFS(Graph G,int v){
    
    //从顶点v出发,深度优先遍历图G
    visit(v);//访问初始顶点v
    visited[v] = true;//对v做已访问标记
    for (w=FistNeighbor(G.v);w;w=NextNeighbor(G,v,w))//找邻接自v的所有结点 
        if (!visited[w]){
    
    //顶点w为顶点v未被访问过的邻接点
            DFS(G,w);//递归调用DFS再访问顶点并标记顶点
}

//DFS非递归算法
//非递归算法使用了栈,使得遍历的方式从右端到左端进行(顶点不能再往深处时弹出并访问)
//此时visited数组表示顶点是否已经入栈,而不需要标记是否已经访问过(未被入栈肯定未访问)
void DFS_Non_RC(Graph G,int v){
    
    
    //从顶点v出发开始进行深度优先搜索,一次遍历一个连通分量的所有顶点
    InitStack(S);//初始化栈S
    Push(S,v);//v入栈
    visited[v] = true;//对v做已入栈标记
    while(!StackEmpty(S)){
    
    
        k=Pop(S);//栈中退出一个顶点
        visit[k];//先访问(相当于其上结点的最右端结点),再将其子节点入栈
        for (w=FistNeighbor(G.v);w;w=NextNeighbor(G,v,w))//找邻接自v的所有结点 
        	if (!visited[w]){
    
    //顶点w为顶点v未被入栈的邻接点
                Push(S,w);
                visited[v] = true;//对v做已入栈标记
   			 }//if
    }//while
}//DFS_Non_RC

6.4 图的应用

6.4.1 最小生成树(最小代价树)

  • 最小生成树不唯一,但最小生成树各边的权值之和唯一且最小

image-20201017190049300

6.4.2 最短路径问题

image-20201017190209898

6.4.2.1 Dijkstra算法(画出求解过程表)

已确认找到最短路径的顶点,其最短路径不能修改(故不适用于负权图)

  1. 初始化(**顶点到顶点间有弧(非存在路径)**则将dist[i]初始化弧上的权值)
  2. 循环n-1轮(每次确认一个Vi的最短路径,然后更新邻接自Vi的顶点dist

image-20201017191329293

6.4.2.2 Floyd算法(画出矩阵A)
  • 最短路径

image-20201017191907873

image-20201017192134426

  • 完整路径:需要通过path矩阵递归找到完整路径(不断判断两顶点间是否还有其他中转顶点

image-20201017192414359

6.4.3 拓扑排序

6.4.3.1 拓扑排序的实现

image-20201017194110782

6.4.3.2 拓扑排序算法(邻接表)
  • 准备工作
    • idegree[]:记录当前顶点入度
    • print[]:记录拓扑序列
    • S:保存度为0的顶点(也可用队列(入度为0的顶点先干谁无所谓))
  • 核心算法(每次选择一个没有前驱顶点输出
    • 每次弹出栈中的一个元素(栈中元素入度均为0
    • 逻辑上删除该顶点和所有以它为起点的有向边(其邻接到的顶点的入度减1);
    • 若其邻接到的顶点减1后,入度为0,则入栈;
bool TopologicalSort(Graph G){
    
    
    InitStack(S);//初始化栈,存储入度为0的顶点
    for(int i = 0;i<G.vexnum;i++)
        if (indegree[i] == 0)//默认顶点的入度已经初始化
            Push(S,i);//将所有入度为01的顶点进栈
    int count = 0;//记录当前已经输出的顶点数(0 ~ n-1)
    while(! StackEmpty(S)){
    
    //栈不空,则存在入读为0的顶点
        Pop(S,i);//栈顶元素出栈(完成该事件)
        print[count++] = i;//输出顶点i
        for (p=G.vertices[i].firstarc;p;p=p->nextarc){
    
    
            //将所有i指向的顶点的入度减1,并且将入度减为0的顶点入栈S
            v = p->adjvex;//顶点Vi所邻接到的第v号顶点
            if (!(--indegree[v]))
                Push(S,v);//将V号顶点入栈
        }        
    }//while
    if (count<G.vexnum)
        return false;//排序失败,有向图中有回路
    else
        return true;//拓扑排序成功
}

6.4.3 关键路径

6.4.3.1 基本概念
  • 关键路径:时间余量为0的活动的边所组成的路径

6.4.3.2 算法关键

初始值设定:ve(源点)=0、vl(汇点)=ve(汇点)

  • 求ve():先将各事件拓扑排序(确定事件发生的先后),再依次求其余顶点的最早发生时间
  • 求vl():先将各事件逆拓扑排序(拓扑排序逆序即可)再依次求其余顶点的最迟发生时间

image-20201017200649416

猜你喜欢

转载自blog.csdn.net/jimmy33777/article/details/110334500