【CS.AL】算法核心之分治算法:从入门到进阶

1000.01.CS.AL.1.4-核心-DivedeToConquerAlgorithm-Created: 2024-06-15.Saturday09:35
在这里插入图片描述

1. 概述

分治算法(Divide and Conquer)是一种重要的算法设计思想,其核心思想是将一个复杂的问题分解为多个相对简单的小问题,通过解决这些小问题再合并其结果,从而得到原问题的解。分治算法的典型特征是递归,常用于求解具有重复子问题性质的问题。

2. 适用场景

分治算法适用于以下场景:

  • 问题可以分解为若干个规模较小且相互独立的子问题。
  • 这些子问题的解可以合并得到原问题的解。
  • 具有最优子结构性质,即子问题的最优解可以合成原问题的最优解。

3. 设计步骤

  1. 分解(Divide):将原问题分解为若干个子问题,这些子问题的结构与原问题相同但规模较小。
  2. 解决(Conquer):递归地解决这些子问题。当子问题的规模足够小时,直接解决。
  3. 合并(Combine):将子问题的解合并,得到原问题的解。

4. 优缺点

  • 优点:分治算法的递归思想使其在解决许多复杂问题时表现出色。具有良好的可扩展性和并行计算的潜力。
  • 缺点:对于某些问题,分治算法可能会引入额外的开销,如递归调用栈和合并步骤的时间复杂度。

5. 典型应用

  • 归并排序(Merge Sort):一种高效的排序算法,利用分治思想将数组分为两半,递归地排序并合并。
  • 快速排序(Quick Sort):另一种高效的排序算法,选择一个基准元素,将数组分为两部分,递归排序。
  • 最近点对问题(Closest Pair Problem):在平面上找到距离最近的两点,分治法可以将时间复杂度降至 O(nlog⁡n)O(n \log n)O(nlogn)。
  • 矩阵乘法(Strassen’s Algorithm):一种用于矩阵乘法的分治算法,降低了时间复杂度。

6. 题目和代码示例

6.1 简单题目:归并排序

题目描述:实现归并排序算法,对给定的数组进行排序。

代码示例

#include <iostream>
#include <vector>

// 函数声明
void mergeSort(std::vector<int>& arr, int left, int right);
void merge(std::vector<int>& arr, int left, int mid, int right);

int main() {
    std::vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
    mergeSort(arr, 0, arr.size() - 1);
    for (int num : arr) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

// 归并排序:递归地将数组分成两半进行排序
void mergeSort(std::vector<int>& arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }
}

// 合并两个已排序的子数组
void merge(std::vector<int>& arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;
    std::vector<int> L(n1), R(n2);

    for (int i = 0; i < n1; ++i) {
        L[i] = arr[left + i];
    }
    for (int j = 0; j < n2; ++j) {
        R[j] = arr[mid + 1 + j];
    }

    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i++];
        } else {
            arr[k] = R[j++];
        }
        ++k;
    }

    while (i < n1) {
        arr[k++] = L[i++];
    }
    while (j < n2) {
        arr[k++] = R[j++];
    }
}

Others.

def merge_sort(arr):
    if len(arr) > 1:
        mid = len(arr) // 2
        left_half = arr[:mid]
        right_half = arr[mid:]

        merge_sort(left_half)
        merge_sort(right_half)

        i = j = k = 0

        while i < len(left_half) and j < len(right_half):
            if left_half[i] < right_half[j]:
                arr[k] = left_half[i]
                i += 1
            else:
                arr[k] = right_half[j]
                j += 1
            k += 1

        while i < len(left_half):
            arr[k] = left_half[i]
            i += 1
            k += 1

        while j < len(right_half):
            arr[k] = right_half[j]
            j += 1
            k += 1

# 示例
arr = [38, 27, 43, 3, 9, 82, 10]
merge_sort(arr)
print(arr)  # 输出: [3, 9, 10, 27, 38, 43, 82]

6.2 中等题目:最近点对问题

题目描述:在平面上找到距离最近的两点,时间复杂度为 O(nlog⁡n)。

代码示例

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

struct Point {
    int x, y;
};

double dist(const Point& p1, const Point& p2) {
    return std::sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}

double closestPair(std::vector<Point>& points, int left, int right) {
    if (right - left <= 3) {
        double minDist = std::numeric_limits<double>::infinity();
        for (int i = left; i < right; ++i) {
            for (int j = i + 1; j <= right; ++j) {
                minDist = std::min(minDist, dist(points[i], points[j]));
            }
        }
        return minDist;
    }

    int mid = left + (right - left) / 2;
    double d1 = closestPair(points, left, mid);
    double d2 = closestPair(points, mid + 1, right);
    double d = std::min(d1, d2);

    std::vector<Point> strip;
    for (int i = left; i <= right; ++i) {
        if (std::abs(points[i].x - points[mid].x) < d) {
            strip.push_back(points[i]);
        }
    }

    std::sort(strip.begin(), strip.end(), [](const Point& p1, const Point& p2) {
        return p1.y < p2.y;
    });

    double minDist = d;
    for (size_t i = 0; i < strip.size(); ++i) {
        for (size_t j = i + 1; j < strip.size() && (strip[j].y - strip[i].y) < minDist; ++j) {
            minDist = std::min(minDist, dist(strip[i], strip[j]));
        }
    }

    return minDist;
}

int main() {
    std::vector<Point> points = {{2, 3}, {12, 30}, {40, 50}, {5, 1}, {12, 10}, {3, 4}};
    std::sort(points.begin(), points.end(), [](const Point& p1, const Point& p2) {
        return p1.x < p2.x;
    });

    std::cout << "最近点对距离: " << closestPair(points, 0, points.size() - 1) << std::endl;
    return 0;
}

Others.

import math

def dist(p1, p2):
    return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)

def closest_pair(points):
    def closest_pair_recursive(points):
        if len(points) <= 3:
            return min((dist(p1, p2), (p1, p2)) for i, p1 in enumerate(points) for p2 in points[i+1:])[1]

        mid = len(points) // 2
        left_half = points[:mid]
        right_half = points[mid:]

        (d1, pair1) = closest_pair_recursive(left_half)
        (d2, pair2) = closest_pair_recursive(right_half)

        d = min(d1, d2)
        pair = pair1 if d1 < d2 else pair2

        strip = [p for p in points if abs(p[0] - points[mid][0]) < d]
        strip.sort(key=lambda p: p[1])

        for i in range(len(strip)):
            for j in range(i+1, len(strip)):
                if strip[j][1] - strip[i][1] >= d:
                    break
                d_new = dist(strip[i], strip[j])
                if d_new < d:
                    d = d_new
                    pair = (strip[i], strip[j])

        return d, pair

    points.sort(key=lambda p: p[0])
    return closest_pair_recursive(points)[1]

# 示例
points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (3, 4)]
print(closest_pair(points))  # 输出: ((2, 3), (3, 4))

6.3 困难题目:分数背包问题

题目描述:给定物品的重量和价值,求在背包容量限制下的最大价值,物品可以分割。

代码示例

#include <iostream>
#include <vector>
#include <algorithm>

struct Item {
    double value;
    double weight;
};

double fractionalKnapsack(std::vector<Item>& items, double capacity) {
    std::sort(items.begin(), items.end(), [](const Item& a, const Item& b) {
        return (a.value / a.weight) > (b.value / b.weight);
    });
    double totalValue = 0;

    for (const auto& item : items) {
        if (capacity >= item.weight) {
            capacity -= item.weight;
            totalValue += item.value;
        } else {
            totalValue += item.value * (capacity / item.weight);
            break;
        }
    }

    return totalValue;
}

int main() {
    std::vector<Item> items = {{60, 10}, {100, 20}, {120, 30}};
    double capacity = 50;
    std::cout << "背包的最大价值: " << fractionalKnapsack(items, capacity) << std::endl;
    return 0;
}

Others.

def fractional_knapsack(values, weights, capacity):
    items = list(zip(values, weights))
    items.sort(key=lambda x: x[0] / x[1], reverse=True)
    total_value = 0
    for value, weight in items:
        if capacity >= weight:
            capacity -= weight
            total_value += value
        else:
            total_value += value * (capacity / weight)
            break
    return total_value

# 示例
values = [60, 100, 120]
weights = [10, 20, 30]
capacity = 50
print(fractional_knapsack(values, weights, capacity))  # 输出: 240.0

7. 题目和思路表格

序号题目题目描述分治策略代码实现
1归并排序对给定的数组进行排序递归地将数组分成两半进行排序代码
2最近点对问题找到距离最近的两点将点集合递归地分成两半代码
3分数背包问题求在背包容量限制下的最大价值每次选择单位重量价值最高的物品代码
4快速排序高效排序算法选择一个基准元素,将数组分为两部分,递归排序代码
5矩阵乘法用于矩阵乘法将矩阵分成更小的子矩阵,递归计算-
6求逆序对数量统计数组中的逆序对个数使用分治法将数组分成两半,递归统计逆序对-
7最大子序和找出最大和的连续子序列将序列递归地分成两半,合并子序列的结果-
8大整数乘法实现高效的大整数乘法使用Karatsuba算法,将大整数分成两部分进行递归计算-
9二维平面上的最近点对在平面上找到距离最近的两点将点集合递归地分成两半,合并结果-
10棋盘覆盖问题用L型骨牌覆盖2^n * 2^n的棋盘将棋盘递归地分成四部分,覆盖部分棋盘-

8. 总结

分治算法是一种强大的算法设计思想,能够高效地解决许多复杂的问题。通过将问题分解为更小的子问题,分治算法不仅能够降低时间复杂度,还具有良好的可扩展性。在实际应用中,理解和掌握分治算法的思想和典型应用,对于解决各种问题具有重要意义。通过本文的例子和思路,相信读者能够深入理解分治算法的关键概念,并灵活应用于实际问题中。

References

  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值