实验——图(拯救007)详细过程

一、实验目的

  1. 熟练掌握图的邻接矩阵和邻接表的存储方式,图论的性质。

  2. 练习DFS和BFS基本算法。

  3. 利用最短路径和最小生成树思想解决图相关的问题。

二、实验内容和要求

问题描述

拯救007:在老电影“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"。

输入样例1

14 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

输入样例2

4 13
-12 12
12 12
-12 -12
12 -12

输出样例2

No

三、算法设计

  1. 主流程设计

int main {
  初始化图;
  输入鳄鱼数量N和007一次能跳跃的最大距离D;
  循环输入每一条鳄鱼的坐标;
  判断007是否能逃出生天;
}
  1. 存储方式分析                                                                                                                                  设计思路:图的存储结构有两种常用方式——邻接表和邻接矩阵,但是对于这道题用这两种方式过于复杂,选择直接运用数组的方式,具体原因如下:                                                          (1)邻接表表示法:对于图中的每个顶点,将所有邻接于该顶点的顶点连成一个单链表,再将所有点的邻接表表头放到一个数组中,就构成了图的邻接表。在邻接表上虽然容易找到任一顶点的第一个邻接点和下一个邻接点,但是存储了许多无用且重复的边,所占的空间较大,对于这道题只用建立一个链表就完成了,而且当每次判断两个结点是否有边或弧相连,即007能否跳跃到正确的结点上时,都要搜索一个或多个链表,会浪费很多时间和空间,所以用这种方式存储处理不好;                                                                                                                               (2)邻接矩阵表示法:邻接矩阵用二维数组表示,代码的实现虽然很简单,但是适合于稠密图。这道题是建立有向图,是一个稀疏图,显然会浪费许多空间,而且有些操作也会经常访问邻接矩阵中0(或∞)代表的无效元素,会浪费许多时间,所以用这种方式存储处理也不好。                                                                                                                                                              (3)用数组最原始的方式存储,可以直接处理,例如007找寻下一个跳跃的结点时,不可能成功的结点遍历计算一遍就可以结束了,不用再存储找寻这个结点的下一个可到达的结点,该方式计算量少,时间和空间运用较少,应优先选择。

  2. DFS函数分析                                                                                                                                    设计思路:通过深度优先搜索函数,遍历每一个结点。 具体实现:通过调用Jump()函数找到007可以成功跳上去的结点,思路是跳到第一个结点上后,以007能跳跃的最大距离为半径,继续寻找下一个结点。实现原理是用数学知识“三条边能否构成直角三角形”判断是否可以跳到下一个结点,找到可跳的点后继续递归调用DFS()函数,标记跳上去过的点(visited[t] = 1),直到可以跳到岸上为止。这里还用到了一个判断能否成功上岸的函数IsSafe(),实现原理只要判断该点坐标距离岸边的距离是否小于007一次能够跳跃的最大距离即可。

int DFS(int t)
{
    /*找到可以跳的点,标记这个点已经访问过*/
    visited[t]=true;
    /*判断是否可以从该点直接跳到岸边*/
    if (IsSafe())
     /*如果判断可以直接跳到岸边了*/
        flag=1;  /*标记flag为1,表示最后可以逃出生天*/
    for (每一个结点)
    {
        /*找到下一个可以跳到的结点*/
        if (!visited[i] && Jump())/*这个结点没有被访问过且通过调用Jump()函数得出可以跳到*/
            /*继续递归调用DFS()函数寻找下一个可以跳的结点*/
            DFS();
    }
    return flag;
}

 

  1. Save007函数分析 设计思路:首先判断以007的最大跳跃距离是否可以直接跳到岸边逃生,如果可以直接输出"Yes",不能的话再开始找逃生路线;找路线时先遍历结点,判断结点是否被访问过,找到第一跳能成功的结点,再开始调用DFS()函数接着往下找可以跳的结点。最后根据做的标记flag的值判断007是否可以逃出生天。

void Save007(N, D) {
    //如果007能一次跳跃到岸边则直接输出Yes
    if (007的最大跳跃距离>=湖心岛到岸边的距离)    
    {
        printf("Yes");
    }
    else
    {
        for (每个结点)
        {
              //visited[i]==1表示该结点已经访问过了
            if (!visited[i] && FirstJump(i))/*如果结点未被访问过且第一跳可以跳成功*/
                /*调用DFS()函数开始找可以跳出去的路线*/
                DFS(i);
        }
        根据标记flag的值判断007是否可以成功逃生
    }
}

 

  1. ADT定义

struct Node {
    int x, y;
}p[100];  //设鳄鱼池是长宽为100米的方形
int visited[101];  //MOOC:可以不用按照邻接矩阵或者邻接表的形式存储该图,计算量少,可直接处理
int flag;  //判断结果标记
int N, D;  //鳄鱼数量 N(≤100) 和 007一次能跳跃的最大距离 D
int FirstJump(int x);   /*判断007能否跳到第一个点*/
int Jump(int x, int y); /*找到第一个结点后,以007一次能跳跃的最大距离为半径,继续找下一个结点*/
int IsSafe(int x);      /*判断能否直接跳到岸边上*/
int DFS(int t);         /*深度优先遍历*/
void Save007(N, D);     /*判断007能否成功获救*/

 

四、算法分析

该算法的时间复杂度主要由以下几个部分组成: (1)计算并找到第一个顶点,即选择第一次跳跃的点(FirstJump(x)):O(1); (2)判断顶点可否作为最后一个结点,即判断该点可否直接跳到岸边(IsSafe(x)):O(1); (3)找下一个结点(Jump(x,y)):O(1); (4)运用递归对各个结点进行遍历计算分析(DFS(t)):O(N); 故整体时间复杂度,即判断007能否成功获救(Save007(N,D))的时间复杂度为 O(n) = O(N^2),空间复杂度为结点空间的使用,即 O(N)。

五、总结

       完成存储方式分析和算法分析部分。通过观看书和慕课相关内容,进一步清晰了图的常见存储方式的优缺点,以及如何选择这些存储方式的方法。在这个过程中有点疑问,慕课中有提及深度优先遍历类似于二叉树的先序遍历,那用堆能不能存储这种图,只存储有用的结点,无用的结点遍历入栈之后就出栈,虽然没有用数组的简便,但是应该比运用邻接表和邻接矩阵的空间和时间复杂度要小得多,还是有些麻烦的。

 

六、源代码

int DFS(int t)
{
    visited[t] = 1;
    if (IsSafe(t) == 1)  //判断是否可以直接跳到岸边
        flag = 1;
    for (int i = 0; i < N; i++)//遍历每一个结点即鳄鱼的位置
    {  
        //判断结点是否被访问并调用Jump()函数判断是否可以成功跳到下一个结点上
        if (!visited[i] && Jump(t, i))
            DFS(i);  //递归调用DFS()函数找可以跳的结点
    }
    return flag;
}
void Save007(N, D) {
    if (D >= 42.5)  //如果007能一次跳跃到岸边直接输出Yes
    {
        printf("Yes");
    }
    else
    {
        for (int i = 0; i < N; i++)
        {
            if (!visited[i] && FirstJump(i))  //visited[i]==1表示该结点已经访问过了
                DFS(i);  //调用DFS()函数
        }
        if (flag == 1)    //根据标记flag的值判断007能否逃出生天
            printf("Yes");
        else
            printf("No");
    }
}

 

以上均为原创作品,欢迎大佬指正!

上一篇:《计算机网络》学习笔记 ·007【无线网络】


下一篇:997?007?准点下班?连数据结构与算法都整不明白,有选择余地?