分治策略解题报告

分治策略解题报告

分治法的原理

​ 分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。即一种分目标完成程序算法,简单问题可用二分法完成。

​ 简单的说,分治就是分而治之,把一个问题拆分成几个小问题,最后再汇总解决的办法。

有两点需要记住:

(1) 分治法基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同。

(2)递归的解这些子问题,然后将各子问题的解合并得到原问题的解。

题目1.求两个n位的(二进制、十进制)长整数乘积的高效算法

(1)两个1位数相乘视为基本运算

(2)两个1位数相乘运算量远高于其相加

算法思路

​ 这里我们假设有两个大整数X、Y,分别设X=1234、Y=5678。现在要求X*Y的乘积,暴力的算法就是把X与Y中的每一项去乘,但是这样的乘法所需的时间复杂度为,效率比较低下。

​ 那我们可以采用分治的算法,将X、Y分别拆分为A与B、C与D,如下图:
在这里插入图片描述

X ∗ Y = ( A ∗ 1 0 n / 2 + B ) ∗ ( C ∗ 1 0 n / 2 + D ) = A C ∗ 1 0 n + ( A D + B C ) ∗ 1 0 n / 2 + B D … … ( 1 ) X * Y = (A * 10^{n/2} + B) * (C * 10^{n/2} + D) = AC * 10^n + (AD + BC) * 10^{n/2} + BD……(1) XY=(A10n/2+B)(C10n/2+D)=AC10n+(AD+BC)10n/2+BD……(1)
此处的n为X、Y的位数。

算法分析

  1. 首先将X和Y分成A,B,C,D
  2. 此时将X和Y的乘积转化为(1)式,把问题转化为求解因式分解的值

​ 在(1)式中,我们一共需要进行4次n/2的乘法(AC2次,AD、BC各一次)和3次加法,因而该算法的时间复杂度为:
T ( n ) = 4 ∗ T ( n / 2 ) + θ ( n ) T(n) = 4 * T(n/2) + θ(n) T(n)=4T(n/2)+θ(n)
计算求得:
T ( n ) = θ ( n 2 ) T(n) = θ(n^2) T(n)=θ(n2)
相当于暴力求解。

但是我们再来看看,我们是否可以用加法来换取乘法?因为多一个加法操作,也是常数项,对时间复杂度没有影响,如果减少一个乘法则不同。

(1)式可以化为:
X ∗ Y = A C ∗ 1 0 n + ( ( A + B ) ∗ ( C + D ) − A C − B D ) ∗ 1 0 n / 2 + B D X * Y = AC * 10^n + ((A + B) * (C + D) - AC - BD) * 10^{n/2} + BD XY=AC10n+((A+B)(C+D)ACBD)10n/2+BD
或者 X ∗ Y = A C ∗ 1 0 n + ( ( A − B ) ∗ ( D − C ) + A C + B D ) ∗ 1 0 n / 2 + B D 或者 X * Y = AC * 10^n + ((A - B) * (D - C) + AC + BD) * 10^{n/2} + BD 或者XY=AC10n+((AB)(DC)+AC+BD)10n/2+BD

此处的主要变换为:
A D + B C = ( A + B ) ∗ ( C + D ) − A C − B D = ( A − B ) ∗ ( D − C ) + A C + B D AD + BC = (A + B) * (C + D) - AC - BD = (A - B) * (D - C) + AC + BD AD+BC=(A+B)(C+D)ACBD=(AB)(DC)+AC+BD
省去了一步乘法,因为乘法的耗时比加法高很多。

现在的时间复杂度为:
T ( n ) = 3 ∗ T ( n / 2 ) + θ ( n ) T(n) = 3 * T(n/2) + θ(n) T(n)=3T(n/2)+θ(n)

计算求得:
T ( n ) = O ( n l o g 2 ( 3 ) ) = O ( n 1.585 ) T(n) = O(n^{log2(3)}) = O(n^{1.585}) T(n)=O(nlog2(3))=O(n1.585)
时间复杂度有较明显的降低。

C++代码实现

#include <iostream>
#include <cmath>
using namespace std;

#define LL long long

int SIGN(LL A)
{
    return (A > 0) - (A < 0); // 处理 A = 0 的情况
}

LL IntegerMultiply(LL x, LL y, int n)
{
    if (x == 0 || y == 0) // 乘数有一个为 0,两数相乘为 0
        return 0;

    LL sign = SIGN(x) * SIGN(y); // 确定结果的符号
    x = abs(x);
    y = abs(y);

    if (n == 1) // 只剩一位时,返回简单运算
        return sign * (x * y);

    int half = n / 2;

    // 计算 10^half 的值
    LL base = 1;
    for (int i = 0; i < half; ++i)
    {
        base *= 10;
    }

    // 分割 x 和 y
    LL a = x / base;
    LL b = x % base;
    LL c = y / base;
    LL d = y % base;

    LL ac = IntegerMultiply(a, c, half);                     // 计算 AC
    LL bd = IntegerMultiply(b, d, half);                     // 计算 BD
    LL abcd = IntegerMultiply(a + b, c + d, half) - ac - bd; // 计算 AD + BC

    // 组合结果
    return sign * (ac * (base * base) + abcd * base + bd);
}

int main()
{
    LL x, y;
    cin >> x >> y;

    // 计算位数 n
    LL n = 0;
    LL maxVal = max(abs(x), abs(y));
    while (maxVal != 0)
    {
        maxVal /= 10;
        n++;
    }
    if (n == 0)
        n = 1; // 确保 n 至少为 1

    cout << "直接相乘 X * Y = " << x * y << endl;
    cout << "分治相乘 X * Y = " << IntegerMultiply(x, y, n) << endl;

    return 0;
}

测试结果

1234 5678
直接相乘 X * Y = 7006652
分治相乘 X * Y = 7006652

123456 456789
直接相乘 X * Y = 56393342784
分治相乘 X * Y = 56393342784

1234 -5678
直接相乘 X * Y = -7006652
分治相乘 X * Y = -7006652

-123456 -456789
直接相乘 X * Y = 56393342784
分治相乘 X * Y = 56393342784 

可见得出结果是正确的。

题目2.直线x上分散有n个点(n个点的位置记为x1,x2,…,xn),求距离最近的点对

算法思路

这个算法使用分治法来解决寻找一维坐标系中最近点对的问题。其基本思路可以概括为以下几个步骤:

  1. 排序
    • 首先对所有点进行排序,以便于后续的分治操作。
  2. 递归分治
    • 将点集分为左右两部分,分别计算每部分中的最近点对。
    • 递归的基准情况是,当点的数量小于或等于1时,返回一个无穷大的距离。
  3. 合并结果
    • 在左半部分和右半部分中找到最近的点对后,取这两个点对中的最小距离 d
    • 然后,创建一个带状区域(strip),该区域包含与中点距离小于 d 的点。
    • 调用 stripClosest 函数来查找带状区域内的最近点对,并更新最小距离及对应的点对。
  4. 返回结果
    • 最终返回最小的距离及相应的点对。

算法分析

  1. 排序:初始的排序操作需要 O(nlogn) 的时间复杂度。

  2. 递归调用

    • 每次分治将点集分为两个部分,因此递归的深度是 O(nlogn)。
    • 在每一层递归中,需要处理带状区域的点。
    • 创建带状区域的时间复杂度是 O(n),因为要遍历所有点。
    • 在带状区域内寻找最近点对的时间复杂度是 O(n),因为可能需要比较所有点。
  3. 总时间复杂度

    • 因此,整个算法的时间复杂度可以表示为:
      T ( n ) = 2 T ( n / 2 ) + O ( n ) T(n) = 2T(n/2)+O(n) T(n)=2T(n/2)+O(n)

    • 根据master定理,这个递归关系的解为:
      T ( n ) = O ( n l o g ⁡ n ) T(n) = O(nlog⁡n) T(n)=O(nlogn)

总结

因此,该算法可以在 O(nlog⁡n) 的时间复杂度下高效地找到一维坐标系中最近的点对。这种方法相较于简单的 O(n^2)的暴力搜索方法具有显著的性能优势。

代码实现

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <limits>

using namespace std;

// 检查带状区域内的点对
double stripClosest(const vector<double> &strip, double d, pair<double, double> &closestPair)
{
    double minDist = d;
    vector<double> sortedStrip = strip; // 创建副本进行排序
    sort(sortedStrip.begin(), sortedStrip.end());

    for (size_t i = 0; i < sortedStrip.size(); ++i)
    {
        for (size_t j = i + 1; j < sortedStrip.size() && (sortedStrip[j] - sortedStrip[i]) < minDist; ++j)
        {
            if (double dist = sortedStrip[j] - sortedStrip[i]; dist < minDist)
            {
                minDist = dist;
                closestPair = {sortedStrip[i], sortedStrip[j]};
            }
        }
    }
    return minDist;
}

// 分治法求解最近点对
pair<double, pair<double, double>> closestPairRecursive(vector<double> &points, int left, int right)
{
    if (right - left <= 1)
        return {numeric_limits<double>::infinity(), {0, 0}};

    int mid = left + (right - left) / 2;
    double midPoint = points[mid];

    auto leftPair = closestPairRecursive(points, left, mid);
    auto rightPair = closestPairRecursive(points, mid, right);

    double d = min(leftPair.first, rightPair.first);
    pair<double, double> closestPair = (d == leftPair.first) ? leftPair.second : rightPair.second;

    vector<double> strip;
    for (int i = left; i < right; ++i)
        if (abs(points[i] - midPoint) < d)
            strip.push_back(points[i]);

    double dStrip = stripClosest(strip, d, closestPair);
    return {min(d, dStrip), closestPair};
}

// 主函数
int main()
{
    int n;
    cout << "请输入点的数量: ";
    cin >> n;

    if (n < 2)
    {
        cout << "没有足够的点进行计算。" << endl;
        return 0;
    }

    vector<double> points(n);
    cout << "请输入点的坐标: ";
    for (double &point : points)
        cin >> point;

    sort(points.begin(), points.end());
    auto result = closestPairRecursive(points, 0, n);

    if (result.first == numeric_limits<double>::infinity())
    {
        cout << "没有足够的点进行计算。" << endl;
    }
    else
    {
        cout << "最小距离为: " << result.first << endl;
        cout << "最近的点对是: (" << result.second.first << ", " << result.second.second << ")" << endl;
    }

    return 0;
}

测试结果

请输入点的数量: 10 1 3 5 -2 -5 1.4 1.6 9 -0.6 1.3
请输入点的坐标: 最小距离为: 0.1
最近的点对是: (1.3, 1.4)

请输入点的数量: 15 0 1 -2 1.55 12 -4 22 4 6 -22 43 -0.4 1.39 11 -3
请输入点的坐标: 最小距离为: 0.16
最近的点对是: (1.39, 1.55)

可见测试结果是比较正确的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

浮央乜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值