zoukankan      html  css  js  c++  java
  • 『嗨威说』数据结构

     本文主要内容:(与树类似)

      一、图的概念

      二、图的重中之重——两种重要存储结构

      三、树的升级拓展应用:最小生成树

      四、本节应用习题

      五、个人反思与未来计划

    一、图的基本概念:

      (1)图的定义:

        图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

        注意:线性表中可以没有元素,称为空表。树中可以没有结点,叫做空树。但是在图中不允许没有顶点,可以没有边

      (2)图的基本术语:

      · 无向边:若顶点Vi和Vj之间的边没有方向,称这条边为无向边,用 (Vi,Vj)来表示。

      · 无向图:图中任意两个顶点的边都是无向边。

      · 有向边:若从顶点Vi到Vj的边有方向,称这条边为有向边,也称为弧,用 <Vi, Vj> 来表示,其中 Vi 称为弧尾,Vj 称为弧头。

      · 有向图:图中任意两个顶点的边都是有向边。

      · 简单图:不存在自环(顶点到其自身的边)和重边(完全相同的边)的图。

      · 稀疏图;有很少条边或弧的图称为稀疏图,反之称为稠密图。

      · 权:表示从图中一个顶点到另一个顶点的距离或耗费。

       · 网:带有权重的图。

      ·  度:与特定顶点相连接的边数。

      · 出度、入度:有向图中的概念,出度表示以此顶点为起点的边的数目,入度表示以此顶点为终点的边的数目。

      · 连通图:任意两个顶点都相互连通的图。

      · 极大连通子图:包含竟可能多的顶点(必须是连通的),即找不到另外一个顶点,使得此顶点能够连接到此极大连通子图的任意一个顶点。

      · 连通分量:极大连通子图的数量。

      · 强连通图:此为有向图的概念,表示任意两个顶点a,b,使得a能够连接到b,b也能连接到a 的图。

      · 连通图的生成树:一个极小连通子图,它含有原图中全部定点,但只有足以构成一棵树的 n-1 条变,这样的连通子图称为连通图的生成树。

      · 最小生成树:此生成树的边的权重之和是所有生成树中最小的。

      (3)图的两种遍历方式:

          · 深度优先遍历:(DFS常利用递归思想

            首先从图中某个顶点v0出发,访问此顶点,然后依次从v相邻的顶点出发深度优先遍历,直至图中所有与v路径相通的顶点都被访问了;若此时尚有顶点未被访问,则从中选一个顶点作为起始点,重复上述过程,直到所有的顶点都被访问。

          · 广度优先遍历:(BFS常利用队列+队列不为空循环思想

            首先,从图的某个顶点v0出发,访问了v0之后,依次访问与v0相邻的未被访问的顶点,然后分别从这些顶点出发,广度优先遍历,直至所有的顶点都被访问完。

    二、图的重中之重 —— 已学习的两种存储结构:

      (1)邻接矩阵:

        图的邻接矩阵的存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称邻接矩阵)存储图中的边或弧的信息。

        优缺点:

          · 优点:结构简单,操作方便

          · 缺点:对于稀疏图,这种实现方式将浪费大量的空间。

      (2)邻接表:

        邻接表是一种将数组与链表相结合的存储方法。其具体实现为:将图中顶点用一个一维数组存储,每个顶点Vi的所有邻接点用一个单链表来存储。这种方式和树结构中孩子表示法一样。

        对于有向图其邻接表结构如下:

        优缺点:

          · 优点:本算法的时间复杂度为 O(N + E),其中N、E分别为顶点数和边数,邻接表实现比较适合表示稀疏图。

          · 缺点:操作繁琐

        注:还有一种十字链表存储结构,暂未教学,等学习熟练之后再单独拿来写博

    三、图的升级拓展之一:最小生成树 (贪心算法):

      (1)最小生成树的概念:

          图的生成树是它的一棵含有所有顶点的无环连通子图。一棵加权图的最小生成树(MST)是它的一棵权值(所有边的权值之和)最小的生成树。

      (2)最小生成树的两种实现算法:

          · 普里姆算法(Prim)

            实现过程:

            从顶点0开始,首先将顶点0加入到树中(标记),顶点0和其它点的横切边(这里即为顶点0的邻接边)加入优先队列,将权值最小的横切边出队,加入生成树中。此时相当于也向树中添加了一个顶点2,接着将集合(顶点1,2组成)和另一个集合(除1,2的顶点组成)间的横切边加入到优先队列中,如此这般,直到队列为空。

          

          · 克鲁斯卡尔算法(Kruskal)

            实现过程:

            按照边的权重顺序来生成最小生成树,首先将图中所有边加入优先队列,将权重最小的边出队加入最小生成树,保证加入的边不与已经加入的边形成环,直到树中有V-1到边为止。

            注:具体实现代码暂未学习,仅了解实现过程,后续增加。

    四、本章习题练习:

        拯救007:(DFS)

    在老电影“007之生死关头”(Live and Let Die)中有一个情节,007被毒贩抓到一个鳄鱼池中心的小岛上,他用了一种极为大胆的方法逃脱 —— 直接踩着池子里一系列鳄鱼的大脑袋跳上岸去!(据说当年替身演员被最后一条鳄鱼咬住了脚,幸好穿的是特别加厚的靴子才逃过一劫。)
    
    设鳄鱼池是长宽为100米的方形,中心坐标为 (0, 0),且东北角坐标为 (50, 50)。池心岛是以 (0, 0) 为圆心、直径15米的圆。给定池中分布的鳄鱼的坐标、以及007一次能跳跃的最大距离,你需要告诉他是否有可能逃出生天。
    
    输入格式:
    首先第一行给出两个正整数:鳄鱼数量 N(≤100)和007一次能跳跃的最大距离 D。随后 N 行,每行给出一条鳄鱼的 (x,y) 坐标。注意:不会有两条鳄鱼待在同一个点上。
    
    输出格式:
    如果007有可能逃脱,就在一行中输出"Yes",否则输出"No"。
    
    输入样例 114 20
    25 -15
    -25 28
    8 49
    29 15
    -35 -2
    5 28
    27 -29
    -8 -28
    -20 -35
    -25 -20
    -13 29
    -30 15
    -35 40
    12 12
    输出样例 1:
    Yes
    输入样例 24 13
    -12 12
    12 12
    -12 -12
    12 -12
    输出样例 2:
    No
    【题目】拯救007

        题目有一点点小坑,刚开始还过了五个测试点,只差一个测试点,但还好一两小时肝一下debug出来了,下面简单说一下题意:

    有一个人在一个圆内,半径为7.5(这是个坑,不是15噢)单位,然后可以从圆内往外跳,但只有固定的几个点可以跳,而且能不能跳过去看这个人的最大跳跃距离。只要能跳出100*100的大矩形则说明可以逃生输出Yes,否则No

        题目大意就是这样啦,简单拟定一下思路:

    1、对于每一个点,用一个struct去实现其存储结构,存储其x、y坐标、能够跳到的点的编号、能够跳到点的数目、能否成为起跳点、能否成为终止点。

    2、输入完x、y坐标点之后,扫一遍全点,链接能跳的点,并且判断能否成为起跳点,能否成为终止点。

    3、特判一下当最大可跳距离大于42.5时,可以不用跳到鳄鱼,可以直接跳出矩形。

    4、实现DFS深搜递归,打上vis数组。

        现在我们来动手试一下吧~ 

        首先,头文件,因为是ACMer选手,习惯了C语言的写法,各位小伙伴不必在意噢,只需要把scanf输入的东西换成cin,printf输出的东西换成cout就搞定了

    #include<stdio.h>
    #include<math.h>
    #include<string.h>
    #define MAX 999

        

        第二步,开始建立图结点的结构体:按上面我思路说的,存储其x、y坐标、能够跳到的点的编号、能够跳到点的数目、能否成为起跳点、能否成为终止点。

    typedef struct ArcNode{
        int x;
        int y;
        int num;
        int Next[MAX];
        bool out = false;
        bool in = false;
    }ArcNode;

        

        第三步,基本变量申明以及函数原型申明:

    int N,maxJump,ans,vis[MAX];
    void DFS(ArcNode *p,int i);
    void buildGraph(ArcNode *&p);
    void buildArc(ArcNode *&p);
    void searchInOut(ArcNode *&p);

        

        第四步,对Main主函数进行模块化函数构建:

    int main()
    {
        ArcNode *G;        //建立结点
        buildGraph(G);      //导入结点内容
        if(maxJump >= 42.5)   //特判
        {
            printf("Yes
    ");
            return 0;
        }
        buildArc(G);        //扫一遍所有点,构造边,使能互相跳的点结合起来
        searchInOut(G);      //扫一遍所有点,判断是否能够成为起跳点和终止点
        ans = -1;          //答案初始化
        for(int i = 1;i<=N;i++)
            if(G[i].in)      //如果这个点是起跳点
            {
                memset(vis,0,sizeof(vis));  //重置vis数组
                DFS(G,i);            //开始深搜
            }    
        if(ans == -1) printf("No
    ");    //打印答案
        else printf("Yes
    ");
        return 0;
    }

        第五步,导入结点,比较简单:一个输入导入就可以了。

    void buildGraph(ArcNode *&p)
    {
        scanf("%d %d",&N,&maxJump);
        p = new ArcNode[N+1];
        for(int i = 1;i<=N;i++)
            scanf("%d %d",&p[i].x,&p[i].y);
    }

        第六步,链接各点,实现保存可以互跳的点,这里需要两层for循环的遍历,可能效率优点不高,但简单粗暴

    void buildArc(ArcNode *&p)
    {
        for(int i = 1;i<=N;i++)
        {
            for(int u = 1;u<=N;u++)
            {
                if(u == i) continue;
                if((maxJump * maxJump) >= (p[i].x-p[u].x)*(p[i].x-p[u].x)+(p[i].y-p[u].y)*(p[i].y-p[u].y)) //这里除了用sqrt还可以直接用平方比较,误差会小一点
                {
                    p[i].num++;
                    p[i].Next[p[i].num] = u; //将可互跳的边导入
                }
            }
        }
    }

        第七步,搜索起始点和终止点,也是比较简单的,一个距离公式就好了。

    void searchInOut(ArcNode *&p)
    {
        for(int i = 1;i<=N;i++)
        {
            if((maxJump + 7.5)*(maxJump + 7.5) >= p[i].x*p[i].x+p[i].y*p[i].y)
                p[i].in = true;
            if(maxJump >= 50 - abs(p[i].x) || maxJump >= 50 - abs(p[i].y))
                p[i].out = true;
        }
    }

        第八步,嵌套一下DFS递归就好了,注意递归的终止条件。

    void DFS(ArcNode *p,int i)
    {
        vis[i] = 1;
        if(p[i].out == true) //如果搜到了这个点是终止点,说明这个人可以跳出去,那么就给答案变量做个标记。
            ans = 1;
        if(p[i].num == 0)  //递归终止条件
            return;
        for(int u = 1; u<=p[i].num; u++)
            if(!vis[p[i].Next[u]])//防止递归循环需要一个标记数组
                DFS(p,p[i].Next[u]);
    }

        

        这样整个程序就完成啦~ 完整代码贴上:

     1 #include<stdio.h>
     2 #include<math.h>
     3 #include<string.h>
     4 #define MAX 999
     5 
     6 typedef struct ArcNode{
     7     int x;
     8     int y;
     9     int num;
    10     int Next[MAX];
    11     bool out = false;
    12     bool in = false;
    13 }ArcNode;
    14 
    15 int N,maxJump,ans,vis[MAX];
    16 void DFS(ArcNode *p,int i);
    17 void buildGraph(ArcNode *&p);
    18 void buildArc(ArcNode *&p);
    19 void searchInOut(ArcNode *&p);
    20 int main()
    21 {
    22     ArcNode *G;
    23     buildGraph(G);
    24     if(maxJump >= 42.5)
    25     {
    26         printf("Yes
    ");
    27         return 0;
    28     }
    29     buildArc(G);
    30     searchInOut(G);
    31     ans = -1;
    32     for(int i = 1;i<=N;i++)
    33         if(G[i].in)
    34         {
    35             memset(vis,0,sizeof(vis));
    36             DFS(G,i);
    37         }    
    38     if(ans == -1) printf("No
    ");
    39     else printf("Yes
    ");
    40     return 0;
    41 }
    42 void buildGraph(ArcNode *&p)
    43 {
    44     scanf("%d %d",&N,&maxJump);
    45     p = new ArcNode[N+1];
    46     for(int i = 1;i<=N;i++)
    47         scanf("%d %d",&p[i].x,&p[i].y);
    48 }
    49 void buildArc(ArcNode *&p)
    50 {
    51     for(int i = 1;i<=N;i++)
    52     {
    53         for(int u = 1;u<=N;u++)
    54         {
    55             if(u == i) continue;
    56             if((maxJump * maxJump) >= (p[i].x-p[u].x)*(p[i].x-p[u].x)+(p[i].y-p[u].y)*(p[i].y-p[u].y))
    57             {
    58                 p[i].num++;
    59                 p[i].Next[p[i].num] = u;
    60             }
    61         }
    62     }
    63 }
    64 void searchInOut(ArcNode *&p)
    65 {
    66     for(int i = 1;i<=N;i++)
    67     {
    68         if((maxJump + 7.5)*(maxJump + 7.5) >= p[i].x*p[i].x+p[i].y*p[i].y)
    69             p[i].in = true;
    70         if(maxJump >= 50 - abs(p[i].x) || maxJump >= 50 - abs(p[i].y))
    71             p[i].out = true;
    72     }
    73 }
    74 void DFS(ArcNode *p,int i)
    75 {
    76     vis[i] = 1;
    77     if(p[i].out == true)
    78         ans = 1;
    79     if(p[i].num == 0)
    80         return;
    81     for(int u = 1; u<=p[i].num; u++)
    82         if(!vis[p[i].Next[u]])
    83             DFS(p,p[i].Next[u]);
    84 }
    【完整代码展示】拯救007 

        

        下面将具体的导入结点和DFS搜索过程打印出来,大家可以看看他的步骤流程:

    五、个人反思及未来计划:

      有一天老师跟我说了一句话,让我一直留着比较深的印象:

      

      是啊,从开学到现在一直有不少人告诉我,大家能够专心干一件事,你真的就是很棒的人了。

      而我,在上学期加了五个社团,Quanta、Eddy、数挖、ACM、招协,拖着班长,拖着五个兼职,我也不知道我怎么活下来的,大概是想把自己忙成狗 吧,把一些伤心的事情忘得一干二净。

      下学期收了收心,退了几个社团,仅留ACM和数挖,班长的事情随着英剧的结束事情也少了不少,兼职也拖剩了一个,慢慢收心,大概上学期各个方面的接触,也让我逐步摸清了未来的发展方向。

      就这样吧,努力计划做好每天该干的事情,不负身边人对我的期望,好好对待每一个人,去努力的带给他们快乐,带给自己快乐,带来更多的动力。

      大二,大概有了方向了,嗯,努力干下去。

      (1)图比较抽象的数据结构上基础有些不牢,进一步学习普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法等,特别是碰到链式存储的指针使用的时候,需要找时间给自己多加强这方面的学习。

        (2)ACM集训队每天几道题,每周写一篇博客。

      (3)完成论文标解并准备好论文演讲『Improving patch-based scene text script identification with ensembles of conjoined networks』

      (4)完成论文标解并准备好论文演讲『汉老双语命名实体识别及对齐方法研究_韩锐』

  • 相关阅读:
    Maven 项目管理工具基础入门系列(二)
    Python OJ 从入门到入门基础练习 10 题
    Maven 项目管理工具基础知识系列(一)
    Markdown 编辑器使用指南
    解决:GitHub 远程端添加了 README.md 文件后,本地 push 代码时出现错误
    CentOS7.4搭建基于用户认证的MongoDB4.0三节点副本集集群详细文档
    Redhat 6.7 x64升级SSH到OpenSSH_7.4p1完整文档
    RHEL6.7 x64双节点安装Oracle 11g R2 RAC
    局域网下通过代理实现服务器的互联网访问
    RedHat 6.7 Enterprise x64环境下使用RHCS部署Oracle 11g R2双机双实例HA
  • 原文地址:https://www.cnblogs.com/WinniyGD/p/10889318.html
Copyright © 2011-2022 走看看