DS博客作业04--图

0.PTA得分截图

1.本周学习总结(0-5分)

1.1 总结图内容

  • 定义:顶点集V和顶点间的关系:边集合E组成的数据结构

  • 基本术语

    ·无向图:没方向边(如图)

    ·有向图:由顶点集和弧集(有方向的边)构成的图(如图)

    ·完全图

    ·完全无向图:每两个顶点之间都存在这一条边

    ·完全有向图:每两个顶点之间都存在着方向相反的两条边

    ·稠密图:当一个图接近完全图时

    ·稀疏图:当一个图含有较少边数是

    ·度,入度,出度

    度:无向图以顶点i为端点的边数

    入度:以顶点i为终点的入边的数目

    出度:以顶点i为始点的出边的数目

    ·路径长度:一条路径上经过的边的数目

    ·简单路径:一条路径上除开始点和结束点可以相同外,其余顶点均不相同

    ·回路,环:一条路径上的开始点与结束点为同一个顶点

    ·简单回路,简单环:开始点与结束点相同的简单路径

    ·连通,连通图和连通分量

    ·连通图:图中任意两个顶点都相通

    ·连通分量:无向图G中的极大连通子图

    注:任何连通图的连通分量只有一个,为连通图本身;而非连通图由多个连通分量。

    ·权和网

    ·图中每一条边都可以附有一个对应的数值,即为权

    ·网:也称为带权图,即边上带有权的图。

  • 图的存储结构:

    邻接矩阵

    适用于稠密图

    存储类型定义如下:

    typedef struct 
    {    int no;	
         InfoType info;	
    } VertexType;
    typedef struct  		
    {    int edges[MAXV][MAXV]; 	
         int n,e;  		
         VertexType vexs[MAXV];	
    }  MatGraph;
     MatGraph g;
    
    

注:图的邻接矩阵表示是唯一的

邻接表

对于图中的每一个顶点i建立一个单链表,将顶点i所有的邻接点都连起来

是一种顺序分配和链式分配相结合的方法

存储类型定义如下:

typedef struct Vnode
{    Vertex data;		
     ArcNode *firstarc;		
}  VNode;
//表明邻接表头结点类型
typedef struct ANode
{     int adjvex;		
      struct ANode *nextarc;
      InfoType info;	
}  ArcNode;
//表明边结点类型
typedef struct 
{     VNode adjlist[MAXV] ;	
       int n,e;		
} AdjGraph;
//声明图邻接表类型
AdjGraph *G;//声明一个邻接表存储的图G

  • 图的遍历

  • 定义:从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次

  • 深度优先遍历

    ·实质:对每个顶点查找邻接点的过程

    ·伪代码:

    访问v节点
     遍历v的邻接点w
         若w未被访问,递归访问w节点
    
  • 广度优先遍历

    ·实质:利用队列,类似于层次遍历进行遍历

    ·伪代码

    建一个访问队列q
    访问v节点,加入队列q
    while(队列不空)
        取队头元素w
        遍历w的邻接表    
             取邻接点j
             若j未被访问,则加入队列q,并访问j。
    end while
    
  • 判断图是否连通

    ·利用深度优先遍历

    ·思路:给定一个visited[]数组并将其中所有元素初始化为0,从0开始进行DFS,结束遍历后若所有顶点i的visited[i]都为1 则说明图连通

    ·伪代码

    bool Connect(AdjGraph *G)
    {
        定义i;
        定义flag为true;
        for i=0 to G.n
            visited[i]=0;//赋初值
        调用DFS算法从0进行遍历;
        for i=0 to G.n
            if 顶点没有被访问到
                flag=false;
        return true;  
    }
    
  • 查找图简单路径

    ·简单路径:即路径上的顶点不重复

    ·利用深度优先遍历

    ·思路:在DFS基础上加上v和has两个形参,has表示u到v是否有路径,若有,has赋值为true进行返回。

    ·伪代码:

    void ExistPath(AdjGraph *G,int u,int v,bool &has)
    {
        定义结点p;
        定义w;
        visited[u]=1;//置u已访问标记
        if u等于v  //找到一条路径
            has=true;
            return;
        DFS遍历;
    }
    
  • 找最短路径

    ·利用深度优先遍历

    ·思路:给定一个数组path[]存放路径,给定形参d了解遍历到了哪里即递归深度。利用DFS从顶点u遍历到顶点v,结束遍历后输出path。

    ·伪代码:

    void FindaPath(AdjGraph *G,int u,int v,int path[],int d)
    {
        定义结点p;
        定义w,i;
        d++;
        path[u]=1;//置u已访问标记
        if u等于v  //找到一条路径
            输出并返回;
        DFS遍历;
    }
    
  • 生成树和最小生成树

  • 生成树

  • 定义:一个连通图的生成树是一个极小连通子图,它含有图中全部n个顶点和构成一棵树的(n-1)条边,不能回路。

  • 广度优先生成树:由BFS遍历所得的生成树

  • 深度优先生成树:由DFS遍历所得的生成树

  • 最小生成树

  • 定义:图的所有生成树中具有边上的权值之和最小的数

  • 普里姆(Prim)算法

    ·一种增量算法,一步一步的选择最小边,并在U集合中添加相应的顶点。即利用贪心算法的原理,局部最优经过调整后成为全局最优,这种算法不需要回溯。

    ·图存储结构:邻接矩阵(需要频繁的取一条条的边)

    ·新建两个数组:closest[]和lowcost[],用于记录V-U中顶点j到U顶点的最小边(为节省空间)

    ·时间复杂度:O(n^2) 空间复杂度:O(n^2)

    ·伪代码:

    初始化lowcost,closest数组
    for(v=1;v<=n;v++)
        遍历lowcost数组     //选最小边
               若lowcost[i]!=0,找最小边
               找最小边对应邻接点k
        最小边lowcost[k]=0;
        输出边(closest[k],k);
        遍历lowcost数组     //修正lowcost
            若lowcost[i]!=0 && edges[i][k]<lowcost[k]
                    修正lowcost[k]=edges[i][k]
                     修正closest[j]=k;
    end
    
  • 克鲁斯卡尔(Kruskal)算法

    ·一种按权值的递增次序选择合适的边来构造最小生成树的方法

    ·图存储结构:邻接矩阵(频繁的取一条条边)

    ·新建一个数组:vest[]用于记录一个顶点i所在的连通分量编号

    ·时间复杂度:O(eloge) 空间复杂度:O(n)

    ·伪代码:

    void Kruskal(MatGraph g)
    {
        定义 i,j,u1,v1,sn1,sn2,k;
        定义辅助数组vest[];
        定义边结构体数组 E[];
        for i=0 to g.n
            for j=0 to g.n
                对图中的边赋值;//起始顶点,终止顶点,权值
        采用直接插入排序对E按权值排序;
        初始化vest[];
        k=1;//表示当前构造生成树的第几条边
        j=0;//E边的下标
        while k<g.n
        {
            取一条边的两个顶点赋予u1,v1;
            得到两个顶点所属的集合编号于sn1,sn2;
            if sn1不等于sn2
            {
                生成边数k++;
                for i=0 to g.n
                    集合编号为sn2的改为sn1;
            }
            j++;
        }
    }
    

    ·改进克鲁斯卡尔(Kruskal)算法

    ·一:将边集排序改为堆排序 二:采用并查集进行连通分量合并

    ·改进后时间复杂度:O(log2 n)

    ·改进后的伪代码:

    void Kruskal(MatGraph g)
    {
        定义 i,j,u1,v1,sn1,sn2,k;
        定义并查集 t[];
        定义边结构体数组 E[];
       for i=0 to g.n
            for j=0 to g.n
                对图中的边赋值;//起始顶点,终止顶点,权值
        采用堆排序对E数组按权值递增排序;
        初始化并查集;
        k=1;//表示当前构造生成树的第几条边
        j=0;//E边的下标
        while k<g.n
        {
            取一条边的两个顶点赋予u1,v1;
            得到两个顶点所属的集合编号于sn1,sn2;
            if sn1不等于sn2
            {
                生成边数k++;
                将u1和v1两个顶点合并;
            }
            j++;
        }
    }
    
  • 普里姆(Prim)算法&克鲁斯卡尔(Kruskal)算法

    ·当一个图有多个最小生成树时,两个算法的结果未必是相同的。

    ·Prim算法适用于稠密图,Kruskal算法适用于稀疏图

  • 应用

  • 公路村村通

    ·伪代码:

    nt prim(MG g, int& count)
    {
        定义数组lowcost[];
        定义数组closest[];
        定义总费用sumCost;
        for i=0 to g.n//赋初值
            让lowcost[i]=矩阵中第一行的权值//从1开始生成
            closest[i]=1;
        end for
        for i=1 to g.n
            min赋值极致大
            for j=1 to g.n
                选出最小的成本赋值至min;
            将min累加至sumCount
            改变最小成本所在下标在lowcost[]中的值=0
            for j=1 to g.n
                将lowcost[]和closest[]的值进行调整修改;
        end for
        return sumCost
    }
    
  • 最短路径

  • 从一个顶点到其余各顶点的最短路径

  • Dijkstra算法

    ·主要思想:

·图存储结构:邻接矩阵(需要频繁地取一条条边及其权值)

·数组dist[]:存放最短路径长度,数组path[]:最短路径序列的前一顶点的序号

·特点

1、一个顶点一旦加入中,最短路径长度不再改变

2、不适合含有负权值的带权图求单源最短路径

·伪代码:

void Dijkstra(MatGraph g,int v)
{
    初始化dist数组、path数组、s数组;
    遍历图中所有节点;
    for i=0 to g.n
        if s[i]!=0
             则dist数组找最短路径,顶点为u;
    s[u]=1;  //加入集合S,顶点已选
    for i=0 to g.n
         if s[i]!=0 && dist[i]>dist[u]+g.edges[u][i]
             则修正dist[i]= dist[i]>dist[u]+g.edges[u][i];
             path[i]=u;      
}
  • 每对顶点之间的最短路径

  • 每次以一个顶点为源点,重复执行Dijkstra算法n次

  • Floyd算法

    ·主要思想:递推产生一个矩阵序列A0,A1……,Ak[i][j]代表i->j的路径上所经过的顶点编号不大于k的最短路径长度。

·二维数组A:存储各顶点最短路径长度 , 二维数组path[]:存储最短路径

·伪代码:

void Floyd(MatGraph g)
{
    定义二维数组A[][],path[][];
    定义i,j,k;
    for i=0 to g.n
        for j=0 to g.n
            A[i][j]赋值;
            if i到j有边时
                path[i][j]=i;
            else //若i到j无边
                path[i][j]=-1;
    for k=0 to g.n
        for i=0 to g.n
            for j=0 to g.n
                if A[i][j]>A[i][k]+A[k][j]
                    修改最短路径长度和最短路径;
}
  • 应用

  • 旅游规划

    ·主要伪代码:

    void Dijkstra(MG g, int departure, int destination)
    {
        定义总路径长度,总收费金额;
        定义结构体数组d;
        定义数组path[],s[];
        for i=0 to g.n
            对结构体数组d进行赋值;
            对path[],s[]数组赋初值;
        end for 
        将出发顶点放入集合中;
        for i=1 to g.n
            min值赋为极大;
            for j=0 to g.n
                比较获取长度最小的下标值k,并改变min值
            end for
        end for
        将k顶点放入集合中;
        for j=0 to g.n
            if 顶点不在集合中 then
                if 路径长度相加是小于原本的路径长度 then
                    修改路径长度以及收费金额;
                if 路径长度相等
                    只需修改收费金额;
        end for
        输出 目的地的路径长度和收费金额;
    }
    
  • 拓扑排序

    ·定义:在一个有向无环图中找一个拓扑序列的过程

    ·图存储结构:邻接表

    ·在定义邻接表结构体时可以加入顶点入度情况,从而遍历邻接表时可以计算每个顶点的入度

    ·多个入度为0的结点,入栈,之后进行出栈访问

    ·选择一个拓扑结点后,将其入度-1就可以实现连接边删除

    ·伪代码:

    void TopSort(AdjGraph *G)
    {
        遍历邻接表
          计算每个顶点的入度,存入头结点count成员;
        遍历图顶点
           if 顶点入度为0
               入栈st;
        while(栈不空)
            出栈节点v,访问;
            遍历v的所有邻接点
            {
                所有邻接点的入度-1  
                if 邻接点入度为0
                    入栈st;
            }
    }
    
  • 关键路径

    ·定义:在AOE网中,从源点到汇点的所有路径中具有最大路径长度的路径

    ·事件的最早开始时间

·事件的最迟开始时间

1.2.谈谈你对图的认识及学习体会。

图的认识:
这一章的内容
1、对图相关概念的一个理解,图,有向图/无向图,度/入度/出度,完全图,子图,连通图……
2、了解图的存储结构,邻接表/邻接矩阵
3、图的基本运算,创建/销毁/输出……
4、图的遍历,DFS/BFS
5、生成树与最小生成树,Prim算法/Kruskal算法
6、最短路径,Dijkstra算法/Floyd算法
7、拓扑排序
8、关键路径
学习体会:
总结下来,这一章有四个算法但在学习的时候我总觉得有十个算法围绕着我。。。有点难...有些东西是通过我这次的博客总结后重新认识和了解的。这一章节的pta,有一丢丢困难,困难也是疫情在家上网课懈怠了的原因。这一章节需要了解算法的内容需要,看到题目的时候才能合理的运用到他们身上,不然可能在创建图时是要邻接表还是邻接矩阵都在发愁……果然学习内容是需要总结,总结下来,头脑里面就会有一个框架也就不会那么乱。

2.阅读代码(0--5分)

2.1 判断二分图

题目:

代码:

2.1.1 设计思路


时间复杂度:O(n^3)
空间复杂度:O(n^2)

2.1.2 伪代码

枚举color中元素 unknown 0,white 1,black 2;
bool isBipartite(int **graph,int graphSize,int *graphColSize)
{
    为pVisited申请空间;
    初始化pVisited中元素都为0;
    为pQueue申请空间;
    定义 front,rear,curr,cow,col,neighborColor;
    for row=0 to graphSize-1
    {
        if pVisited[row]!=0 //还未访问过
            continue;
        把row放入队列pQueue中;
        将pVisited[row]数值更改为1;
        while 队列不为空
        {
            将队头元素赋予curr;
            若pVisited[curr]为白,则改成黑色,若为黑,改为白色并赋予neighborColor;
            for col=0 to graphColSize[curr]-1 
                if graph[curr][col]未被访问
                    将neighborColor赋予pVisited[graph[curr][col]];
                    graph[curr][col]入队;
                if pVisited[graph[curr][col]] 与 neighborColor不同
                    return false;
        }
    }
}

2.1.3分析该题目解题优势及难点。

解题优势:剖析本质为图染色问题,分别利用队列完成深度优先搜索,存储下一个要访问结点的顺序,先对每个未着色邻接点着色放入队列中。使用哈希数组记录每个结点的颜色,在搜索结点时如果存在当前点和邻接点颜色相反,则着色失败。
难点:在于对这道题目的理解,如果没有较好的理解,也不会想到用图染色问题进行解决。还有是对哈希数组的利用,也很独到。

2.2 阈值距离内邻居最少的城市

题目:

代码:

2.2.1 设计思路

2.2.2 伪代码

int findTheCity(int n, vector <vector<int>> &edges, int distanceThreshold)
{
    定义二维数组D,并初始化为INT_MAX(无穷);
    根据题目所给edges[][]初始化D[][];
    for k=0 to n
    {
        for i=0 to n
        {
            for j=0 to n
            {
                if i等于j 或 矩阵中数值为INT_MAX
                    continue;//防止两个INT_MAX相加溢出;
                D[i][j]等于(D[i][k]+D[k][j],D[i][j])中较小的那一个;
            }
        }
    }
    for i=0 to n
    {
        for j=0 to n
        {
            if i不等于j且路程小于distanceThreshold
                cnt++;
        }
        if cnt小于等于minNum
            minNum=cnt;
            编号为i;
    }
    return 编号i;
}

2.2.3 运行结果

2.2.4分析该题目解题优势及难点。

解题优势:利用Floyd算法进行解题,最后再从小到大进行判断邻居最少的城市。就可以取较大编号邻居最少的城市了。
难点:题目解读很重要,该代码利用邻接矩阵存图减少算法难度,利用Floyd算法计算便于查找邻居最少城市。

2.3 节点间通路

题目:

代码:

2.3.1 设计思路

2.3.2 伪代码

bool bfs(int n, vector<vector<int>>& edge, int start, int target)
{
    定义队列que;
    定义标记访问数组vis,初始化false;
    start入队;
    更改vis[start]为true;
    while 队列不为空
    {
        取队头元素cur;
        删去队头;
        for i=0 to edge[cur].size()
            if edge[cur][i] 未被访问过
                if edge[cur][i] 为目标终点
                    return true;
                edge[cur][i]入队;
                更改vis[edge[cur][i]] = true;
    }
    return false;
}

2.3.3 运行结果

2.3.4分析该题目解题优势及难点。

解题优势:利用队列,从0的链表访问能到达的节点入队。也利用了数组vis[],对已经入列的节点不再进行处理。
难点:对于这个广度遍历的应用吧,其实这道题DFS和BFS都可以进行解题。

原文地址:https://www.cnblogs.com/lz0149/p/12809084.html