分治算法课后习题2

大盒子数

你正在参加⼀档游戏节⽬。你会看到⼀排盒⼦,每个盒⼦⾥都包含⼀个任意且唯⼀的数字。你的⽬标是在尽可能少地打开盒⼦的情况下找到⼀个盒⼦,其数字⼤于其左边和右边的盒⼦中的数字。当然,除⾮它是第⼀个或最后⼀个盒⼦,在这种情况下,它只需要⼤于其相邻的那个盒⼦的数字即可。假设⼀共有n个盒⼦,你的算法的时间复杂度应该优于 O(n)。给出算法的基本思路和伪代码描述,分析算法的时间复杂度,并给出正确性证明。

目标分析:找到一个盒子数使其值大于相邻的盒子数,要求打开的盒子数尽可能小。由于盒子数唯一,所以必然存在一个最大的值解和若干局部最优解

对于一个数字序列来说,要找到一个满足条件的数字,它必然会出现在一个局部峰值的位置上(即比相邻的两个数字都大)。我们可以利用这一特性来设计算法。首先,我们检查中间的盒子,如果它比两侧的盒子都大,那么它就是我们要找的数字。如果它比左侧的盒子小,那么在左侧必然存在一个局部峰值,我们可以将搜索范围缩小到左侧。同理,如果它比右侧的盒子小,我们将搜索范围缩小到右侧。

function find_peak(arr):
    n = length(arr)
    left = 0
    right = n - 1
    
    while left <= right:
        mid = left + (right - left) / 2
        
        if (arr[mid] > arr[mid - 1] and arr[mid] > arr[mid + 1]):
            return arr[mid]
        elif arr[mid] < arr[mid - 1]:
            right = mid - 1
        else:
            left = mid + 1

T ( n ) = T ( n / 2 ) ⇒ O ( l o g n ) T(n)=T(n/2)\Rightarrow O(log n) T(n)=T(n/2)O(logn)
算法的时间复杂度是 O(log n),因为它类似于二分查找,每次将搜索范围减少一半。

正确性分析:我们设找到大盒子数需要打开的盒数量为k,则:

  • n=1,显然,这个盒子中的数就是我们要找的(不需要打开),k=0;
  • n=2,我们取两个盒子中较大的那个,k=2≤n
  • n≥3,基于了局部峰值的性质,从中间向两侧开始比较必然会有一个局部峰值可以在log n次比较后找到。

最近点对

最近对问题要求在包含有n个点的集合S中,找出距离最近的两个点。p1(x1,y1),p2(x2,y2),……,pn(xn,yn)是平面的n个点。

分治法求解

  1. 分解
    - 对所有的点按照x坐标(或者y)从小到大排序(排序方法时间复杂度O(nlogn))。
    - 根据下标进行分割,使得点集分为两个集合。
  2. 解决
    - 递归的寻找两个集合中的最近点对。
    - 取两个集合最近点对中的最小值 m i n ( d i s l e f t , d i s r i g h t ) min(dis_{left}, dis_{right}) min(disleftdisright)
  3. 合并
    - 最近距离不一定存在于两个集合中,可能一个点在集合A,一个点在集合B,而这两点间距离小于dis。

在这里插入图片描述
其中如何合并是关键。根据递归的方法可以计算出划分的两个子集中所有点对的最小距离 d i s l e f t , d i s r i g h t dis_{left}, dis_{right} disleftdisright,再比较两者取最小值,即 d i s = m i n ( d i s l e f t , d i s r i g h t ) dis =min(dis_{left}, dis_{right}) dis=min(disleftdisright)。那么一个点在集合A,一个在集合B中的情况,可以针对此情况,用之前分解的标准值,即按照x坐标(或者y)从小到大排序后的中间点的x坐标作为mid,划分一个 [ m i d − d i s , m i d + d i s ] [mid - dis, mid + dis] [middis,mid+dis]区域,如果存在最小距离点对,必定存在这个区域中。
在这里插入图片描述
之后只需要根据 [ m i d − d i s , m i d ] [mid - dis, mid] [middis,mid]左边区域的点来遍历右边区域 [ m i d , m i d + d i s ] [mid, mid + dis] [mid,mid+dis]的点,即可找到是否存在小于dis距离的点对。
但是存在一个最坏情况,即通过左右两个区域计算得到的dis距离来划分的第三区域可能包含集合所有的点,这时候进行遍历查找,时间复杂度仍然和brute force方法相同,都为 O ( n 2 ) O(n^2) O(n2)。因此需要对此进行深一步的考虑。

将宽为 2 δ 2 \delta 2δ的带状区域内的点根据 坐标从小到⼤排序,比如下图中 s 9 s_{9} s9表示点y轴坐标第9小的点。在这里插入图片描述1985年Preparata和Shamos在给出该问题的一个分治算法并且还具体分析了在[mid - dis, mid + dis]区域中出现的情况,若(p,q)是Q的最近点对,p在带域左半部分,则q点必在下图所示的 δ ∗ 2 δ \delta*2\delta δ2δ长方形上,而在该长方形上,最多只能由右边点集的6个点。每个点对之间的距离不小于 δ \delta δ
在这里插入图片描述

证明: 如果 ∣ j − i ∣ > 7 |j-i|>7 ji>7,那么 d ( s i − s j ) ≥ δ d(s_{i}-s_{j})≥\delta d(sisj)δ

  1. 考虑带状区域中⼀个 δ ∗ 2 δ \delta*2\delta δ2δ的矩形 ,其下底边恰好经过点 s i s_{i} si
  2. s j s_{j} sj是任意位于R上⽅的点,显然 d ( s i − s j ) ≥ δ d(s_{i} - s_{j})≥\delta d(sisj)δ
  3. 将矩形R分成8个小正⽅形
  4. 每个小正⽅形中最多只有⼀个点
    • 小正⽅形的对角线长为 δ 2 < δ \frac{\delta }{\sqrt{2}}< \delta 2 δ<δ
    • 两侧的最近点对的距离是 δ \delta δ
  5. 除了 s i s_{i} siR中最多只有7个点

借此,可以将可能的线性时间缩小到常数级,大大提高了平均时间复杂度。

在分解和合并时,可能存在按照x轴、y轴进行排序的预处理O(nlogn),该问题在解决阶段只做提取的操作为 Θ(n),递推式为:
T ( n ) = { 1 n ≤ 3 2 T ( n 2 ) + O ( n ) n > 3 T(n)=\left\{\begin{matrix} 1 & n\leq 3\\ 2T(\frac{n}{2}) + O(n) & n> 3 \end{matrix}\right. T(n)={12T(2n)+O(n)n3n>3
计算后得到整体时间复杂度为:O(nlogn)

  • 输入
    P x P_{x} Px:n个点,按照横坐标排序
    P y P_{y} Py:n个点,按照纵坐标排序
  • 构造分割线将 p 1 . . . p n p_{1}...p_{n} p1...pn分成L和R两个部分,每个部分均有 n 2 \frac{n}{2} 2n个点
  • 变量
    L x , L y L_{x},L_{y} Lx,Ly:L中的点按坐标大小排序
    R x , R y R_{x},R_{y} Rx,Ry:R中的点按坐标大小排序
    A y A_{y} Ay:与垂线距离小于 δ \delta δ的点,按照纵坐标排序
  • Closest_Pair( P x , P y P_{x},P_{y} Px,Py)
    P x P_{x} Px左半部分的点加⼊ L x L_{x} Lx,右半部分的点加⼊ R x R_{x} Rx //O(n)
    对于 P y P_{y} Py中的每个点,根据其横坐标将其加⼊ L Y L_{Y} LY或者 R y R_{y} Ry //O(n)
    ( l 1 , l 2 ) ← C l o s e s t _ P a i r ( P x , P y ) (l_{1},l_{2})\leftarrow Closest\_Pair(P_{x},P_{y}) (l1,l2)Closest_Pair(Px,Py)
    ( r 1 , r 2 ) ← C l o s e s t _ P a i r ( R x , R y ) (r_{1},r_{2})\leftarrow Closest\_Pair(R_{x},R_{y}) (r1,r2)Closest_Pair(Rx,Ry)
    δ ← m i n { d ( l 1 , l 2 ) , d ( r 1 , r 2 ) } \delta\leftarrow min\left \{ d(l_{1},l_{2}),d(r_{1},r_{2}) \right \} δmin{d(l1,l2),d(r1,r2)}
    对于 P y P_{y} Py中的点,如果其距离分割线的距离小于 δ \delta δ,将其加入 A y A_{y} Ay
    对于 A y A_{y} Ay中的每个点
    • 计算其与后续7个点的距离,如果某个距离小于 δ \delta δ,更新 δ \delta δ和最近点对
  • 返回最近点对
#include <iostream>
#include <complex>
#include <vector>
#include <algorithm>
#include <cmath>
#include <random>
#include <ctime>
#include <chrono>
using namespace std;
using namespace std::chrono;

typedef long double LD;
typedef complex<LD> Point;

LD dist(Point p1, Point p2) {
    return sqrt(pow(p1.real() - p2.real(), 2) + pow(p1.imag() - p2.imag(), 2));
}

pair<Point, Point> brute_force_closest_pair(vector<Point>& P) {
    pair<Point, Point> closest = make_pair(Point(0, 0), Point(0, 0));
    LD min_dist = dist(P[0], P[1]);
    for (size_t i = 0; i < P.size(); ++i) {
        for (size_t j = i + 1; j < P.size(); ++j) {
            LD d = dist(P[i], P[j]);
            if (d <= min_dist) {
                min_dist = d;
                closest = make_pair(P[i], P[j]);
            }
        }
    }
    return closest;
}

pair<Point, Point> closest_pair_recursive(vector<Point>& Px, vector<Point>& Py) {
    size_t n = Px.size();
    if (n <= 3) {
        return brute_force_closest_pair(Px);
    }

    size_t mid = n / 2;
    vector<Point> Qx(Px.begin(), Px.begin() + mid);
    vector<Point> Rx(Px.begin() + mid, Px.end());

    Point mid_point = Px[mid];

    vector<Point> Qy, Ry;
    for (auto& point : Py) {
        if (point.real() < mid_point.real() || (point.real() == mid_point.real() && point.imag() <= mid_point.imag())) {
            Qy.push_back(point);
        }
        else {
            Ry.push_back(point);
        }
    }

    pair<Point, Point> closest_left = closest_pair_recursive(Qx, Qy);
    pair<Point, Point> closest_right = closest_pair_recursive(Rx, Ry);

	LD delta1 = dist(closest_left.first, closest_left.second);
	LD delta2 = dist(closest_right.first, closest_right.second);
    LD delta = min(delta1,delta2);

    vector<Point> Sy;
    for (auto& point : Py) {
        if (abs(mid_point.real() - point.real()) < delta) {
            Sy.push_back(point);
        }
    }

    pair<Point, Point> closest_pair = delta1 < delta2 ? closest_left : closest_right;

    for (size_t i = 0; i < Sy.size(); ++i) {
        for (size_t j = i + 1; j < Sy.size() && j < i + 8; ++j) {
            LD d = dist(Sy[i], Sy[j]);
            if (d < delta) {
                delta = d;
                closest_pair = make_pair(Sy[i], Sy[j]);
            }
        }
    }

    return closest_pair;
}

pair<Point, Point> closest_pair(vector<Point>& P) {
    vector<Point> Px = P;
    vector<Point> Py = P;
    sort(Px.begin(), Px.end(), [](const Point& p1, const Point& p2) {
        return p1.real() < p2.real();
        });
    sort(Py.begin(), Py.end(), [](const Point& p1, const Point& p2) {
        return p1.imag() < p2.imag();
        });
    return closest_pair_recursive(Px, Py);
}

vector<Point> generate_random_points(int n) {
    vector<Point> points;

    // 
    mt19937 rng(time(0));

    // Set random seeds to the current time
    uniform_real_distribution<LD> dist(-1000, 1000); // points are in the range [-1000, 1000]
    for (int i = 0; i < n; ++i) {
        LD x = dist(rng);
        LD y = dist(rng);
        points.push_back(Point(x, y));
    }

    return points;
}

int main() {
    while (1){
        int n; cin >> n;
        cout << n << " random points are being generated..." << endl;
        vector<Point> points = generate_random_points(n);
        //time evaluation
        auto start_time = high_resolution_clock::now();

        pair<Point, Point> closest = closest_pair(points);

        auto end_time = high_resolution_clock::now();
        auto duration = duration_cast<microseconds>(end_time - start_time);

        cout << "Algorithm executed in " << duration.count() << " microseconds." << endl;

        cout << "Closest pair: (" << closest.first.real() << ", " << closest.first.imag() << ") and ("
            << closest.second.real() << ", " << closest.second.imag() << ")" << endl;

        cout << "<---------------------------------------------------------------->" << endl;
    }
    return 0;
}

运行结果
在这里插入图片描述可以看到运行时长与点集规模是呈现nlogn增长的。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恭仔さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值