• 第六章总结


    第六章我们学习到的是图,一种比树还要复杂一点的数据结构。

    首先是图的定义: 图G有两个集合V和E组成,记为G=(V,E),其中V是顶点的有穷非空集合,E为V中顶点偶对的有穷集合,

    这些顶点偶对成为边。V(G)和E(G)通常分为表图G的顶点集合和边集合,E(G)可以为空集。若E(G)为空,则图G

    只有顶点而没有边;若边集E(G)为有向边的集合,则称该图有向图;若边集E(G)为无向边的集合,则称该图为无向图。

    然后介绍一下比较重要的图的遍历

    图的遍历

        图的遍历和树的遍历类似,希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫图的遍历。

        对于图的遍历来说,如何避免因回路陷入死循环,就需要科学地设计遍历方案,通过有两种遍历次序方案:深度优先遍历和广度优先遍历。

    1.深度优先遍历

        深度优先遍历,也有称为深度优先搜索,简称DFS。其实,就像是一棵树的前序遍历。

        它从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。  

    邻接矩阵表示图代码:

    void DFS_AM (AMGraph G,int v)
    {//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
       cout<<v; visited[v]=true;//访问第v个顶点,并置访问的标志数组相应的分量值为true
       for(w=0;w<G.vexnum;w++)//依次检查邻接矩阵v所在的行
           if((G.arcs[v][w]!=0)&&(!visited[w])) DFS_AM(G,w);//G.arcs[v][w]表示w是v的邻接点,如果w未访问,则递归调用DFS_AM
       
    
    }

     邻接表表示图代码:

    void DFS(GraphList g, int i)
    {
        EdgeNode *p;
        visited[i] = TRUE;
        cout<<g->adjList[i].data;   //打印顶点,也可以其他操作
        p = g->adjList[i].firstedge;
        while(p)
        {
            if(!visited[p->adjvex])
            {
                DFS(g, p->adjvex);           //对访问的邻接顶点递归调用
            }
            p = p->next;
        }
    }

    2. 广度优先遍历

        广度优先遍历,又称为广度优先搜索,简称BFS。图的广度优先遍历就类似于树的层序遍历了。

    代码如下:

    //广度优先遍历
    void BFS(AGraph* G,int v) {
        ANode *p;
        queue<int> qu;
        vector<int> flag(G->n);
        int w;
        cout<<v<<" ";
        flag[v]=1;
        qu.push(v);
        while(!qu.empty()) {
            w = qu.front();
            qu.pop();
            p = G->adjlist[w]->firstarc;
            while(p) {
                if(!flag[p->adjvex]) {
                    cout<<p->adjvex<<" ";
                    flag[p->adjvex] = 1;
                    qu.push(p->adjvex);
                }
                p = p->nextarc;
            }
        }
        cout<<endl;
    } 

    然后就是图的应用

    1.最小生成树

    a.普里姆算法

    Prim算法
    Prim算法是用来解决最小生成树问题的。
    基本思想:对图G(V,E)设置集合S,存放已经被访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化所有从u能到达的顶点v与集合S之间的最短距离。这样的操作执行n次(n为顶点个数),直到集合S已包含所有顶点。

    核心代码如下:

    void prim(Graph G,int vcount,int father[])
    {
        int i,j,k;
        int lowcost[max_vertexes];
    
    int closeset[max_vertexes],used[max_vertexes];
    
    int min;
        for (i=0;i<vcount;i++)
            {
    
    /* 最短距离初始化为其他节点到1号节点的距离 */
            lowcost[i]=G[0][i];
    
        /* 标记所有节点的依附点皆为默认的1号节点 */
    
    
            closeset[i]=0; 
            used[i]=0;
            father[i]=-1; 
            }
        used[0]=1;  /*第一个节点是在U集合里的*/
    
    /* vcount个节点至少需要vcount-1条边构成最小生成树 */
        for (i=1;i<=vcount-1;i++)
            {
            j=0;
    
        min = infinity;
    
           /* 找满足条件的最小权值边的节点k */
            for (k=1;k<vcount;k++)
    
             /* 边权值较小且不在生成树中 */
                if ((!used[k])&&(lowcost[k]<min)) 
    
              {
    
                  min =  lowcost[k];
    
                  j=k;
    
               }
            father[j]=closeset[j]; 
            used[j]=1;;//把第j个顶点并入了U中
            for (k=1;k<vcount;k++)
    
             /* 发现更小的权值 */
                if (!used[k]&&(G[j][k]<lowcost[k]))
                    { 
    
                      lowcost[k]=G[j][k];/*更新最小权值*/
                      closeset[k]=j;;/*记录新的依附点*/
    
                     }
            }
    }

    b.kruskal算法

    kruskal算法

    基本思想:假设连通网N=(V,{E})。则令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通分量。在E中选择最小代价的边,若该边依附的顶点落在T中不同的连通分量中,则将该边加入到T中,否则舍去此边而选择下一条代价最小的边,依次类推,直到T中所有顶点都在同一连通分量上为止。

    核心代码:

    void MiniSpanTree_Kruskal(MGraph G)
    {
        int i, j, n , m;
        int k = 0;
        int parent[MAXVEX];/* 定义一数组用来判断边与边是否形成环路 */
    
        Edge edges[MAXEDGE];/* 定义边集数组,edge的结构为begin,end,weight,均为整型 */
    
        /* 此处省略将邻接矩阵G转换为边集数组edges并按权由小到大排列的代码*/
        for (i = 0; i < G.numVertexes; i++)
            parent[i] = 0;
    
        cout << "打印最小生成树:" << endl;
        for (i = 0; i < G.numEdges; i++)/* 循环每一条边 */
        {
            n = Find(parent, edges[i].begin);
            m = Find(parent, edges[i].end);
            if (n != m)/* 假如n与m不等,说明此边没有与现有的生成树形成环路 */
            {
                parent[n] = m;/* 将此边的结尾顶点放入下标为起点的parent中。 */
                /* 表示此顶点已经在生成树集合中 */
    
                cout << "(" << edges[i].begin << ", " << edges[i].end << ") "
                     << edges[i].weight << endl;
            }
        }

    本周回顾:

    上周确实非常忙碌,所以pta上作业完成程度只有一半,还没对作业进行整理总结,而且没有对第四第五章进行复习,导致小测成绩不尽人意,而

    学到图之后,要处理的东西也越来越多,更需要花时间去消化和理解,对于算法的理解是很重要的,然后提高将算法转为可运行的代码的能力,这

    个过程我觉得会很辛苦,但是收获也是很多的,所以还是给自己打打气,下周已没有活动,是时候专心去打代码了。

    学习资料推荐:

    帮助理解普里姆算法和克鲁斯卡尔算法的大牛博客:https://www.cnblogs.com/PJQOOO/p/3855017.html

    帮助了解路径规划(最短路径)的博客:https://www.cnblogs.com/zhuweisky/archive/2005/09/29/246677.html

  • 相关阅读:
    Javascript面向对象编程--原型字面量
    Javascript面向对象编程--原型(prototype)
    Javascript面向对象编程--封装
    java word操作
    uniapp获取mac地址,ip地址,验证设备是否合法
    element-ui+vue表单清空的问题
    mysql,oracle查询当天的数据
    vue+element在el-table-column中写v-if
    idea修改页面不用重启项目(转)
    vue+element实现表格v-if判断(转)
  • 原文地址:https://www.cnblogs.com/fengwanthousand/p/10890271.html
Copyright © 2020-2023  润新知