c++ 分治算法

news2024/11/9 2:23:01

分治算法(Divide and Conquer)

分治算法是一种重要的算法设计范式,其核心思想是将一个复杂的问题分解为多个规模较小的相同问题,逐步解决这些较小的问题,最后将这些问题的解合并成原问题的解。分治算法通常包含三个步骤:

  1. 分解(Divide):将问题分解成几个子问题,子问题应该是原问题的规模较小的版本。
  2. 解决(Conquer):递归地解决每个子问题。如果子问题足够小,则直接解决。
  3. 合并(Combine):将子问题的解合并成原问题的解。

应用

分治算法在很多经典问题中都得到了应用,以下是几个典型的应用:

  • 归并排序(Merge Sort)
  • 快速排序(Quick Sort)
  • 最大子数组和问题(Maximum Subarray Problem)
  • 大数相乘(Karatsuba Algorithm)
  • 求逆序对的个数

归并排序(Merge Sort)

归并排序是一个典型的分治算法应用。其时间复杂度是 O(nlogn),比冒泡排序和插入排序等算法更高效。

归并排序的步骤:

  1. 分解:将数组分成两个子数组,递归排序这两个子数组。
  2. 合并:将两个已经排序的子数组合并成一个有序的数组。

代码实现(c++)

#include <iostream>
#include <vector>

using namespace std;

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

    vector<int> leftArr(n1), rightArr(n2);

    // 将数据拷贝到临时数组
    for (int i = 0; i < n1; ++i) {
        leftArr[i] = arr[left + i];
    }
    for (int j = 0; j < n2; ++j) {
        rightArr[j] = arr[mid + 1 + j];
    }

    int i = 0, j = 0, k = left;
    // 合并两个子数组
    while (i < n1 && j < n2) {
        if (leftArr[i] <= rightArr[j]) {
            arr[k++] = leftArr[i++];
        } else {
            arr[k++] = rightArr[j++];
        }
    }

    // 将剩余的元素拷贝到原数组
    while (i < n1) {
        arr[k++] = leftArr[i++];
    }
    while (j < n2) {
        arr[k++] = rightArr[j++];
    }
}

// 归并排序主函数
void mergeSort(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);  // 合并
    }
}

int main() {
    vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
    
    cout << "原数组: ";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    mergeSort(arr, 0, arr.size() - 1);  // 排序
    
    cout << "排序后数组: ";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

代码说明

  • mergeSort函数:递归地分解数组,将其拆分成小的子数组,直到每个子数组只有一个元素或为空。
  • merge函数:用于合并两个已经排序的子数组,保证合并后形成一个新的排序数组。
  • main函数中,定义了一个整数数组arr,然后调用mergeSort对数组进行排序。

复杂度分析

  • 时间复杂度:归并排序的时间复杂度是 O(nlogn),其中 n是数组的大小。每次分解数组的过程需要 O(logn) 层,每层的合并操作需要 O(n)的时间。

  • 空间复杂度:归并排序需要额外的空间来存储临时数组,所以空间复杂度是 O(n))。

快速排序(Quick Sort)

快速排序是另一种基于分治思想的排序算法,其时间复杂度的平均情况也是 O(nlog⁡n),但最坏情况下可能是 O(n^2)。

代码实现(c++)

#include <iostream>
#include <vector>

using namespace std;

// 分区函数
int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[high];  // 选择最后一个元素作为基准
    int i = low - 1;  // i 是较小元素的索引

    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 quickSort(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);  // 找到基准元素的位置

        quickSort(arr, low, pi - 1);  // 排序基准元素左边的部分
        quickSort(arr, pi + 1, high); // 排序基准元素右边的部分
    }
}

int main() {
    vector<int> arr = {38, 27, 43, 3, 9, 82, 10};

    cout << "原数组: ";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    quickSort(arr, 0, arr.size() - 1);  // 排序

    cout << "排序后数组: ";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

复杂度分析

  • 最坏情况:O(n^2)(选择的基准元素极差,如每次选择最小或最大元素)

  • 最好情况:O(nlog⁡n)(每次选中中位数基准,数组均匀划分)

  • 平均情况:O(nlog⁡n)(常见情况,基准元素大致均匀分布)

  • 空间复杂度:O(log⁡n) 到 O(n),取决于递归栈深度

最大子数组和问题(Maximum Subarray Problem)

给定一个整数数组,要求找出一个连续子数组,使得其元素和最大。这个问题可以通过分治算法来求解。

代码实现(c++)

#include <iostream>
#include <vector>
#include <algorithm> // 用于max

using namespace std;

// 求解最大子数组和
int maxCrossingSum(const vector<int>& arr, int left, int mid, int right) {
    int left_sum = INT_MIN, right_sum = INT_MIN;
    int sum = 0;

    // 从中点向左扫描,找出最大子数组和
    for (int i = mid; i >= left; --i) {
        sum += arr[i];
        left_sum = max(left_sum, sum);
    }

    sum = 0;
    // 从中点向右扫描,找出最大子数组和
    for (int i = mid + 1; i <= right; ++i) {
        sum += arr[i];
        right_sum = max(right_sum, sum);
    }

    return left_sum + right_sum; // 返回跨越中点的最大子数组和
}

int maxSubArraySum(const vector<int>& arr, int left, int right) {
    if (left == right) {
        return arr[left]; // 基本情况:只有一个元素
    }

    int mid = left + (right - left) / 2; // 找到中点

    // 分治递归计算左半部分、右半部分以及跨越中点的最大子数组和
    int left_sum = maxSubArraySum(arr, left, mid);
    int right_sum = maxSubArraySum(arr, mid + 1, right);
    int cross_sum = maxCrossingSum(arr, left, mid, right);

    return max({left_sum, right_sum, cross_sum}); // 返回三者中的最大值
}

int main() {
    vector<int> arr = {-2, 1, -3, 4, -1, 2, 1, -5, 4};

    cout << "原数组: ";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    int n = arr.size();
    int max_sum = maxSubArraySum(arr, 0, n - 1);

    cout << "最大子数组和是: " << max_sum << endl;

    return 0;
}

代码说明

  • maxCrossingSum函数:用于计算跨越中点的子数组和。它首先从中点向左扫描,找到最大左半部分的和,再从中点向右扫描,找到最大右半部分的和。最后返回两个部分的和。

  • maxSubArraySum函数:递归地计算数组的最大子数组和。它通过不断将数组分成两部分来求解,并合并结果。

复杂度分析

  • 时间复杂度

    • 每次分割数组的时间复杂度是 O(n),因为我们需要计算跨越中点的子数组和。

    • 每次递归的深度是 log⁡n,因为每次将数组分成两半。

      因此,分治法的时间复杂度为 O(nlog⁡n)。

  • 空间复杂度

    • 由于使用递归,空间复杂度主要由递归栈深度决定,最深递归深度为 log⁡n。
    • 因此空间复杂度为 O(log⁡n)。

逆序对问题(Inversion Count Problem)

逆序对的定义是,对于一个数组中的两个元素,若前面的元素大于后面的元素,则它们构成一个逆序对。分治算法可以有效地计算数组中的逆序对数。

代码实现(c++)

#include <iostream>
#include <vector>

using namespace std;

// 合并两个已排序的子数组,同时计算逆序对
int mergeAndCount(vector<int>& arr, int left, int mid, int right) {
    int count = 0;
    int n1 = mid - left + 1;
    int n2 = right - mid;
    
    // 创建临时数组
    vector<int> leftArr(n1), rightArr(n2);

    // 拷贝数据到临时数组
    for (int i = 0; i < n1; ++i) {
        leftArr[i] = arr[left + i];
    }
    for (int i = 0; i < n2; ++i) {
        rightArr[i] = arr[mid + 1 + i];
    }

    // 合并两个排序好的子数组,同时统计逆序对
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (leftArr[i] <= rightArr[j]) {
            arr[k++] = leftArr[i++];
        } else {
            arr[k++] = rightArr[j++];
            count += (n1 - i); // 右数组的当前元素小于左数组的所有剩余元素
        }
    }

    // 将剩余元素拷贝到原数组
    while (i < n1) {
        arr[k++] = leftArr[i++];
    }
    while (j < n2) {
        arr[k++] = rightArr[j++];
    }

    return count;
}

// 递归分治函数
int mergeSortAndCount(vector<int>& arr, int left, int right) {
    int count = 0;
    if (left < right) {
        int mid = left + (right - left) / 2;
        count += mergeSortAndCount(arr, left, mid); // 左半部分
        count += mergeSortAndCount(arr, mid + 1, right); // 右半部分
        count += mergeAndCount(arr, left, mid, right); // 合并并计算逆序对
    }
    return count;
}

int main() {
    vector<int> arr = {1, 20, 6, 4, 5};

    cout << "原数组: ";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    int n = arr.size();
    int inv_count = mergeSortAndCount(arr, 0, n - 1);

    cout << "数组中的逆序对数: " << inv_count << endl;

    return 0;
}

代码说明

  • mergeAndCount函数:在合并两个已排序的子数组时,统计逆序对。当右侧子数组的元素小于左侧子数组的元素时,所有左侧子数组的剩余元素都会与该右侧元素形成逆序对。

  • mergeSortAndCount函数:递归地分治计算逆序对数,并调用mergeAndCount来合并并计算逆序对。

复杂度分析

  • 时间复杂度为 O(nlog⁡n)
  • **空间复杂度为 **O(n)

递归矩阵乘法(Divide and Conquer Matrix Multiplication)

矩阵乘法的传统方法是每个元素通过行列相乘来计算,时间复杂度为 O(n^3)。但是可以使用分治法来优化矩阵乘法的实现。

代码实现(c++)

#include <iostream>
#include <vector>

using namespace std;

// 矩阵乘法
void matrixMultiply(const vector<vector<int>>& A, const vector<vector<int>>& B, vector<vector<int>>& C, int n) {
    if (n == 1) {
        C[0][0] = A[0][0] * B[0][0];
        return;
    }

    int mid = n / 2;

    // 创建四个子矩阵
    vector<vector<int>> A11(mid, vector<int>(mid)), A12(mid, vector<int>(mid)),
                        A21(mid, vector<int>(mid)), A22(mid, vector<int>(mid));
    vector<vector<int>> B11(mid, vector<int>(mid)), B12(mid, vector<int>(mid)),
                        B21(mid, vector<int>(mid)), B22(mid, vector<int>(mid));
    vector<vector<int>> C11(mid, vector<int>(mid)), C12(mid, vector<int>(mid)),
                        C21(mid, vector<int>(mid)), C22(mid, vector<int>(mid));
    vector<vector<int>> temp1(mid, vector<int>(mid)), temp2(mid, vector<int>(mid));

    // 分解A和B为4个子矩阵
    for (int i = 0; i < mid; i++) {
        for (int j = 0; j < mid; j++) {
            A11[i][j] = A[i][j];
            A12[i][j] = A[i][j + mid];
            A21[i][j] = A[i + mid][j];
            A22[i][j] = A[i + mid][j + mid];

            B11[i][j] = B[i][j];
            B12[i][j] = B[i][j + mid];
            B21[i][j] = B[i + mid][j];
            B22[i][j] = B[i + mid][j + mid];
        }
    }

    // 计算C11, C12, C21, C22
    matrixMultiply(A11, B11, C11, mid);
    matrixMultiply(A12, B21, C12, mid);
    matrixMultiply(A21, B11, C21, mid);
    matrixMultiply(A22, B21, C22, mid);
    
    // 合并四个子矩阵得到最终的结果矩阵C
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            C[i][j] = C11[i][j] + C12[i][j] + C21[i][j] + C22[i][j];
        }
    }
}

int main() {
    int n = 4; // 2x2 矩阵
    vector<vector<int>> A = {{1, 2}, {3, 4}};
    vector<vector<int>> B = {{5, 6}, {7, 8}};
    vector<vector<int>> C(n, vector<int>(n));

    matrixMultiply(A, B, C, n);

    cout << "矩阵C的结果是:\n";
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            cout << C[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

复杂度分析

  • 时间复杂度为 O(n3)
  • 空间复杂度是 O(n^2)

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

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

相关文章

【青牛科技】应用方案 | D75xx-150mA三端稳压器

概 述 D75XX系列是一套三端高电流低压稳压器。它们可以提供 150mA 的输出电流和允许输入电压高达30V。它们有几个固定的输出电压范围为3.0 V至5.0 V。CMOS 技术确保低电压降和低静态电流。 虽然这些设备主要设计为固定电压调节器&#xff0c;但它们可以与外部元件一起使用&…

BO-CNN-LSTM回归预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多输入单输出回归预测

BO-CNN-LSTM回归预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多输入单输出回归预测 目录 BO-CNN-LSTM回归预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多输入单输出回归预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 …

WPF 打包

打包为单个exe文件直接运行 - - -版本.NET8 新建WPF项目 右键 - 发布 选择发布文件夹 选择发布文件夹 选择发布文件夹 配置 配置,保存 发布 WPF 打包为exe安装程序 示例 实现思路 引导项目中嵌入其它项目可运行目录的zip引导项目中解压zip文件到指定文件夹是…

三维测量与建模笔记 - 3.3 张正友标定法

上图中&#xff0c;提到了世界坐标系在张正友标定法中的设计&#xff0c;可以理解为将世界坐标系的原点放到了棋盘格左上角点的位置&#xff0c;并且棋盘格平面上所有点的Z为0&#xff0c;将Z规定为0的话&#xff0c;可以简化掉一个维度&#xff08;列向量r3&#xff09;。去掉…

【解决办法】无法使用右键“通过VSCode打开文件夹”

个人博客&#xff1a;苏三有春的博客 前言 作者的编程环境为VScode&#xff0c;工作时常使用VScode打开整个工程文件夹。如果先打开VScode再从VScode中选择文件夹打开效率太慢&#xff0c;作者一般使用的方式是右键文件夹&#xff0c;直接选择"通过code打开文件夹"…

推荐一款ETCD桌面客户端——Etcd Workbench

Etcd Workbench 我相信很多人在开始管理ETCD的时候都去搜了Etcd客户端工具&#xff0c;然后找到了官方的Etcd Manager&#xff0c;但用完之后发现它并不好用&#xff0c;还不支持多连接和代码格式化&#xff0c;并且已经好几年不更新了&#xff0c;于是市面上就有了好多其他客…

Docker配置及简单应用

谈论/理解 Docker 的常用核心部分&#xff0c;以下皆在 Ubuntu 操作系统下进行 1 国内源安装 Docker-ce 1.1 配置 Linux 内核流量转发 因为docker和宿主机的端口映射&#xff0c;本质是内核的流量转发功能&#xff0c;所以要对其进行配置 1.1.1 未配置流量转发 如果没有配置流…

(十二)JavaWeb后端开发——MySQL数据库

目录 1.数据库概述 2.MyQSL 3.数据库设计 DDL 4.MySQL常见数据类型 5.DML 1.数据库概述 数据库&#xff1a;DataBase(DB)&#xff0c;是存储和管理数据的仓库 数据库管理系统&#xff1a;DataBase ManagementSystem(DBMS)&#xff0c;操纵和管理数据库的大型软件 SQL&a…

fastadmin后台列表根据所选中的行统计指定字段|fastadmin点击checkbox或反选统计某个字段的值

当选中对应行时&#xff0c;统计选中行的用户注册数和用户点击数。 此项功能需要有 点击全选触发事件、点击反选触发事件、勾选某一行触发事件、反选某一行触发事件&#xff0c;用到fastadmin自带的表格事件功能&#xff0c;参考&#xff1a;https://doc.fastadmin.net/doc/19…

stm32使用串口DMA实现数据的收发

前言 DMA的作用就是帮助CPU来传输数据&#xff0c;从而使CPU去完成更重要的任务&#xff0c;不浪费CPU的时间。 一、配置stm32cubeMX 这两个全添加上。参数配置一般默认即可 代码部分 只需要把上期文章里的HAL_UART_Transmit_IT(&huart2,DATE,2); 全都改为HAL_UART_Tra…

轨迹规划中优化预测:学习多个初始解的优化器

Abstract 在许多应用中&#xff0c;如机器人控制、自动驾驶和投资组合管理&#xff0c;需要在严格的运行时间限制下连续地解决相似的优化问题。在这种情况下&#xff0c;局部优化方法的性能对初始解的质量非常敏感&#xff1a;不良的初始化可能会导致收敛缓慢或得到次优解。为…

05 SQL炼金术:深入探索与实战优化

文章目录 SQL炼金术&#xff1a;深入探索与实战优化一、SQL解析与执行计划1.1 获取执行计划1.2 解读执行计划 二、统计信息与执行上下文2.1 收集统计信息2.2 执行上下文 三、SQL优化工具与实战3.1 SQL Profile3.2 Hint3.3 Plan Baselines3.4 实战优化示例 SQL炼金术&#xff1a…

JS封装随机生成一个颜色值工具函数

本文给大家带来的是封装的一个随机生成一个颜色值的工具函数。案例中提供了4个不同的调用函数&#xff0c;但实现的功能本质上都是一样的&#xff0c;开箱即用&#xff0c;随调随用。 //方法一 function getRandomColor() { //随机颜色return #${Math.floor(Math.random() * …

CESS 正式加入政府区块链协会 (GBA) ,出席 Blockchain Infrastructure 大会

北京时间 11 月 6 日&#xff0c;特朗普赢得 2024 年美国总统大选。与此同时&#xff0c;我们很高兴地宣布&#xff0c;CESS 已正式加入政府区块链协会 (GBA)。GBA 是一个全球性协会&#xff0c;致力于将区块链专业人士汇聚在一起&#xff0c;共同推动区块链技术在政府、金融和…

ARXML汽车可扩展标记性语言规范讲解

ARXML: Automotive Extensible Markup Language &#xff08;汽车可扩展标记语言&#xff09; xmlns: Xml name space &#xff08;xml 命名空间&#xff09; xsd: Xml Schema Definition (xml 架构定义) 1、XML与HTML的区别&#xff0c;可扩展。 可扩展&#xff0c;主要是…

数据结构_哈夫曼树及其应用

构造算法的例子 构造算法的实现 初始化&#xff0c;置权值 int i, m, s1, s2;m 2 * n - 1;for (i 1; i < m; i){HT[i].lch 0;HT[i].rch 0;HT[i].parent 0;}for (i 1; i < n; i){cin >> HT[i].weight;}合并结点 // 创建哈夫曼树for (i n 1; i < m; i){s1…

基于AI深度学习的中医针灸实训室腹针穴位智能辅助定位系统开发

在中医针灸的传统治疗中&#xff0c;穴位取穴的精确度对于治疗效果至关重要。然而&#xff0c;传统的定位方法&#xff0c;如体表标志法、骨度折量法和指寸法&#xff0c;由于观察角度、个体差异&#xff08;如人体姿态和皮肤纹理&#xff09;以及环境因素的干扰&#xff0c;往…

esp32学习:利用虫洞ESP32开发板,快速实现无线图传

我们的虫洞开发板&#xff0c;能够完美运行esp who AI代码&#xff0c;所以实现无线图传那是非常容易的&#xff0c;我们先看看examples目录&#xff1a; 里面有比较多的web例程&#xff0c;在这些例程下&#xff0c;稍作修改&#xff0c;就可以快速实现我的图传无线功能&#…

力扣排序455题(分发饼干)

假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。 但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i],这是能 让孩子们满足胃口的饼干的最小尺寸;并且每块饼 干j&#xff0c;都有一个尺寸 s[j]。如果 s[j]> g[i]&…

【论文复现】基于深度学习的手势识别算法

本文所涉及所有资源均在这里可获取。 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐、摄影的一位博主。 &#x1f4d7;本文收录于论文复现系列&#xff0c;大家有兴趣的可以看一看…