利用Kruskal算法求最小生成树解决聪明的猴子问题 -- 数据结构

题目:聪明的猴子

链接:https://ac.nowcoder.com/acm/problem/19964

在一个热带雨林中生存着一群猴子,它们以树上的果子为生。昨天下了一场大雨,现在雨过天晴,但整个雨林的地 表还是被大水淹没着,部分植物的树冠露在水面上。猴子不会游泳,但跳跃能力比较强,它们仍然可以在露出水面 的不同树冠上来回穿梭,以找到喜欢吃的果实。现在,在这个地区露出水面的有N棵树,假设每棵树本身的直径都 很小,可以忽略不计。我们在这块区域上建立直角坐标系,则每一棵树的位置由其所对应的坐标表示(任意两棵树 的坐标都不相同)。在这个地区住着的猴子有M个,下雨时,它们都躲到了茂密高大的树冠中,没有被大水冲走。由 于各个猴子的年龄不同、身体素质不同,它们跳跃的能力不同。有的猴子跳跃的距离比较远(当然也可以跳到较近 的树上),而有些猴子跳跃的距离就比较近。这些猴子非常聪明,它们通过目测就可以准确地判断出自己能否跳到 对面的树上。

【问题】 现已知猴子的数量及每一个猴子的最大跳跃距离,还知道露出水面的每一棵树的坐标,你 的任务是统计有多少个猴子可以在这个地区露出水面的所有树冠上觅食。

输入描述:

第1行为一个整数,表示猴子的个数M(2 ≤ M ≤ 500);第2行为M个整数,依次表示猴子的最大跳跃距离(每个整数值在1--1000之间);第3行为一个整数表示树的总棵数N(2 ≤ N ≤ 1000);第4行至第N+3行为N棵树的坐标(横纵坐标均为整数,范围为:-1000--1000)。(同一行的整数间用空格分开)

输出描述:

包括一个整数,表示可以在这个地区的所有树冠上觅食的猴子数

输入

4
1 2  3  4
6
0 0
1 0
1 2
-1 -1
-2  0
2  2

输出

3

分析:

1.题目中的树冠上觅食的猴子数是指能够在所有树上自由移动的猴子数;

2.为了解决这一道题,我们应该要求出最小生成树中最长边的大小,再用每个猴子能够移动的最大距离逐一比较;

3.这里我将采用Kruskal算法求最小生成树:   ①将所有边按权值排序(以升序为例)   ②按照边的排序构建最小生成树

代码:

1.顶点结构定义:
typedef struct point{
    int x, y;//坐标(x,y)
    bool status;
}point;
2.边结构定义:
typedef struct edge{
    point p1, p2;//边的两个端点
    double weight;//边的权值
}edge;
3.对edge e[]进行升序排序:
//按权值快速排序
    qsort(e, tree_num*(tree_num-1)/2, sizeof(edge), cmp);
//cmp函数
int cmp(const void *a, const void *b)
{
    return (*(edge *)a).weight > (*(edge *)b).weight ? 1 : -1;
}
4.Kruskal函数
 1 /*传入:
 2 edge *e  存放边的数组
 3 int tree_num  树的数量
 4 point *p  存放点的数组
 5 */
 6
 7 double kruskal(edge *e, int tree_num, point *p)
 8 {
 9     //并查集
10     int v[tree_num];
11     for(int i=0; i<tree_num; i++){
12         v[i] = i;
13     }
14
15     double longest;
16
17     int i1, i2;
18     for(int i=0; i<tree_num*(tree_num-1)/2; i++){
19         if(search(p, e[i].p1, tree_num) == -1 || search(p, e[i].p2, tree_num) == -1){
20             exit(-1);//ERROR:没有找到e[i].p1在p[]中的坐标
21         }
22         //serach()查找 e[i].p1在p[]中的坐标
23         i1 = v[search(p, e[i].p1, tree_num)];
24         i2 = v[search(p, e[i].p2, tree_num)];
25
26
27         if(i1 == i2 == tree_num-1)break;//已经构建成最小生成树
28         if(i1 == i2)continue;//边的两个端点已经在同一个集合中
29
30         //将i1,i2中较大的作为标记
31         int i3 = i1>i2 ? i1:i2;
32         for(int j=0; j<tree_num; j++){
33             if(v[j] == i1 || v[j] == i2){
34                 v[j] = i3;
35             }
36         }
37         longest = e[i].weight;//更新长边
38     }
39     return longest;//返回最长边
40 }
5.search函数(Kruskal函数中调用):
int search(point *p, point p1, int tree_num)
{
    for(int i=0; i<tree_num; i++){
        if(p1.x == p[i].x && p1.y == p[i].y){
            return i;
        }
    }
    return -1;
}
6.所有代码:

#include<iostream>
#include<cmath>
#include<cstring>
#include <stdlib.h>
using namespace std;

#define max 1000

typedef struct point{
    int x, y;//坐标(x,y)
    bool status;
}point;

typedef struct edge{
    point p1, p2;//边的两个端点
    double weight;//边的权值
}edge;

int cmp( const void *a ,const void *b);
double kruskal(edge *e, int tree_num, point *p);
int get_root(int *v, int x);
int search(point *p, point p1, int tree_num);
int main(){
    //输入猴子数量
    int monkey_num;
    cin>>monkey_num;

    //输入猴子能到达的最远距离
    int step[monkey_num];
    memset(step, 0, sizeof(step));
    for(int i=0; i<monkey_num; i++){
        cin>>step[i];
    }

    //输入树的数量
    int tree_num;
    cin>>tree_num;

    //输入树的坐标(点记录)
    point p[max];
    memset(p, 0, sizeof(p));
    for(int i=0; i<tree_num; i++){
        cin>>p[i].x>>p[i].y;
        p[i].status = 1;//status为1表示可操作的树
    }

    //边记录
    edge e[max];
    int t = 0;
    int vi[max] = {0};
    for(int i=0; p[i].status != 0; i++){
        for(int j=0; p[j].status != 0 ; j++){
            if(i != j && vi[j] == 0){//增加边的权值,两个端点
                e[t].weight = sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x) + (p[i].y-p[j].y)*(p[i].y-p[j].y));
                e[t].p1 = p[i];
                e[t++].p2 = p[j];
            }
        }
        vi[i] = 1;//标记p[i]点连接的所有边已经被读取
    }    

    //按权值快速排序
    qsort(e, tree_num*(tree_num-1)/2, sizeof(edge), cmp);

    double longest = kruskal(e, tree_num, p);

    int ans = 0;
    for(int i=0; i<monkey_num; i++){
        if(step[i] >= longest)ans++;
    }
    cout<<ans;

    return 0;
}    

int cmp(const void *a, const void *b)
{
    return (*(edge *)a).weight > (*(edge *)b).weight ? 1 : -1;
}

double kruskal(edge *e, int tree_num, point *p)
{
    //并查集
    int v[tree_num];
    for(int i=0; i<tree_num; i++){
        v[i] = i;
    } 

    double longest;

    int i1, i2;
    for(int i=0; i<tree_num*(tree_num-1)/2; i++){
        if(search(p, e[i].p1, tree_num) == -1 || search(p, e[i].p2, tree_num) == -1){
            exit(-1);//ERROR:没有找到e[i].p1在p[]中的坐标
        }
        //serach()查找 e[i].p1在p[]中的坐标
        i1 = v[search(p, e[i].p1, tree_num)];
        i2 = v[search(p, e[i].p2, tree_num)];

        if(i1 == i2 == tree_num-1)break;//已经构建成最小生成树
        if(i1 == i2)continue;//边的两个端点已经在同一个集合中 

        //将i1,i2中较大的作为标记
        int i3 = i1>i2 ? i1:i2;
        for(int j=0; j<tree_num; j++){
            if(v[j] == i1 || v[j] == i2){
                v[j] = i3;
            }
        }
        longest = e[i].weight;
    }
    return longest;
}

int search(point *p, point p1, int tree_num)
{
    for(int i=0; i<tree_num; i++){
        if(p1.x == p[i].x && p1.y == p[i].y){
            return i;
        }
    }
    return -1;
}

ALL


总结:很多时候还是听懂容易实践难,难就难在为了实现功能要有一层一层缜密的逻辑需要构建,漏了一种情况都会影响结果的。

(外面的oj真的很严格呢)



参考资料:

【算法】图的最小生成树(Kruskal算法)


原文地址:https://www.cnblogs.com/yi2105/p/10888329.html

时间: 2024-11-07 13:25:02

利用Kruskal算法求最小生成树解决聪明的猴子问题 -- 数据结构的相关文章

Prim算法和Kruskal算法求最小生成树

Prim算法 连通分量是指图的一个子图,子图中任意两个顶点之间都是可达的.最小生成树是连通图的一个连通分量,且所有边的权值和最小. 最小生成树中,一个顶点最多与两个顶点邻接:若连通图有n个顶点,则最小生成树中一定有n-1条边. Prim算法需要两个线性表来进行辅助: visited: 标记已经加入生成树的顶点:(它的功能可以由tree取代) 初始状态:生成树根节点为真,其它为0. tree: 记录生成树,tree[x]保存顶点x的直接根节点下标,若x为树的根节点则tree[x]为其自身. 初始状

Kruskal算法求最小生成树

求加权连通图的最小生成树的算法.kruskal算法总共选择n- 1条边,(共n条边)所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中.注意到所选取的边若产生环路则不可能形成一棵生成树.kruskal算法分e 步,其中e 是网络中边的数目.按耗费递增的顺序来考虑这e 条边,每次考虑一条边.当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入. 克鲁斯卡尔算法(Kruskal's algorithm)是两个经典的最小生成树算法的

kruskal算法求最小生成树(jungle roads的kruskal解法)

注意: 注意数组越界问题(提交出现runtimeError代表数组越界) 刚开始提交的时候,边集中边的数目和点集中点的数目用的同一个宏定义,但是宏定义是按照点的最大数定义的,所以提交的时候出现了数组越界问题,以后需要注意啦. Description The Head Elder of the tropical island of Lagrishan has a problem. A burst of foreign aid money was spent on extra roads betwe

Prime算法求最小生成树 (邻接表)

/* Name: Prime算法求最小生成树 (邻接表) Copyright: Author: 巧若拙 Date: 25/11/14 13:38 Description: 实现了 Prime算法求最小生成树 (邻接表)的普通算法和最小堆优化算法. */ #include<stdio.h> #include<stdlib.h> #define MAX 2000   //最大顶点数量 #define INFINITY 999999   //无穷大 typedef int VertexT

Prime算法求最小生成树 (邻接矩阵)

/* Name: Prime算法求最小生成树 (邻接矩阵) Copyright: Author: 巧若拙 Date: 25/11/14 13:38 Description: 实现了 Prime算法求最小生成树 (邻接矩阵)的普通算法和最小堆优化算法. */ #include<stdio.h> #include<stdlib.h> #define MAX 2000   //最大顶点数量 #define INFINITY 999999   //无穷大 typedef struct Mi

最小生成树 克鲁斯卡尔(Kruskal)算法求最小生成树

Kruskal算法的过程: (1) 将全部边按照权值由小到大排序. (2) 按顺序(边权由小到大的顺序)考虑没条边,只要这条边和我们已经选择的边步构成圈,就保留这条边,否则放弃这条边.算法 成功选择(n-1)条边后,形成一个棵最小生成树,当然如果算法无法选择出(n-1)条边,则说明原图不连通. 图中的路径按照权值的大小的排序为 AF 1; BE 4; BD 5; BC 6; DC:10; BF 11; DF 14; AE 16; AB 17; EF 33; 算法的处理过程如下 先选A,F不在一个

hdu 1875 畅通工程再续(kruskal算法计算最小生成树)

畅通工程再续 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 18411    Accepted Submission(s): 5769 Problem Description 相信大家都听说一个"百岛湖"的地方吧,百岛湖的居民生活在不同的小岛中,当他们想去其他的小岛时都要通过划小船来实现.现在政府决定大力发展百岛湖,发展首先

Kruskal算法(最小生成树)

Kruskal算法:首先按照边的权值进行从小到大排序,每次从剩余的边中选择权值最小的边且不会产生回路的边加入到生成树中,直到加入n-1条边就结束: 算法难点在与如何判断是否会产生回路.这个可以通过并查集实现,将所有加入生成树的结点加入同一个集合: 代码: #include <string.h> #include<iostream> #include<vector> #include<queue> #include <algorithm> usin

Kruskra算法求最小生成树

算法思想: (1)创建图,将每个节点的根节点,标记为-1. (2)对图中的边按权重排序 (3)遍历每条边,获取边两端的根节点 如果两个端点的根节点相等,且均是-1,则代表这是一条孤立的边(同时修改目的节点的父节点设为源节点),把这条边计入生成树 如果两个端点的根节点相等,且不是-1(可能是-2,-3,之类),这说明加入这条边构成环路,不需要任何操作 如果两个端点的根节点不相等,这说明这条边连接了两棵树,(同时修改目的节点根节点的父节点设为源节点),把这条边计入生成树 注:其实我们知道最小生成树,