利用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)。(同一行的整数间用空格分开)

输出描述:

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

输入

复制
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算法)



猜你喜欢

转载自www.cnblogs.com/yi2105/p/10888329.html