分治算法的介绍与原理解析

文章目录

  • 1.分治算法
    • [1.1 如何判断分治问题](#1.1 如何判断分治问题)
    • [1.2 为什么通过分治可以提升效率](#1.2 为什么通过分治可以提升效率)
      • [1.2.1 操作数量的优化](#1.2.1 操作数量的优化)
      • [1.2.2 并行计算优化](#1.2.2 并行计算优化)
    • [1.3 分治常见应用](#1.3 分治常见应用)

1.分治算法

分治(divide and conquer),全称是分而治之,是一种非常重要且非常常见的算法。分治通常基于递归实现,主要包括"分"和"治"两个阶段。

  1. 分(划分阶段):递归地将原问题分解为两个或多个子问题,直到到达最小子问题时结束。
  2. 治(合并阶段):从已知解地最小子问题开始,从底到顶地将问题地解进行合并,从而构建出原问题地解。
    其实在排序阶段我们就已经使用过分治算法了,当我们在处理归并排序时,就用到了归并排序。
    同样也是分成了"分"和"治":
  3. 分:递归地将原数组划分为两个子数组(子问题),直到子数组只剩一个元素(最小子问题)
  4. 治:从底到顶地将有序子数组进行合并,从而得到有序地原数组

1.1 如何判断分治问题

一个问题是否合适使用分治解决,可以参考以下几点:

  1. 问题可以分解:原问题可以分解为规模更小、类似地子问题,以及能够以相同方式递归地进行划分。
  2. 子问题是相互独立的:子问题间没有重叠,互相没有关联,独立存在。
  3. 子问题的解可以合并:原问题的解通过合并子问题的解得到。
    如此一来,归并排序显然是满足上面的3个条件的。
  4. 问题可以分解:递归地将数组(原问题)划分为两个子数组(子问题)。
  5. 子问题相互独立:每个子数组都是可以独立地进行排序
  6. 子问题地解可以合并:两个有序子树可以合并为一个有序数组。
c 复制代码
void _MergeSort(int* a, int* tmp, int begin, int end)
{
	//确定递归出口
	if (begin >= end)
		return;
	int mid = (begin + end) / 2;//划分数组,将数组一分为二
	//以下为分解逻辑
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, mid + 1, end);
	//以下为合并逻辑
	int begin1 = begin,end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int index = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
			tmp[index++] = a[begin1++];
		else
			tmp[index++] = a[begin2++];
	}
	//处理剩余元素
	while (begin1 <= end1)
		tmp[index++] = a[begin1++];
	while (begin2 <= end2)
		tmp[index++] = a[begin2++];
	//将临时数组存放的数据重新复制到原数组
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));

}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);//临时数组,存放合并时的数据
	if (tmp == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	//归并排序的核心逻辑,再封装一个函数来实现
	_MergeSort(a, tmp, 0, n - 1);
}

1.2 为什么通过分治可以提升效率

分治不仅可以有效地解决,算法问题,往往还可以提升算法效率。在排序算法中,快速排序、归并排序、堆排序相比较于选择排序、冒泡排序、插入排序更快,就是因为它们应用了分治的策略。
提问 :为什么分治可以提升算法效率,它的底层逻辑是什么?为什么将大问题分解为多个子问题、解决子问题、将子问题的解合并为原问题的解、这几步的效率为什么就比直接解决原问题的效率更高?
回答

1.2.1 操作数量的优化

以"冒泡排序"为例,其处理一个长度为n的数组需要O(N^2)的时间。假设我们按照下图来操作,将数组从中点分为两个子数组,则划分需要O(N)时间,排序每个子数组需要O((N/2)^2)时间,合并两个子数组需要O(N)的时间,总体时间复杂度为:

O(n+(n/2)^2+n) = O((n^2)/2+2*n)

接下来,我们计算以下不等式,其左边和右边分别为划分前和划分后的操作总数:

N^2 > (N^2)/2+2N
N^2-(N^2)/2-2N > 0
N(N-4) > 0

这意味着当N>4时,划分后的操作数量更少,排序效率应该更高 ,不过要注意的是这里划分后的时间复杂度仍然平阶O(N^2),只是复杂度中的常数项变小了。

进一步想,如果我们把子数组不断地再从中间划分为两个子数组 ,直到子数组只剩下一个元素时停下划分呢?这种思想就是"归并排序",时间复杂度为O(NlogN).

再去思考,如果我们再多设置几个划分点 ,将原数组平均划分为k个子数组呢?这种情况与"桶排序"非常类似非常适合排序海量数据,理论时间复杂度为O(N+K)

1.2.2 并行计算优化

我们知道,分治生成地子问题相互独立地,因此通常可以并向解决。也就是说,分治不仅可以降低算法时间复杂度,还有利于操作系统地并行优化。

并行优化再多核或多处理器的环境中尤其有效,因为系统可以同时处理多个子问题,更加充分利用计算机资源,从而显著减少总体的运行时间。

1.3 分治常见应用

  1. 寻找最近点对:该算法首先将点集分成两部分,然后分别找出两部分中最近的点对,最后找出跨越两部分的最近点对。
  2. 大整数乘法:例如Karastsuba算法,它将大整数乘法分解为几个较小的整数的乘法和加法。
  3. 矩阵乘法:例如Strassen算法,它将大矩阵分解为多个小矩阵的乘法和加法。
  4. 汉诺塔问题:汉诺塔问题可以通过递归解决,这是典型的分治策略应用。
  5. 求解逆序对 :再一个序列中,如果前面的数字大于后面的数字,那么这连个数字构成一个逆序对。求解逆序对问题可以利用分治的思想,借助归并排序求解。
    在另一方面,分治在算法和数据结构的设计中应用非常广泛。
  6. 二分查找:二分查找是将有序实在从中点索引处分为两部分,然后根据目标值与中间元素比较结果,决定排除哪一半的区间,并在剩余区间执行相同的二分操作。
  7. 归并排序:递归地将原数组划分为两个子数组,直到子数组只剩一个元素,从底到顶地将有序子数组进行合并,从而得到有序地原数组
  8. 快速排序:快速排序是选取一个基准值,然后把数字分为两个子数组,一个数组的元素比基准值小,另一个子数组比基准值大,再对这两部分较小相同的划分操作,直到子数组只剩下一个元素。
  9. 桶排序:推排序的基本思想是将数据分散到多个桶,然后最每个桶内的元素进行排序,最后将各个桶的元素以此取出,从而得到一个有序数组。
  10. :例如二叉搜索树,AVL树,红黑树,B树,B+树等,它们的查找、插入、删除等操作都可以视为分治策略的应用
  11. :堆是一种特殊的完全二叉树,其各种操作,如插入、删除和堆化,实际上都隐含了分治的思想。
  12. 哈希表:虽然哈希表并不直接应用到分治,但某些哈希冲突解决方案间接地使用了分治策略,例如,链式地址中地长链表会被转化为红黑树,以提高查询效率。
相关推荐
Watermelo6171 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
乐之者v7 分钟前
leetCode43.字符串相乘
java·数据结构·算法
小奥超人8 分钟前
RAR压缩算法的文件修复功能详解
windows·经验分享·winrar·办公技巧
A懿轩A1 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
古希腊掌管学习的神1 小时前
[搜广推]王树森推荐系统——矩阵补充&最近邻查找
python·算法·机器学习·矩阵
云边有个稻草人1 小时前
【优选算法】—复写零(双指针算法)
笔记·算法·双指针算法
半盏茶香1 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
忘梓.2 小时前
解锁动态规划的奥秘:从零到精通的创新思维解析(3)
算法·动态规划
Evand J2 小时前
LOS/NLOS环境建模与三维TOA定位,MATLAB仿真程序,可自定义锚点数量和轨迹点长度
开发语言·matlab
LucianaiB2 小时前
探索CSDN博客数据:使用Python爬虫技术
开发语言·爬虫·python