递归与分治策略-第二节:分治和典型分治问题

Posted 我擦我擦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了递归与分治策略-第二节:分治和典型分治问题相关的知识,希望对你有一定的参考价值。

文章目录

一:分治法基本概念

(1)基本思想

分治法基本思想:是将一个规模为 n n n的问题分解为 k k k个规模较小的子问题,这些子问题相互独立且与原问题相同。递归地解决这些子问题,然后将各个子问题的解合并得到原问题的解。分治法求解过程有三个步骤

  • 分解:将原问题分解为若干规模较小,相互独立,与原问题形式相同的子问题
  • 求解:若子问题规模较小且容易被解决则直接解决,否则递归解决各个子问题(也即继续分解)
  • 合并:将各个子问题的解合并为原问题的解

其基本框架如下

  • ∣ P ∣ |P| P表示问题 P P P的规模, n 0 n0 n0为某一阈值,表示当问题 P P P不超过 n 0 n0 n0时,问题容易解决
  • a d h o c ( P ) adhoc(P) adhoc(P)是该分治法的基本子算法,用于直接解决小规模的问题 P P P
  • m e r g e ( y 1 , y 2 , . . . , y k ) merge(y1, y2, ... ,yk) merge(y1,y2,...,yk)是该分治法的合并子算法,用于将问题 P P P的子问题 P 1 P1 P1 P 2 P2 P2,…, P k Pk Pk的解 y 1 y1 y1 y 2 y2 y2,…, y k yk yk合并为P的解

根据大量实践人们发现,使用分治法设计算法时,将一个问题分成大小相等的 k k k个子问题这样的分割方法往往是最有效的。其实在很多问题中,我们往往会取 k = 2 k=2 k=2

(2)适用条件

分治法适用条件:分治法所能解决的问题一般具有以下几个特征

  • 该问题的规模缩小到一定的程度就可以容易地解决
  • 该问题可以分解为若千个规模较小的结构自相似问题,即该问题具有最优子结构性质
  • 利用该问题分解出的子问题的解可以合并为该问题的解
  • 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题

(3)复杂度分析

复杂度分析:分治法将规模为 n n n的问题分解为 a a a个规模为 n b \\fracnb bn子问题去解。设分解阈值 n 0 = 1 n_0=1 n0=1,且adhoc解规模为1的问题耗费1个时间单位。再设将原问题分解为 a a a个规模为 n b \\fracnb bn子问题以及用merge将这个子问题的解合并为原问题的解需要 f ( n ) f(n) f(n)个单位时间。用 T ( n ) T(n) T(n)表示该分治法解规模为 ∣ P ∣ = n |P|=n P=n的问题所需的计算时间,则有

利用迭代法可求得

T ( n ) = n l o g b a + ∑ j = 0 ( l o g b n ) − 1 a j f ( n b j ) T(n)=n^log_ba+\\sum\\limits_j=0^(log_bn)-1a^jf(\\fracnb^j) T(n)=nlogba+j=0(logbn)1ajf(bjn)

二:典型分治问题

(1)二分搜索

二分搜索:给定已经排好序的 n n n个元素 a [ 0 : n − 1 ] a[0:n-1] a[0:n1],现要求在这 n n n个元素中找出某一特定元素 x x x。二分搜索的基本思想是将 n n n个元素分成个数大致相同的两半,取 a [ n 2 ] a[\\fracn2] a[2n] x x x作比较,如果 a [ n 2 ] = x a[\\fracn2]=x a[2n]=x,则找到 x x x,算法终止,否则

  • 如果 x < a [ n 2 ] x<a[\\fracn2] x<a[2n],那么只在数组 a a a的左半部分继续搜索 x x x
  • 如果 x > a [ n 2 ] x>a[\\fracn2] x>a[2n],那么只在数组 a a a的右半部分继续搜索 x x x

二分搜索思路看起来非常简单,但是它的细节却很麻烦,因为这个算法一不小心就容易写出bug,这里一LeetCode:704.二分查找这道题为例,进行说明

这道题代码非常简单,如下

class Solution 
    public int search(int[] nums, int target) 
        int left = 0;
        int right = nums.length - 1;
        while(left <= right)
            int mid = (left + right) / 2;
            if(target == nums[mid])
                return mid;
            else if(target <nums[mid])
                right = mid - 1;
            else
                left = mid + 1;
            
        
        return -1;
    

但是这里有几个问题值得大家仔细思考

①:为什么循环终止条件是<=而不是<

这是因为right=nums.lenth-1而不是nums.length

  • right=nums.lenth-1用于闭区间[left, right]
  • nums.length用于左闭右开区间[left, right)

while(left<=right)的终止条件是left==right+1,写成区间就是[right+1, right],这很明显返回-1,因为怎么可能区间左端要大于右端?

whle(left < right)的终止条件是left == right,写成区间就是[right, right],这个时候区间中还有有一个数字,还没有被检索,如果这个时候返回-1,很明显就是错误的

②:为什么是left=mid+1right=mid-1,而不是right=midleft=mid

我们的搜索区间是封闭的,也即[left, right],当mid不是要找的target时,下一步自然应该是扣除这个点去其他区间搜索

(2)大整数乘法

A:大整数乘法(Karatsuba算法)

  • 在某些情况下需要处理很大的整数,无法在计算机硬件能直接表示的整数范围内进行处理。若用浮点数来表示它,则只能近似地表示它的大小,计算结果中的有效数字也受到限制。若要精确地表示大整数并在计算结果中要求精确地得到所有位数上的数字,就必须用软件的方法来实现大整数的算术运算

大整数乘法:设 X X X Y Y Y都是 n n n位二进制整数,请计算它们的乘积 X Y XY XY

当然你可以采用竖式相乘的方法计算,但是这种方法计算步骤较多,效率也比较低


可以将 n n n位二进制整数 X X X Y Y Y分为2段,每段长 n 2 \\fracn2 2n

于是, X = A × 2 n 2 + B X=A×2^\\fracn2+B X=A×22n+B, Y = C × 2 n 2 + D Y=C×2^\\fracn2+D Y=C×22n+D,因此 X Y = ( A × 2 n 2 + B ) ( C × 2 n 2 + D ) = A C × 2 n + ( A D + B C ) × 2 n 2 + B D = A C × 2 n + ( ( A + B ) ( C + D ) − A C − B D ) × 2 n 2 + B D XY=(A×2^\\fracn2+B)(C×2^\\fracn2+D)=AC×2^n+(AD+BC)×2^\\fracn2+BD=AC×2^n+((A+B)(C+D)-AC-BD)×2^\\fracn2+BD XY=(A×22n+B)(C×22n+以上是关于递归与分治策略-第二节:分治和典型分治问题的主要内容,如果未能解决你的问题,请参考以下文章

第二章:递归与分治策略

2021算法竞赛入门班第二节课递归分治二分练习题

第二章-递归与分治策略

分治与递归-棋盘覆盖问题

计算机算法设计与分析之递归与分治策略——二分搜索技术

五大常见算法策略——递归与分治策略