当前位置: 首页 > news >正文

算法之分而治之

分而治之

  • 分而治之
    • 核心思想
    • 案例分析
      • 二分查找
      • 归并排序
      • 快速排序
      • 最大子数组问题
    • 分治算法的适用场景
    • 分治算法与其他算法策略的比较
    • 总结

分而治之

核心思想

“分而治之”(Divide and Conquer)是一种算法设计策略,它将一个问题分解成更小的、相互独立的子问题,然后递归地解决这些子问题,最后将它们的解合并起来,得到原始问题的解。这个策略通常包含三个步骤:

  • 分解(Divide): 将原问题分解为若干个规模较小的子问题。这一步通常通过递归的方式来实现。

  • 解决(Conquer): 递归地解决这些子问题。如果子问题足够小,就直接解决。

  • 合并(Combine): 将子问题的解合并成原问题的解。

这个思想常常用于解决一些复杂的问题,例如排序、查找、图算法等。一些著名的算法,比如归并排序和快速排序,就是分而治之的典型例子。

下面以归并排序为例来说明分而治之思想的应用:

归并排序的步骤

  • 分解: 将待排序的数组分成两半。

  • 解决: 递归地对这两半数组进行归并排序。

  • 合并: 将两个已排序的子数组合并成一个有序的数组。

这个过程一直递归下去,直到子问题的规模足够小,可以直接解决。通过这样的分治策略,最终整个数组被排序。

分而治之的优点

  • 简化复杂问题: 将一个大问题分解为若干个小问题,使问题的解决变得更加简单和直观。

  • 提高效率: 在某些情况下,分而治之可以通过并行处理来提高算法的效率。

  • 可复用性: 将问题分解为独立的子问题,这些子问题可以在不同的上下文中被复用。

  • 可维护性: 代码结构清晰,易于理解和维护。

总体来说,分而治之是一种强大的问题解决思想,能够在设计算法时提供清晰的思路和结构。

案例分析

二分查找

#include <stdio.h>
//1.正常数组的查找
int searchData(int array[], int arrayNum, int posData) 
{for (int i = 0; i < arrayNum; i++) {if (array[i] == posData)return i;}return -1;
}
//2.正常的二分查找--->有序的
int binarySerachData(int array[], int arrayNum, int posData)
{int left = 0;int right = arrayNum - 1;			//右边的数组下标是等于数组长度-1while (left <= right) {int mid = (left + right) / 2;if (array[mid] == posData) {return mid;}else if (array[mid] > posData) {right = mid - 1;}else {left = mid + 1;}}return -1;
}
//3.分而治之的二分查找
int binarySearch(int array[], int left, int right, int posData)
{if (left > right)return -1;int mid = (left + right) / 2;if (array[mid] == posData)return mid;else if (array[mid] > posData)	//左边的问题{return binarySearch(array, left, mid - 1, posData);}else                              {//右边问题return binarySearch(array, mid+1, right, posData);}}
int main() 
{int array[10] = { 0,1,2,3,4,5,6,7,8,9 };printf("index:%d\n", binarySerachData(array, 10, 1));printf("index:%d\n", binarySearch(array, 0,9, 1));return 0;
}

复杂度分析

  • 时间复杂度:O(log n),每次将搜索范围减半
  • 空间复杂度:迭代版本为O(1),递归版本为O(log n),因为递归调用栈的深度

归并排序

归并排序是分而治之思想的典型应用,它将数组分成两半,分别排序后再合并。

问题描述:给定一个无序数组,将其排序为有序数组。

算法思路

  1. 分解:将数组分成两个子数组
  2. 解决:递归地对两个子数组进行排序
  3. 合并:将两个已排序的子数组合并成一个有序数组

代码实现

#include <stdio.h>
#include <stdlib.h>// 合并两个已排序的子数组
void merge(int arr[], int left, int mid, int right) {int i, j, k;int n1 = mid - left + 1;int n2 = right - mid;// 创建临时数组int* L = (int*)malloc(n1 * sizeof(int));int* R = (int*)malloc(n2 * sizeof(int));// 复制数据到临时数组for (i = 0; i < n1; i++)L[i] = arr[left + i];for (j = 0; j < n2; j++)R[j] = arr[mid + 1 + j];// 合并临时数组i = 0; // 第一个子数组的索引j = 0; // 第二个子数组的索引k = left; // 合并后数组的索引while (i < n1 && j < n2) {if (L[i] <= R[j]) {arr[k] = L[i];i++;} else {arr[k] = R[j];j++;}k++;}// 复制L[]的剩余元素while (i < n1) {arr[k] = L[i];i++;k++;}// 复制R[]的剩余元素while (j < n2) {arr[k] = R[j];j++;k++;}free(L);free(R);
}// 归并排序函数
void mergeSort(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() {int arr[] = {12, 11, 13, 5, 6, 7};int arr_size = sizeof(arr) / sizeof(arr[0]);printf("原始数组: ");for (int i = 0; i < arr_size; i++)printf("%d ", arr[i]);printf("\n");mergeSort(arr, 0, arr_size - 1);printf("排序后数组: ");for (int i = 0; i < arr_size; i++)printf("%d ", arr[i]);printf("\n");return 0;
}

复杂度分析

  • 时间复杂度:O(n log n),在最好、平均和最坏情况下都是如此
  • 空间复杂度:O(n),需要额外的空间来存储临时数组
  • 稳定性:稳定排序算法

快速排序

快速排序也是分而治之的经典应用,它通过选择一个基准元素(pivot),将数组分为小于和大于基准的两部分,然后递归地对这两部分进行排序。

问题描述:给定一个无序数组,将其排序为有序数组。

算法思路

  1. 分解:选择一个基准元素,将数组分为两部分,一部分小于基准,另一部分大于基准
  2. 解决:递归地对两部分进行快速排序
  3. 合并:由于分区操作已经将元素放在正确的位置,无需额外的合并步骤

代码实现

#include <stdio.h>// 交换两个元素
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}// 分区操作
int partition(int arr[], int low, int high) {// 选择最右边的元素作为基准int pivot = arr[high];int i = (low - 1); // 小于基准的元素的索引for (int j = low; j <= high - 1; j++) {// 如果当前元素小于基准if (arr[j] < pivot) {i++; // 增加小元素的索引swap(&arr[i], &arr[j]);}}swap(&arr[i + 1], &arr[high]);return (i + 1);
}// 快速排序函数
void quickSort(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() {int arr[] = {10, 7, 8, 9, 1, 5};int n = sizeof(arr) / sizeof(arr[0]);printf("原始数组: ");for (int i = 0; i < n; i++)printf("%d ", arr[i]);printf("\n");quickSort(arr, 0, n - 1);printf("排序后数组: ");for (int i = 0; i < n; i++)printf("%d ", arr[i]);printf("\n");return 0;
}

复杂度分析

  • 时间复杂度:平均情况下为O(n log n),最坏情况下为O(n²)(当数组已经排序时)
  • 空间复杂度:O(log n),由于递归调用栈的深度
  • 稳定性:不稳定排序算法

最大子数组问题

最大子数组问题是寻找具有最大和的连续子数组,也可以用分治法解决。

问题描述:给定一个整数数组,找出一个具有最大和的连续子数组。

算法思路

  1. 分解:将数组分成两半
  2. 解决:递归地在两半中找出最大子数组
  3. 合并:考虑跨越中点的最大子数组,并与左右两半的最大子数组比较,取最大值

代码实现

#include <stdio.h>
#include <limits.h>// 寻找跨越中点的最大子数组
int maxCrossingSum(int arr[], int low, int mid, int high) {// 左半部分int sum = 0;int left_sum = INT_MIN;for (int i = mid; i >= low; i--) {sum += arr[i];if (sum > left_sum)left_sum = sum;}// 右半部分sum = 0;int right_sum = INT_MIN;for (int i = mid + 1; i <= high; i++) {sum += arr[i];if (sum > right_sum)right_sum = sum;}// 返回左右和的总和return left_sum + right_sum;
}// 使用分治法求解最大子数组
int maxSubArraySum(int arr[], int low, int high) {// 基本情况:只有一个元素if (low == high)return arr[low];// 找到中点int mid = (low + high) / 2;/* 返回以下三者中的最大值:a) 左半部分的最大子数组和b) 右半部分的最大子数组和c) 跨越中点的最大子数组和 */return max3(maxSubArraySum(arr, low, mid),maxSubArraySum(arr, mid + 1, high),maxCrossingSum(arr, low, mid, high));
}// 返回三个整数中的最大值
int max3(int a, int b, int c) {return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
}// 测试函数
int main() {int arr[] = {-2, -5, 6, -2, -3, 1, 5, -6};int n = sizeof(arr) / sizeof(arr[0]);int max_sum = maxSubArraySum(arr, 0, n - 1);printf("最大子数组和为: %d\n", max_sum);return 0;
}

复杂度分析

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(log n),由于递归调用栈的深度

分治算法的适用场景

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

  1. 问题可以分解为相似的子问题:如果一个问题可以被分解为结构相同但规模较小的子问题,那么分治法是一个很好的选择。

  2. 子问题相互独立:子问题之间没有重叠,即一个子问题的解不依赖于其他子问题的解。如果子问题有重叠,可能需要考虑动态规划。

  3. 问题规模较大:对于规模较大的问题,分治法可以将其分解为更小、更易于解决的子问题。

  4. 存在基本情况:问题必须能够被分解到一个简单的基本情况,这个基本情况可以直接解决。

分治算法与其他算法策略的比较

算法策略特点适用场景典型例子
分治法将问题分解为独立的子问题,解决后合并子问题相互独立,结构相同归并排序、快速排序、二分查找
动态规划将问题分解为重叠的子问题,存储子问题的解子问题有重叠,具有最优子结构背包问题、最长公共子序列
贪心算法在每一步选择当前最优解局部最优解导致全局最优解霍夫曼编码、最小生成树
回溯法尝试所有可能的解,遇到不满足条件的解则回溯需要找到所有可能的解八皇后问题、数独

总结

分而治之是一种强大的算法设计策略,它通过将复杂问题分解为更小的子问题来简化解决过程。这种方法在许多经典算法中得到了应用,如归并排序、快速排序、二分查找等。

相关文章:

  • Unity 场景管理核心教程:从 LoadScene 到 Loading Screen 实战 (Day 35)
  • 配置 VS Code 使用 ESLint 格式化
  • 多模态大语言模型arxiv论文略读(三十二)
  • Linux深度探索:进程管理与系统架构
  • uniapp云打包针对谷歌视频图片权限的解决方案
  • [架构之美]一键服务管理大师:Ubuntu智能服务停止与清理脚本深度解析
  • 《AI大模型应知应会100篇》第30篇:大模型进行数据分析的方法与局限:从实战到边界探索
  • 自定义错误码的必要性
  • Macbook IntelliJ IDEA终端无法运行mvn命令
  • XAML 标记扩展
  • Android端使用无障碍服务实现远程、自动刷短视频
  • 【TeamFlow】4.2 Yew库详细介绍
  • 03-HTML常见元素
  • 衡石科技ChatBI--飞书数据问答机器人配置详解(附具体操作路径和截图)
  • 24、ASP.NET⻚⾯之间传递值的⼏种⽅式
  • 【C++】基于红黑树的map和set封装实现
  • Django 入门指南:构建强大的 Web 应用程序
  • 4.1腾讯校招简历优化与自我介绍攻略:公式化表达+结构化呈现
  • 在springboot3.4.4和jdk17环境下集成使用mapstruct
  • 汽车动力转向器落锤冲击试验台
  • 云南巧家警方抓获一名网逃人员:带70余万现金“隐居”山洞,昼伏夜出
  • 牛市早报|现货黄金价格站上3400美元,上交所召开私募机构座谈会
  • 湖南平江发生人员溺亡事件,已造成4人死亡
  • 国务院国资委:推动央企强化资金统筹,确保及时付款
  • “小时光:地铁里的阅读”摄影展开幕,嘉宾共话日常生活与阅读
  • 官方披露:定西民政局原局长将收受烟酒高价“倒卖”给单位,用于违规接待