• 《数据结构与算法分析:C语言描述》复习——第十章“算法设计技巧”——平面最近点对


    2014.07.06 17:56

    简介:

      给定二维平面上n个点,求出距离最近的两个点的距离。

    描述:

      个人觉得这本书的第十章是最精华的,因为每种算法思想用一个典型问题来讲解。平面最近点对问题,自然是分治法的例子了。

      暴力的O(n^2)枚举法自然不用多说,两层循环能得出结果,但效率上只能承担至多几千个点的计算。

      分治法的基本思路是:如果我将n个点分为两批,那么距离最短的点要么同属一批(这样可以用子问题得到答案),要么各落在一批中(这样就必须合并子问题才能得到答案)。

      分解为子问题的过程自然很简单,选出一个分界点mid,然后将n个点分为两部分,对每一部分递归求最近距离点对。

      合并过程复杂一些,并且发生在递归调用结束之后。也就是:

        递归左半部分的子问题 -> 递归右半部分的子问题 -> 合并结果得出最终答案

      我们定义δ(delta)为左右两部分计算得出的最近距离的较小值。如果最终结果比δ还要小的话,那么它应该会出现在分界线mid处(否则的话,最终结果肯定早就在子问题中得到了,也就不可能比δ还小),而且是由离mid很近的一左一右两个点得到。因此以mid位置的点p[mid]为中心,δ为半径的正方形区域(不是圆形,因为不好计算)都是可能得到最小值的区域。

      那么,我们从下标mid开始向两边扫描,找出所有落在这个可疑区域的点,于是可以得到左右两个点集P和P。然后将两个集合中的点一一比对就能得到结果了。

      如果这样算法就完了,那么实际的程序运行甚至会比暴力算法还慢,因为数据如果一直是无序的,那就无法在该结束循环的时候提前结束循环。

      排序1:在算法的一开始,就应该选取一个坐标轴(比如X)进行排序,这样我们在“那么,我们从下标mid开始向两边扫描”这句话的时候,就有了break的条件了。如果X坐标是有序的, 那么当某个p[i]的X坐标与p[mid]相差超过了δ,就可以停止扫描了。

      排序2:“于是可以得到左右两个点集P和P”,尽管我们可以直接对两个候选集合中的点一一计算距离,得到最短距离。但这么做的话,效率仍然是暴力级别的。这些点全都落在了2δ为边长的正方形区域内,而且这个δ给人的印象很小很小,于是我们可能会认为P与P加起来点的数量很小,即使枚举也没关系的。在实际的递归调用里,一个看似小的浪费就会被放大到可观察的程度,然后你的代码就超时了。在排序1中X坐标已经有序了,那么可能变化的比较离谱的,只有Y坐标了。于是我们对候选集合中点按Y坐标进行排序。有序的数据,总能给break语句制造机会。这个排序让算法完整了。

      为了确认算法的效率,我把代码修改了一句话之后提交到了ZOJ的题目“Quoit Design”运行,结果的确能AC掉。

    实现:

      1 // Divide-and-conquer solution for Closest Pair of Points problem.
      2 // This piece of code is also accepted on Zhejiang University Online Judge
      3 // Problem ID 2107.
      4 // http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1107
      5 #include <algorithm>
      6 #include <cmath>
      7 #include <cstdio>
      8 #include <vector>
      9 using namespace std;
     10 
     11 struct Point {
     12     double x;
     13     double y;
     14     Point(double _x = 0.0, double _y = 0.0): x(_x), y(_y) {}
     15 };
     16 
     17 bool comparatorX(const Point &a, const Point &b)
     18 {
     19     if (a.x == b.x) {
     20         return a.y < b.y;
     21     } else {
     22         return a.x < b.x;
     23     }
     24 }
     25 
     26 bool comparatorY(const Point &a, const Point &b)
     27 {
     28     if (a.y == b.y) {
     29         return a.x < b.x;
     30     } else {
     31         return a.y < b.y;
     32     }
     33 }
     34 
     35 double dist(const Point &a, const Point &b)
     36 {
     37     return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
     38 }
     39 
     40 double closestPairOfPointsRecursive(const vector<Point> &points, int left, 
     41     int right)
     42 {
     43     if (right - left + 1 == 2) {
     44         return dist(points[left], points[left + 1]);
     45     } else if (right - left + 1 == 3) {
     46         return min(dist(points[left], points[left + 1]), 
     47             min(dist(points[left + 1], points[left + 2]), 
     48             dist(points[left], points[left + 2])));
     49     }
     50     
     51     int mid = left + (right - left) / 2;
     52     double delta = min(closestPairOfPointsRecursive(points, left, mid), 
     53         closestPairOfPointsRecursive(points, mid + 1, right));
     54     vector<Point> p;
     55 
     56     int i, j;
     57     i = mid;
     58     while (i >= left && points[i].x > points[mid].x - delta) {
     59         p.push_back(points[i]);
     60         --i;
     61     }
     62     
     63     i = mid + 1;
     64     while (i <= right && points[i].x < points[mid].x + delta) {
     65         p.push_back(points[i]);
     66         ++i;
     67     }
     68 
     69     int np;
     70     double result = delta;
     71 
     72     sort(p.begin(), p.end(), comparatorY);
     73     np = (int)p.size();
     74     for (i = 0; i < np; ++i) {
     75         for (j = i + 1; j < np; ++j) {
     76             if (p[j].y - p[i].y > result) {
     77                 break;
     78             }
     79             result = min(result, dist(p[i], p[j]));
     80         }
     81     }
     82     p.clear();
     83     
     84     return result;
     85 }
     86 
     87 double closestPairOfPoints(vector<Point> &points)
     88 {
     89     int n = points.size();
     90     sort(points.begin(), points.end(), comparatorX);
     91     return closestPairOfPointsRecursive(points, 0, n - 1);
     92 }
     93 
     94 int main()
     95 {
     96     vector<Point> points;
     97     int n;
     98     int i;
     99     
    100     while (scanf("%d", &n) == 1 && n > 0) {
    101         points.resize(n);
    102         for (i = 0; i < n; ++i) {
    103             scanf("%lf%lf", &points[i].x, &points[i].y);
    104         }
    105         // printf("%.2f
    ", closestPairOfPoints(points) / 2.0);
    106         printf("%.2f
    ", closestPairOfPoints(points));
    107     }
    108     
    109     return 0;
    110 }
  • 相关阅读:
    Seconds_Behind_Master的计算
    innnodb 线程在做什么?
    Mysql Join_buffer_size的使用原理
    C 实现位图排序
    C 内存池的实现
    C实现队列
    mysqld执行的函数栈
    Source Insight的基本用法
    MySQL高性能以及高安全测试
    【设计篇】状态与策略
  • 原文地址:https://www.cnblogs.com/zhuli19901106/p/3828396.html
Copyright © 2020-2023  润新知