100种算法【Python版】第2篇——分治法

news2024/10/22 15:09:28

分而治之

  • 1 分治法原理
  • 2 示例说明:归并排序
    • 2.1 分治法的步骤
    • 2.2 归并排序代码
  • 3 分治法应用
    • 3.1 最近点对问题
      • 3.1.1 Python3代码
      • 3.1.2 分治法思路说明
    • 3.2 快速傅里叶变换(FFT)
      • 3.2.1 Python3代码
      • 3.2.1 分治法思路说明
    • 3.3 最长公共子序列问题
      • 3.3.1 Python3代码
      • 3.3.2 分治法思路说明
  • 4 总结

1 分治法原理

分治法(Divide and Conquer)是一种广泛应用于算法设计的策略。它通过将一个复杂问题分解为若干个较小的子问题,递归地解决这些子问题,然后将这些子问题的解合并为原问题的解。分治法的基本步骤可以概括为以下几个阶段:

  • 分解(Divide)
    将原问题分解成多个小的、相似的子问题。这些子问题的规模通常比原问题要小。
  • 解决(Conquer)
    递归地解决这些子问题。如果子问题的规模足够小,可以直接解决。
  • 合并(Combine)
    将子问题的解合并成原问题的解。

2 示例说明:归并排序

问题:对一个数组 [38, 27, 43, 3, 9, 82, 10],进行排序。

2.1 分治法的步骤

  • 分解(Divide)
    将数组分为两半。例如分成 [38, 27, 43] 和 [3, 9, 82, 10]。
  • 解决(Conquer)
    递归地对每个子数组进行排序。
    • 对 [38, 27, 43]:
      再分为 [38] 和 [27, 43],对 [27, 43] 进一步分为 [27] 和 [43],然后合并成 [27, 43]。
    • 对 [3, 9, 82, 10]:
      分为 [3, 9] 和 [82, 10],分别排序后合并。
  • 合并(Combine)
    将已排序的子数组[27, 38, 43] 和 [3, 9, 10, 82] 合并。最终合并的顺序如下:
    [3, 9, 10, 27, 38, 43, 82]。

2.2 归并排序代码

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_array = []
    i = j = 0

    # 合并两个已排序的数组
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            sorted_array.append(left[i])
            i += 1
        else:
            sorted_array.append(right[j])
            j += 1

    # 添加剩余元素
    sorted_array.extend(left[i:])
    sorted_array.extend(right[j:])

    return sorted_array


# 示例使用
if __name__ == "__main__":
    arr = [38, 27, 43, 3, 3, 9, -82, 10]
    sorted_arr = merge_sort(arr)
    print("排序后的数组:", sorted_arr)

3 分治法应用

3.1 最近点对问题

问题描述:平面中存在一些点,计算欧几里得距离最近的两个点。

3.1.1 Python3代码

import math


def closest_pair(points):
    """
    查找最近点对
    :param points: 点的列表,每个点是一个元组 (x, y)
    :return: 最近点对的距离和对应的点
    """
    # 将点按 x 坐标排序
    points.sort(key=lambda point: point[0])
    return closest_pair_rec(points)


def closest_pair_rec(points):
    """
    递归查找最近点对
    :param points: 排序后的点列表
    :return: 最近点对的距离和对应的点
    """
    n = len(points)

    if n <= 3:  # 小于等于3个点时,使用暴力法
        return brute_force(points)

    mid = n // 2
    mid_point = points[mid]

    # 递归查找左右两侧的最近点对
    dl = closest_pair_rec(points[:mid])  # 左侧
    dr = closest_pair_rec(points[mid:])  # 右侧

    # 找到左侧和右侧的最小距离
    d = min(dl[0], dr[0])

    # 创建一个带状区域
    strip = [point for point in points if abs(point[0] - mid_point[0]) < d]

    # 在带状区域中查找最近点对
    return min(dl, dr, closest_in_strip(strip, d), key=lambda x: x[0])


def brute_force(points):
    """
    暴力法查找最近点对
    :param points: 点的列表
    :return: 最近点对的距离和对应的点
    """
    min_dist = float('inf')
    closest_points = None

    for i in range(len(points)):
        for j in range(i + 1, len(points)):
            dist = distance(points[i], points[j])
            if dist < min_dist:
                min_dist = dist
                closest_points = (points[i], points[j])

    return min_dist, closest_points


def closest_in_strip(strip, d):
    """
    在带状区域中查找最近点对
    :param strip: 带状区域中的点
    :param d: 当前已知的最小距离
    :return: 最近点对的距离和对应的点
    """
    min_dist = d
    closest_points = None
    strip.sort(key=lambda point: point[1])  # 按 y 坐标排序

    for i in range(len(strip)):
        for j in range(i + 1, len(strip)):
            if strip[j][1] - strip[i][1] >= min_dist:  # 超过最小距离则停止
                break
            dist = distance(strip[i], strip[j])
            if dist < min_dist:
                min_dist = dist
                closest_points = (strip[i], strip[j])

    return min_dist, closest_points


def distance(p1, p2):
    """
    计算两点之间的欧几里得距离
    :param p1: 点1
    :param p2: 点2
    :return: 欧几里得距离
    """
    return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)


# 示例使用
if __name__ == "__main__":
    points = [(12.3, -30.2), (-40.5, 50.5), (5.0, 1.0), (2.5, 3.1), (7.5, -1.1), (3.4, -4.2)]
    min_distance, closest_points = closest_pair(points)
    print("最近点对的距离:", min_distance)
    print("最近点对的点:", closest_points)

3.1.2 分治法思路说明

  • 分解(Divide)
    • 分割点集:首先,将输入的点集按 x x x 坐标进行排序。然后将点集分为左右两个子集。这个分割的过程是通过计算中间点的索引来实现的。
    • 示例:对于点集 points,通过 mid = n // 2 找到中间点 mid_point,然后递归调用 closest_pair_rec(points[:mid]) 和 closest_pair_rec(points[mid:]) 来处理左侧和右侧的点集。
  • 解决(Conquer)
    • 递归求解:分别递归地计算左侧和右侧子集的最近点对。调用 closest_pair_rec 函数会继续将子集进一步分解,直到子集的大小小于等于 3。
    • 暴力法处理小子集:当点的数量小于等于 3 时,直接使用暴力法(brute_force 函数)计算最近点对的距离。这是因为对于小规模数据,暴力法的效率是可以接受的。
  • 合并(Combine)
    • 计算带状区域:在获得左右子集的最近点对距离后,取二者的最小值 d。接下来,创建一个带状区域(strip),该区域的宽度为 2d,包含可能的最近点对。
    • 带状区域内查找:在带状区域的点集中,通过调用 closest_in_strip 函数查找最近点对。这个步骤是合并的关键,因为它考虑了可能跨越中间线的点对。
  • 基本情况
    • 递归终止条件:在 closest_pair_rec 函数中,当点的数量小于等于 3 时,直接调用暴力法进行处理。这是递归的基本情况,确保算法能够终止。

3.2 快速傅里叶变换(FFT)

快速傅里叶变换(Fast Fourier Transform, FFT)是计算离散傅里叶变换(Discrete Fourier Transform, DFT)及其逆变换的高效算法。FFT 的主要目标是将时间域信号转换为频域表示,从而分析信号的频率成分。快速傅里叶变换是现代信号处理中的基础算法之一,广泛应用于信号处理、音频和图像处理、通信、科学计算和生物医学等多个领域。

问题描述:
给定一个长度为 N N N的序列 x [ n ] x[n] x[n],计算其离散傅里叶变换,得到频域表示 X [ k ] X[k] X[k]。离散傅里叶变换的公式为:
X [ k ] = ∑ n = 0 N − 1 x [ n ] ⋅ e − 2 π i n k N        ( k = 0 , 1 , 2 , … … , N − 1 ) X[k]=\sum_{n=0}^{N-1}x[n]\cdot e^{-2\pi i\frac{nk}{N}} \;\;\; (k=0, 1, 2, ……,N-1) X[k]=n=0N1x[n]e2πiNnk(k=0,1,2,……,N1)
其中:

  • X [ k ] X[k] X[k] 是频域信号的第 k k k 个分量
  • x [ n ] x[n] x[n] 是时域信号的第 n n n 个样本
  • N N N 是信号的总个数

3.2.1 Python3代码

import cmath


def fft(x):
    """
    快速傅里叶变换
    :param x: 输入信号,必须是 2 的幂次
    :return: 输入信号的傅里叶变换结果
    """
    N = len(x)
    if N <= 1:  # 基本情况:如果只有一个元素,直接返回
        return x

    # 分解:将输入信号分为偶数和奇数索引的元素
    even = fft(x[0::2])  # 偶数部分
    odd = fft(x[1::2])  # 奇数部分

    # 合并:计算傅里叶变换
    T = [cmath.exp(-2j * cmath.pi * k / N) * odd[k] for k in range(N // 2)]

    # 组合结果
    return [even[k] + T[k] for k in range(N // 2)] + \
        [even[k] - T[k] for k in range(N // 2)]


# 示例使用
if __name__ == "__main__":
    # 输入信号,长度为 8(2 的幂次)
    x = [0, 1, 0, 0, 0, 0, 0, 0]  # 示例输入信号
    print("输入信号:", x)
    y = fft(x)
    print("傅里叶变换结果:", y)

3.2.1 分治法思路说明

  • 函数 fft(x):
    • 输入信号 x 是一个列表,长度必须是 2 的幂次。
    • 当 N <= 1 时,直接返回输入信号作为基本情况。
    • 使用递归将信号分为偶数和奇数部分。
  • 分解:
    • even = fft(x[0::2]) 获取偶数索引的元素。
    • odd = fft(x[1::2]) 获取奇数索引的元素。
  • 合并:
    • 计算合并部分 T,使用复数指数函数 cmath.exp 来计算傅里叶变换的核心公式。
    • 最后将偶数部分与奇数部分的结果结合,形成最终的傅里叶变换结果。

3.3 最长公共子序列问题

最长公共子序列(Longest Common Subsequence, LCS)问题是指在给定的两个序列(通常是字符串)中,找出它们的最长公共子序列。子序列是指可以通过删除一些元素(可以不删除任何元素)而不改变其余元素顺序得到的序列。

问题描述:
给定两个序列 X 和 Y,求它们的最长公共子序列的长度,以及具体的子序列。

3.3.1 Python3代码

def lcs(X, Y):
    """
    基于分治法的最长公共子序列(LCS)实现
    :param X: 字符串 X
    :param Y: 字符串 Y
    :return: 最长公共子序列的长度和具体的子序列
    """
    length, sequence = lcs_helper(X, Y, len(X), len(Y))
    return length, sequence


def lcs_helper(X, Y, m, n):
    """
    递归求解 LCS 的辅助函数
    :param X: 字符串 X
    :param Y: 字符串 Y
    :param m: 字符串 X 的长度
    :param n: 字符串 Y 的长度
    :return: LCS 的长度和具体的子序列
    """
    # 基本情况:如果任一字符串为空,LCS 长度为 0,子序列为空
    if m == 0 or n == 0:
        return 0, ""

    # 如果最后一个字符相同,递归求解剩余部分
    if X[m - 1] == Y[n - 1]:
        length, sequence = lcs_helper(X, Y, m - 1, n - 1)
        return length + 1, sequence + X[m - 1]

    # 如果最后一个字符不同,递归求解去掉最后一个字符的两种情况
    else:
        left_length, left_sequence = lcs_helper(X, Y, m, n - 1)
        right_length, right_sequence = lcs_helper(X, Y, m - 1, n)

        # 返回较长的序列及其长度
        if left_length > right_length:
            return left_length, left_sequence
        else:
            return right_length, right_sequence


# 示例使用
if __name__ == "__main__":
    X = "AGGTA088*B"
    Y = "GXTXA087Y*B"
    length, sequence = lcs(X, Y)
    print("最长公共子序列的长度:", length)
    print("最长公共子序列的值:", sequence)

3.3.2 分治法思路说明

  • 分解(Divide)
    • 将问题分解为更小的子问题。对于两个字符串 X X X Y Y Y,考虑它们的最后一个字符:
      • 如果 X [ m − 1 ] X[m-1] X[m1] Y [ n − 1 ] Y[n-1] Y[n1] 相同,则这个字符必定属于$ LCS$。此时问题可以转化为求解 l c s ( X [ 0 : m − 1 ] , Y [ 0 : n − 1 ] ) lcs(X[0:m-1], Y[0:n-1]) lcs(X[0:m1],Y[0:n1]),即去掉这两个字符后的 L C S LCS LCS
      • 如果 X [ m − 1 ] X[m-1] X[m1] Y [ n − 1 ] Y[n-1] Y[n1] 不同,则需要考虑两种情况:
        (1)去掉 X X X 的最后一个字符,求解 l c s ( X [ 0 : m − 1 ] , Y [ 0 : n ] ) lcs(X[0:m-1], Y[0:n]) lcs(X[0:m1],Y[0:n])
        (2)去掉 Y Y Y 的最后一个字符,求解 l c s ( X [ 0 : m ] , Y [ 0 : n − 1 ] ) lcs(X[0:m], Y[0:n-1]) lcs(X[0:m],Y[0:n1])
  • 解决(Conquer)
  • 对每个分解出的子问题递归调用 l c s _ h e l p e r lcs\_helper lcs_helper 函数。每次调用根据最后一个字符的匹配情况决定下一步的递归路径:
    - 如果字符相同,继续求解去掉这两个字符后的 L C S LCS LCS
    - 如果字符不同,分别计算去掉一个字符后的两种情况。
  • 合并(Combine)
    • 在递归返回时,结合子问题的解以构建最终的 L C S LCS LCS
      • 当两个字符相同时, L C S LCS LCS 长度加 1,并将这个字符添加到 L C S LCS LCS 中。
      • 当字符不同时,取去掉一个字符的两种情况中较长的 L C S LCS LCS 作为结果。
  • 基本情况
    递归的基本情况是当任一字符串的长度为 0 时, L C S LCS LCS 的长度为 0,且子序列为空。这是递归终止的条件。

4 总结

分治法是一种解决问题的方法,基本思路是把一个复杂的问题拆分成几个简单的小问题,先解决这些小问题,然后把它们的结果合起来,得到原问题的答案。

(1)优点

  • 节省时间:很多问题通过分治法可以把时间复杂度大幅度降低。
  • 并行处理:因为小问题可以独立解决,所以适合用在并行计算中。

(2)缺点

  • 递归深度:如果递归层数太多,可能会导致栈溢出,尤其是在处理超大数据时。
  • 合并复杂:有些问题在合并结果时可能比较复杂,难以实现。

总结:

以下是适合使用分治法解决的问题的特点:

  • 可分解性
    • 问题可以拆分:这些问题能够被拆分成多个较小的子问题,且这些子问题通常具有相似的结构。
    • 递归性质:每个子问题可以通过相同的方法继续拆分,直到达到基本情况。
  • 独立性
    • 子问题独立:子问题的解决通常不依赖于其他子问题的结果,这使得它们可以独立地被解决。
    • 并行计算:因为子问题是独立的,分治法很适合并行处理,能够利用多核处理器的优势。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220874.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习中,师傅b站泷羽sec——xss挖掘过程

某职业技术学院网站xss挖掘&#xff1a; 资产归纳 例如&#xff1a;先把功能点都看一遍&#xff0c;大部分都是文章 根据信息搜集第一课学习到一般主站的防御力是比较强的&#xff0c;出现漏洞的点不是对新手不友好。 在资产验证过程中还是把主站看了一遍 没有发现有攻击的机会…

G1 GAN生成MNIST手写数字图像

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 G1 GAN生成MNIST手写数字图像 1. 生成对抗网络 (GAN) 简介 生成对抗网络 (GAN) 是一种通过“对抗性”学习生成数据的深度学习模型&#xff0c;通常用于生成…

如何调试浏览器中的内存泄漏?

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介⭐ 如何调试浏览器中的内存泄漏&#xff1f;1. 什么是内存泄漏&#xff1f;2. 调试内存泄漏的工具3. 如何使用 Memory 面板进行内存调试3.1 获取内存快照&#xff08;Heap Snapshot&#xff09;获取内存快照的步骤&#xff1a;快照…

即时通讯增加Redis渠道

情况说明 在本地和服务器分别启动im服务&#xff0c;当本地发送消息时&#xff0c;会发现服务器上并没有收到消息 初版im只支持单机版&#xff0c;不支持分布式的情况。此次针对该情况对项目进行优化,文档中贴出的代码非完整代码&#xff0c;可自行查看参考资料[2] 代码结构调…

C Primer Plus 第9章——第一篇

你该逆袭了 文章目录 一、复习函数1、定义带形式参数的函数2、声明带形式参数函数的原型3、使用 return 从函数中返回值&#xff08;1&#xff09;、返回值不仅可以赋给变量&#xff0c;也可以被用作表达式的一部分。&#xff08;2&#xff09;、返回值不一定是变量的值&#x…

springboot redisTemplate hash 序列化探讨

前提提要&#xff1a;这个是个人小白总结&#xff0c;写完博客后开始厌蠢。 redisTemplate 有两种插入hash的方式 redisTemplate.opsForHash().putAll(key, map);redisTemplate.opsForHash().put(key, field, value);在使用的过程中&#xff0c;难免会疑问为什么 key field v…

Windows下部署autMan

一、安装autMan 下载autMan压缩包 https://github.com/hdbjlizhe/fanli/releases 解压安装包 二、运行&#xff08;注意&#xff0c;无论是交互运行还是静默运行&#xff0c;终端均不可关闭&#xff09; 基本运行 双击autMan.exe运行。 高级运行 在autMan文件夹&#xff0…

Sigrity Power SI Model Extraction模式如何提取电源网络的S参数和阻抗操作指导(一)

Sigrity Power SI Model Extraction模式如何提取电源网络的S参数和阻抗操作指导(一) Sigrity PowerSI是频域电磁场仿真工具,以下图为例介绍如果用它观测电源的网络的S参数以及阻抗的频域曲线. 观测IC端电源网络的自阻抗 1. 用powerSi.exe打开该SPD文件

工业相机详解及选型

工业相机相对于传统的民用相机而言&#xff0c;具有搞图像稳定性,传输能力和高抗干扰能力等&#xff0c;目前市面上的工业相机大多数是基于CCD&#xff08;Charge Coupled Device)或CMOS(Complementary Metal Oxide Semiconductor)芯片的相机。 一&#xff0c;工业相机的分类 …

sentinel原理源码分析系列(六)-统计指标

调用链和统计节点构建完成&#xff0c;进入统计指标插槽&#xff0c;统计指标在最后执行的&#xff0c;等后面的插槽执行完&#xff0c;资源调用完成了&#xff0c;根据资源调用情况累计。指标统计是最重要的插槽&#xff0c;所有的功能都依靠指标数据&#xff0c;指标的正确与…

你知道什么叫数控加工中心吗?

加工中心是一种高度机电一体化的数控机床&#xff0c;具有刀库&#xff0c;自动换刀功能&#xff0c;对工件一次装夹后进行多工序加工的数控机床。通过计算的控制系统和稳定的机械结构&#xff0c;加工中心能够实现高精度的加工&#xff0c;确保工件的尺寸精度和表面质量。通过…

实用好助手

在现代职场中&#xff0c;拥有高效且适用的工具能够显著提升我们的工作效率与质量。除了常见的办公软件&#xff0c;还有许多小众但非常实用的工具可以大幅度优化工作流程。以下是另外五个推荐的工作软件&#xff0c;它们各自具备独特的功能与优势&#xff0c;值得一试。 1 …

【Docker系列】在 Docker 容器中打印和配置环境变量

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

双十一有哪些值得买的东西?2024年最全双十一好物推荐榜单来了!

双十一能够入手的好东西那肯定是非常多的&#xff0c;不过要想买到性价比高、实用性强的好物&#xff0c;就必须得做些功课了。作为一个智能家居和数码领域的博主&#xff0c;自然知道每年双十一买什么是最划算的。如果有朋友正在为双十一不知道买什么而发愁&#xff0c;那就快…

python+大数据+基于热门视频的数据分析研究【内含源码+文档+部署教程】

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ &#x1f345;由于篇幅限制&#xff0c;想要获取完整文章或者源码&#xff0c;或者代做&am…

登录后端笔记(一):注册、登录;基于MD5加密

一、注册 一、参数&#xff1a;lombok pom.xml里引入依赖&#xff1b; 二、响应数据&#xff1a;Result 原视频 两个注解对应有参无参生成构造方法&#xff1b; data类型是泛型T&#xff0c;即data在使用时可对应object可对应string字符串可对应bean对象可对应map等&#x…

微信碰一碰支付系统有哪些好的?教程详解抢先看!

支付宝“碰一碰支付”的风刚刚刮起来&#xff0c;它的老对手微信便紧随其后&#xff0c;推出了自己的碰一碰支付设备&#xff0c;再次印证了这个项目市场前景广阔的同时&#xff0c;也让与碰一碰支付系统相关问题的热度又上了一层楼&#xff0c;尤其是微信碰一碰支付系统有哪些…

炒股VS炒游戏装备,哪个更好做

这个项目&#xff0c;赚个10%都是要被嫌弃的 虽然天天都在抒发自己对股市的看法&#xff0c;但自己自始至终也没有买进任何一支股票。之所以对这个话题感兴趣&#xff0c;着实是因为手上的游戏搬砖项目也是国际性买卖&#xff0c;跟国际形势&#xff0c;国际汇率挂钩&#xff0…

RAG总结及前沿之Meta-Chunking切分思路及VisRAG多模态实现机制解读

今天我们来看两个工作&#xff0c;一个是关于RAG的切分策略&#xff0c;Meta-Chunking&#xff0c;里面基于数学常识提到的边际采样分块&#xff08;Margin Sampling Chunking&#xff09;通过LLMs对连续句子是否需要分割进行二元分类&#xff0c;基于边际采样得到的概率差异来…

基于SSM+微信小程序的房屋租赁管理系统(房屋2)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的房屋租赁管理系统实现了有管理员、中介和用户。 1、管理员功能有&#xff0c;个人中心&#xff0c;用户管理&#xff0c;中介管理&#xff0c;房屋信息管理&#xff…