1. 程式人生 > >利用Kruskal演算法求最小生成樹解決聰明的猴子問題 -- 資料結構

利用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演算法)