分治算法(算法详解+模板+例题)

1.定义

分治算法(Divide and Conquer Algorithm)是一种通过将复杂问题分解为多个较小的子问题来解决的方法。这些子问题的形式与原问题相同或相似,且彼此独立。通过递归地解决这些子问题,并将它们的解合并起来,最终得到原问题的解。这种方法在排序(如快速排序、归并排序)、查找(如二分查找)和数学计算(如大整数乘法)等领域有广泛的应用。

2.基本思路

1.分解:

  • 目标:将原问题分解为若干个规模较小、相互独立的子问题。
  • 方法
    • 确定分解的标准或条件。
    • 根据标准或条件将原问题划分为多个子问题。
    • 每个子问题的形式与原问题相同或相似。

2.解决:

  • 目标:递归地求解这些子问题。
  • 方法
    • 如果子问题的规模足够小,可以直接求解,不再继续分解。
    • 否则,继续递归地应用分治算法,将子问题进一步分解,直到达到可以直接求解的规模。

3.合并:

  • 目标:将子问题的解合并起来,形成原问题的解。
  • 方法
    • 设计合适的合并策略,将子问题的解组合成原问题的解。
    • 合并过程中可能涉及一些额外的计算或操作。

 3.操作步骤

1. 分解

目标:将原问题分解为若干个规模较小、相互独立的子问题。

操作步骤1.确定分解标准:根据问题的特点,选择合适的分解标准或条件。

2.划分子问题:按照分解标准将原问题划分为多个子问题。

3.确保子问题形式相同:确保每个子问题的形式与原问题相同或相似。

2. 解决

目标:递归地求解这些子问题。

操作步骤1.检查子问题规模:判断子问题的规模是否足够小,可以直接求解。

2.直接求解小规模问题:如果子问题的规模足够小,直接求解,不再继续分解。

3.递归求解大规模问题:如果子问题的规模仍然较大,继续递归地应用分治算法,将子问题进一步分解,直到达到可以直接求解的规模。

3. 合并

目标:将子问题的解合并起来,形成原问题的解。

操作步骤1.设计合并策略:根据问题的特点,设计合适的合并策略。

2.执行合并操作:将子问题的解组合成原问题的解。

3.处理合并过程中的额外计算:合并过程中可能涉及一些额外的计算或操作,确保这些操作正确无误。

4.代码模板

1.通用分治算法模板(C++)
#include <iostream>
#include <vector>
using namespace std;
// 基本情况:如果问题规模足够小,直接求解
bool is(const vector<int>& p) {
    // 根据问题的具体情况判断
    return p.size() <= 1;
}
// 直接求解基本情况
vector<int> solve(const vector<int>& p) {
    // 根据问题的具体情况求解
    return p;
}
// 分解问题
vector<vector<int>> di(const vector<int>& p) {
    // 根据问题的具体情况分解
    int n = p.size(),mid = n / 2;
    vector<int> left(p.begin(), p.begin() + mid);
    vector<int> right(p.begin() + mid, p.end());
    return {left, right};
}
// 合并子问题的解
vector<int> com(const vector<vector<int>>& s) {
    // 根据问题的具体情况合并
    vector<int> r;
    for (const auto& sub : s) {
        r.insert(r.end(), s.begin(), s.end());
    }
    return r;
}
// 分治算法主函数
vector<int> d(const vector<int>& p) {
    if (is(p))
        return solve(p);
    vector<vector<int>> su = di(p);
    vector<vector<int>> s;
    for (const auto& subproblem : subproblems) {
        su.push_back(di(s));
    }

    return com(s);
}
2.快速排序的具体实现(C++)
#include <iostream>
#include <vector>
using namespace std;
// 分区操作
int pa(vector<int>& arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    for (int j = low; j < high; j++)
        if (arr[j] < pivot) {
            i++;
            swap(arr[i], arr[j]);
        }
    swap(arr[i + 1], arr[high]);
    return i + 1;
}
// 快速排序主函数
void qu(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = pa(arr, low, high);
        qu(arr, low, pi - 1);  // 递归排序左半部分
        qu(arr, pi + 1, high); // 递归排序右半部分
    }
}
int main() {
    vector<int> arr = {10, 7, 8, 9, 1, 5};
    int n = arr.size();
    qu(arr, 0, n - 1);
    cout << "Sorted array: ";
    for (int i = 0; i < n; i++)
        cout << arr[i] << " ";
    cout << endl;
    return 0;
}

5.经典例题

1. [NOIP1998 普及组] 幂次方

题目描述

任何一个正整数都可以用 2 的幂次方表示。例如 137=2^7+2^3+2^0 。同时约定次方用括号来表示,即 a^b 可表示为 a(b)。由此可知,137 可表示为 2(7)+2(3)+2(0)

进一步:7= 2^2+2+2^0  ( 2^1 用 2 表示),并且 3=2+2^0。所以最后 137 可表示为 2(2(2)+2+2(0))+2(2+2(0))+2(0)。又如 1315=2^10 +2^8 +2^5 +2+1

所以 1315 最后可表示为 2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)。

输入格式

一行一个正整数 n。

输出格式

符合约定的 n 的 0, 2 表示(在表示中不能有空格)。

样例 #1

样例输入 #1
1315

样例输出 #1
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)

提示

【数据范围】对于 100% 的数据,1 < n < 2 *10^4。

NOIP1998 普及组 第三题

参考代码:

#include <iostream>
using namespace std;

// 定义全局变量 n
int n;

// 计算 m 的二进制表示中最高位 1 的位置(即 log2(m))
int log(int m) {
    int cnt = 0;
    // 不断右移 m,直到 m 变为 1
    while (m != 1) {
        m >>= 1; // 右移一位
        cnt++;   // 计数器加一
    }
    return cnt; // 返回计数器的值
}

// 递归解决将 n 表示为 2 的幂次和的形式
void solve(int n) {
    if (n == 0) {
        cout << "0"; // 如果 n 为 0,直接输出 0
    } else if (n == 2) {
        cout << "2"; // 如果 n 为 2,直接输出 2
    } else if (n == 3) {
        cout << "2+2(0)"; // 如果 n 为 3,输出 2+2(0)
    } else {
        int l = log(n); // 计算 n 的最高位 1 的位置
        cout << "2("; // 输出 2(
        solve(l); // 递归处理 l
        cout << ")"; // 输出 )
        n -= (1 << l); // 减去 2^l
        if (n) {
            cout << "+"; // 如果 n 还有剩余,输出 +
            solve(n); // 递归处理剩余的 n
        }
    }
    return;
}

int main() {
    cin >> n; // 读取输入的 n
    solve(n); // 调用 solve 函数处理 n
    return 0;
}
2. [NOIP2001 提高组] 一元三次方程求解

题目描述

有形如:a x^3 + b x^2 + c x + d = 0  这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 -100 至 100 之间),且根与根之差的绝对值 >1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 位。

提示:记方程 f(x) = 0,若存在 2 个数 x1 和 x2,且 x1<x2,f(x1)  f(x2) < 0,则在 (x1, x2) 之间一定有一个根。

输入格式

一行,4 个实数 a, b, c, d。

输出格式

一行,3 个实根,从小到大输出,并精确到小数点后 2 位。

样例 #1

样例输入 #1
1 -5 -4 20

样例输出 #1
-2.00 2.00 5.00

提示

【题目来源】NOIP 2001 提高组第一题

#include <bits/stdc++.h>
using namespace std;

// 定义全局变量 a, b, c, d
double a, b, c, d;

int main() {
    // 从标准输入读取四个双精度浮点数 a, b, c, d
    cin >> a >> b >> c >> d;

    // 使用 for 循环在区间 [-100, 100] 内以 0.001 的步长遍历所有可能的 i 值
    for (double i = -100; i <= 100; i += 0.001) {
        // 计算多项式 f(i) = a*i^3 + b*i^2 + c*i + d 的值
        double value = i * i * i * a + i * i * b + i * c + d;

        // 判断 f(i) 是否接近于 0(误差小于 0.0001)
        if (fabs(value) < 0.0001) {
            // 如果 f(i) 接近于 0,输出当前的 i 值
            cout << i << endl;
        }
    }

    return 0;
}
3.最大子段和

题目描述

给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个整数,表示序列的长度 n。

第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字 ai。

输出格式

输出一行一个整数表示答案。

样例 #1

样例输入 #1
7
2 -4 3 -1 2 -4 3

样例输出 #1
4

提示

样例 1 解释

选取 [3, 5] 子段 {3, -1, 2},其和为 4。

数据规模与约定

- 对于 40% 的数据,保证 n <= 2 *10^3。
- 对于 100% 的数据,保证 1 <= n <= 2 *10^5,-10^4 <= ai<=10^4。

#include <bits/stdc++.h>
using namespace std;

// 定义两个数组 a 和 s,用于存储输入数据和前缀和
long long a[200005], s[200005];

int main() {
    // 读取输入的整数 n,表示数组的长度
    long long n;
    cin >> n;

    // 初始化最大子数组和为一个很小的负数
    long long sum = -1000000;

    // 读取数组 a 的元素
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    // 计算前缀和数组 s,并更新最大子数组和
    for (int i = 1; i <= n; i++) {
        // 计算当前前缀和 s[i]
        s[i] = s[i - 1] + a[i];

        // 如果当前元素 a[i] 大于当前前缀和 s[i],则更新 s[i] 为 a[i]
        if (a[i] > s[i]) {
            s[i] = a[i];
        }

        // 更新最大子数组和 sum
        if (sum < s[i]) {
            sum = s[i];
        }
    }

    // 输出最大子数组和
    cout << sum;

    return 0;
}
### 常见数据结构 #### 数据结构定义 数据结构是一种特定的方式,用于组织和存储计算机中的数据以便高效访问和修改。常见的数据结构包括但不限于数组、链表、栈、队列、哈希表、树(如二叉树)、图等[^2]。 #### 数组 数组是一个连续内存区域,其中的元素可以通过索引快速访问。其特点是随机存取时间复杂度为O(1),但在中间插入或删除操作的时间复杂度较高,通常为O(n)[^1]。 ```python # 创建并初始化一个简单的整型数组 arr = [1, 2, 3, 4, 5] # 访问第3个元素 element = arr[2] print(element) # 输出:3 ``` #### 链表 链表由一系列节点组成,每个节点包含数据部分和指向下一个节点的链接部分。链表适合频繁插入和删除的操作场景,但不支持随机访问。 ```python class Node: def __init__(self, data=None): self.data = data self.next = None # 初始化链表头结点 head = Node(1) second = Node(2) # 连接两个节点 head.next = second ``` ### 常见算法及其示例子题解析 #### 排序算法 排序是对一组对象按照某种顺序排列的过程。以下是几种经典的排序算法: ##### 快速排序 (Quick Sort) 快速排序采用分治策略来实现排序。它的核心思想是选取一个基准值,将待排序序列划分为两部分,一部分比基准小,另一部分比基准大,再分别对这两部分递归调用快速排序。 ```python def quick_sort(arr): if len(arr) <= 1: return arr else: pivot = arr[len(arr) // 2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quick_sort(left) + middle + quick_sort(right) sorted_array = quick_sort([3,6,8,10,1,2,1]) print(sorted_array) # 输出:[1, 1, 2, 3, 6, 8, 10] ``` ##### 归并排序 (Merge Sort) 归并排序也是一种基于分治法的排序方法。它将原始列表分割成较小子列表直到每个子列表只有一个元素,然后把这些有序子列表反复合并得到最终结果。 ```python def merge_sort(arr): if len(arr) <= 1: return arr mid = len(arr) // 2 left_half = merge_sort(arr[:mid]) right_half = merge_sort(arr[mid:]) return merge(left_half, right_half) def merge(left, right): sorted_arr = [] while left and right: if left[0] < right[0]: sorted_arr.append(left.pop(0)) else: sorted_arr.append(right.pop(0)) sorted_arr.extend(left or right) return sorted_arr result = merge_sort([9,7,5,11,12,2,14,3,10,6]) print(result) # 输出:[2, 3, 5, 6, 7, 9, 10, 11, 12, 14] ``` #### 查找算法 查找是在集合中寻找某个特定项的过程。下面介绍一种高效的查找技术——二分搜索。 ##### 改进版二分搜索 对于已排序数组`a[0:n-1]`,改进后的二分搜索不仅能够找到目标值的位置,还能在找不到的情况下返回小于该值的最大元素位置`i`和大于该值的最小元素位置`j`[^3]。 ```python def modified_binary_search(a, x): low, high = 0, len(a)-1 i, j = -1, -1 while low <= high: mid = (low + high) // 2 if a[mid] == x: return mid, mid elif a[mid] < x: i = mid low = mid + 1 else: j = mid high = mid - 1 return i, j array = [1, 3, 5, 7, 9, 11] target = 6 index_i, index_j = modified_binary_search(array, target) print(f"Index of largest element less than {target}: {index_i}") print(f"Index of smallest element greater than {target}: {index_j}") # 输出:Index of largest element less than 6: 2 # Index of smallest element greater than 6: 3 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值