算法导论(三)--分治法

  • 二分查找
  • 乘方问题
  • 斐波那契数列
  • 矩阵乘法
  • VLSI(Very Large Scale Integration) Layout(超大规模集成电路布局问题)

分治法就是把一个大的问题分成若干个小的子问题,然后递归地解决小问题,最后再合并子问题的解。回顾一下归并排序,就是一个典型的分治法的例子:第一步把整个数组一分为二,第二步递归地对每个子数组排序,第三步合并两个有序数组。归并排序的复杂度T(n)=2T(n/2)+Θ(n),可以用主定理的2解出,为Θ(nlogn)。

二分查找

二分查找是分治法一个简单的例子。从一个有序的数组中找出x,这个问题可以用这样的分治法:1. 把x与数组的中间元素比较,2. 如果x小于中间的数,那么x一定在数组左边,果x大于中间的数,那么x一定在数组右边,因此只在一个子数组中递归查找,3. 合并这一步没有任何计算量。复杂度T(n)=T(n/2)+Θ(1),解出结果为Θ(logn)。
代码:

int bi_search(int a[],int p,int r,int x)
{int q=p+(r-p)/2;if(a[q]==x)return q;else if(a[q]>x)return bi_search(a, p, q,x);elsereturn bi_search(a, q+1, r, x);
}

乘方问题

给定一个数x,和一个正整数n,求xn
朴素的解法就是把n个x相乘,即x·x·x…·x,乘n-1次,复杂度是Θ(n)。如果用分治法呢,可以在非线性时间内解决这个问题吗?
我们可以把xn划分为两个xn/2,即
xn={xn/2⋅xn/2n为偶数xn−1/2⋅xn−1/2⋅xn为奇数x^n=\begin{cases} x^{n/2}·x^{n/2} & n为偶数 \\ x^{n-1/2}·x^{n-1/2}·x & n为奇数 \\ \end{cases}xn={xn/2⋅xn/2xn−1/2⋅xn−1/2⋅x​n为偶数n为奇数​
T(n)=T(n/2)+Θ(1)=Θ(logn)

斐波那契数列

斐波那契数列的定义是:
F(n)={0n=01n=1F(n−1)+F(n−2)n>1F(n)=\begin{cases} 0 & n=0 \\ 1 & n=1 \\ F(n-1)+F(n-2) & n>1 \\ \end{cases}F(n)=⎩⎪⎨⎪⎧​01F(n−1)+F(n−2)​n=0n=1n>1​
如果直接根据递归式来写程序,复杂度是非常高的,因为如果画一个斐波那契树,将会看到很多重复的子树,实际上很多计算我们都重复算了,比如计算F(10),就要计算F(9)和F(8),计算F(9)就要计算F(8)和F(7),这时就计算过F(8)了,当F(9)计算完之后又要重新计算F(8)。这样复杂度达到了指数级,T(n)=Ω(φn),φ=1+√5/2。但我们希望的是多项式级的算法。

因此这里不能直接用递归,可以用自下而上的算法,依次计算F(0),F(1),…,F(n),这样要计算F(n)时,F(n-1)和F(n-2)已经计算好了,直接相加就行。这种算法复杂度是线性的,T(n)=Θ(n)。

那么有比线性复杂度更好的方法吗?这里可以用一种取巧的方法,如果用φn/√5,并取最接近的整数,就得到了第n位斐波那契数。这是斐波那契数列的一个特性。利用上面的平方递归式计算出φn/√5,就可以用Θ(logn)的复杂度解决这个问题。

为了防止计算机对浮点数的误差,可以用另一种方法。用下面这个定理:
[F(n+1)F(n)F(n)F(n−1)]=[1110]n\left[ \begin{matrix} F(n+1) & F(n) \\ F(n) & F(n-1) \\ \end{matrix} \right]= \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \\ \end{matrix} \right]^n [F(n+1)F(n)​F(n)F(n−1)​]=[11​10​]n
归纳法简单证明一下:
n=1时显然成立。
n>1时,假设
[F(n)F(n−1)F(n−1)F(n−2)]=[1110]n−1\left[ \begin{matrix} F(n) & F(n-1) \\ F(n-1) & F(n-2) \\ \end{matrix} \right]= \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \\ \end{matrix} \right]^{n-1} [F(n)F(n−1)​F(n−1)F(n−2)​]=[11​10​]n−1
那么
[F(n+1)F(n)F(n)F(n−1)]=[F(n)F(n−1)F(n−1)F(n−2)]⋅[1110]=[1110]n\left[ \begin{matrix} F(n+1) & F(n) \\ F(n) & F(n-1) \\ \end{matrix} \right]= \left[ \begin{matrix} F(n) & F(n-1) \\ F(n-1) & F(n-2) \\ \end{matrix} \right]· \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \\ \end{matrix} \right] = \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \\ \end{matrix} \right]^n [F(n+1)F(n)​F(n)F(n−1)​]=[F(n)F(n−1)​F(n−1)F(n−2)​]⋅[11​10​]=[11​10​]n
计算矩阵的乘方,复杂度同样是Θ(logn)。

矩阵乘法

如何计算两个n·n的矩阵的积?
首先看看矩阵乘法的公式:
A=[aij],B=[bij],i, j=1~n,那么A·B的结果C的每一项cij=∑k=1naik⋅bkjc_{ij}=\sum_{k=1}^n{a_{ik}·b_{kj}}cij​=∑k=1n​aik​⋅bkj​,最明显的做法就是对每一项对每个i和j分别求和,分别计算出所有的乘积然后求和,复杂度是Θ(n3)。
伪代码:

for i <- 1 to n
do for j <- 1 to ncij=0;do for k <- 1 to ndo cij <- cij + aik·bkj

具体代码:

//下面是矩阵相乘的代码,不一定是方阵
typedef struct Matrix
{Matrix(int r,int c){this->r=r;this->c=c;val=new int*[r];for(int i=0;i<r;i++)val[i]=new int[c];for(int i=0;i<r;i++)for(int j=0;j<c;j++)val[i][j]=0;}Matrix(int r,int c,int* v){this->r=r;this->c=c;val=new int*[r];for(int i=0;i<r;i++)val[i]=new int[c];for(int i=0;i<r;i++)for(int j=0;j<c;j++)val[i][j]=v[i*c+j];}~Matrix(){for(int i=0;i<r;i++){delete[] val[i];val[i]=NULL;}}void print(){for(int i=0;i<r;i++){for(int j=0;j<c;j++)cout<<val[i][j]<<" ";cout<<endl;}}int **val;int r;int c;
}Matrix;
void Matrix_mul(Matrix m1,Matrix m2,Matrix& m3)
{assert(m1.c==m2.r);int n1=m1.r,n2=m2.c;//dimensionint t=m1.c;//* times for one elementint tmp=0;for(int i=0;i<n1;i++){for(int j=0;j<n2;j++){for(int k=0;k<t;k++)tmp+=m1.val[i][k]*m2.val[k][j];m3.val[i][j]=tmp;tmp=0;}}
}

矩阵乘法如何利用上面的乘方分治来优化呢?将矩阵分块。将每个n·n的矩阵划分成一个2·2的矩阵,每个小块都是一个n/2·n/2的小矩阵。C=A·B就可以写成:
[rstu]=[abcd]⋅[efgh]\left[ \begin{matrix} r &amp; s \\ t &amp; u \\ \end{matrix} \right]= \left[ \begin{matrix} a &amp; b \\ c &amp; d \\ \end{matrix} \right]· \left[ \begin{matrix} e &amp; f \\ g &amp; h \\ \end{matrix} \right] [rt​su​]=[ac​bd​]⋅[eg​fh​]
那么r=ae+bg,s=af+bh,t=ce+dg,u=cf+dh。
一共分了8个块,每次计算C中的一项都是两个块的矩阵乘积加上另两个块的乘积。每两个小块的乘积实际上是递归的矩阵乘法,总共要计算8次乘积。然后还需要4次矩阵求和,矩阵相加的复杂度是Θ(n2)。因此T(n)=8T(n/2)+Θ(n2),利用主方法,T(n)=(n3),竟然并没有优化!

斯特拉森(Strassen)的算法:
加法的复杂度只有Θ(n2),我们并不介意多做几次加法,所以我们要做的是减少乘法的次数。斯特拉森(Strassen)的算法就将乘法减少到了7次。定义P1~P7:
P1=a·(f-h)
P2=(a+b)·h
P3=(c+d)·e
P4=d·(g-e)
P5=(a+d)·(e+h)
P6=(b-d)·(g+h)
P7=(a-c)·(e+f)
那么
r=P4+P5-P2+P6
s=P1+P2
t=P3+P4
u=P1+P5-P3-P7

总结一下斯特拉森算法的分治步骤:先将矩阵A和B划分,然后计算上面所有的P,这需要Θ(n2)复杂度;然后递归地处理所有的Pi,得到7个乘积;最后把这些乘积组合起来,得到r,s,t,u,这需要Θ(n2)复杂度。T(n)=7T(n/2)+Θ(n2)=Θ(nlog27)

VLSI(Very Large Scale Integration) Layout(超大规模集成电路布局问题)

在集成电路布局时,假设电路是一个完全二叉树,现在我们要在网格上把这棵树放到芯片布局上。假设这棵树有n个叶子节点,那么如何布局使得这棵树在一个网格上占据最小空间?树的节点放在网格上(正方形网格)的顶点上,边放在网格的正交路线上。

朴素的算法:

上图这棵树中,叶子节点树记为W(n),高度记为H(n),W(n)是n级别,H(n)是logn级别。将这棵树分为左子树和右子树,那么H(n)=H(n/2)+Θ(1)=Θ(logn),W(n)=2W(n/2)+O(1)=Θ(n),因此面积是Θ(nlogn)。

更好的方法:
可不可以让复杂度更低,比如Θ(n)?这需要改变布局。注意到√n和√n的乘积是n,现在的目标是让W(n)的复杂度变为Θ(√n),H(n)的复杂度也变为Θ(√n),这样乘积就是Θ(n)。想一想主方法中那种递归式有这样的形式,什么时候nlogba是√n?b=4,a=2。并且要让nlogba占主导地位,因此满足主定理的1,T(n)=2T(n/4)+O(n1/2-ε)。具体来说就是H布局:

有4个H,每个H是n/4个节点,中间是根节点。这个图的宽和高都是L(n),每个子H图的宽是L(n/4),因为只有1/4叶节点。因此L(n)=2L(n/4)+Θ(1)=Θ(√n)

算法导论(三)--分治法相关推荐

  1. c++分治法求最大最小值实现_程序员:算法导论,分治法、归并排序,伪代码和Java实现...

    分治法 我们首先先介绍分治法.分治法的思想:将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后在合并这些子问题的解来解决原问题的解. 还是拿扑克牌举例子,假设桌上有两堆牌面朝 ...

  2. [转载] 算法导论:分治法,python实现合并排序MERGE-SORT

    参考链接: Python中的合并排序merge sort 1. 简单合并排序法实现 思想:两堆已排好的牌,牌面朝下,首先掀开最上面的两张,比较大小取出较小的牌,然后再掀开取出较小牌的那一堆最上面的牌和 ...

  3. 三大算法之一:分治法(带你用分治法思想优化程序,计算降低复杂算法的时间复杂度)

    目录 ​ 零.前言 1.分治法 1.含义 2.分治法主要思想 3.分治法的求解步骤 1.确定初始条件 2.计算每一部分的时间复杂度 3.合并时间复杂度 4.求解 3.最大最小值问题 1.问题描述 2. ...

  4. c语言分治法求众数重数_算法实验二 分治法 众数问题.pdf

    算法实验二 分治法 众数问题 算法分析与设计实验二 分治法 主要内容 • 实验目的 • 主要实验仪器设备和环境 • 实验内容 • 实验要求 • 注意点 实验目的 • 理解分治法的基本思想 • 针对特定 ...

  5. 用递归与分治策略求解网球循环赛日程表_算法设计:分治法(比赛日程安排)...

    一.算法思路 1.思路 分治算法的思想是:对于一个规模位N的问题,若该问题可以容易解决(比如规模N较小),则直接解决,否则将其分解为M个规模较小的子问题,这些子问题互相独立,并且与原问题形式相同,递归 ...

  6. Java常用算法二:分治法

    文章目录 一.分治算法的基本步骤 二.分治算法解决汉诺塔问题 2.1 汉诺塔的规则: 2.2 使用分治算法 笔记参考:尚硅谷 分治法就是把很复杂的问题分而治之,把一个很大的问题分成几个很小的问题,再把 ...

  7. 算法设计——用分治法查找数组元素的最大值和最小值、用分治法实现合并排序、最小费用问题、树的最大连通分支问题(代码实现)

    代码链接:pan.baidu.com/s/15inIth8Vl89R1CgQ_wYc2g  提取码:gf13 算法分析与设计第 1 次实验 时间 2020.3.31 地点 软件大楼 127 实验名称 ...

  8. 算法与数据结构-分治法

    分治法 分治法的思想 将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解. 分治模式在每层递归时都有三个步骤: 分解(Divide):将原 ...

  9. 五大常用算法之四:分治法

    分治法和动态规划有点像,都是分解成子问题 中科大的张署老师课件很清楚,摘录如下: 1.什么是分治法 当求解的问题较复杂或规模较大时,不能立刻得到原问题的解,但这些问题本身具有这样的特点,它可以分解为若 ...

  10. 0008算法笔记——【分治法】循环赛事日程表

    问题描述: 设有n=2^k个运动员要进行网球循环赛.现要设计一个满足以下要求的比赛日程表: (1)每个选手必须与其他n-1个选手各赛一次:      (2)每个选手一天只能参赛一次:      (3) ...

最新文章

  1. 每天只睡4小时!大佬们都这么拼吗?
  2. 2021-1-17 随笔
  3. java:UDP通信
  4. 学习Git_12.10
  5. 2017中国电商峰会共话“一带一路”网上商机
  6. 小心,疫情下在线教育免费试听引起的“后遗症”
  7. 交换机的特点及工作原理
  8. 2020年9月国产数据库流行度排行:阿里腾讯花开两朵 TiDB和达梦逐浪潮头
  9. Javascript特效:缓动动画
  10. JavaMail简单版实验测试
  11. SVN安装包汉化VS插件
  12. 十四届恩智浦智能车竞赛双车组-星夜兼程队2019回顾
  13. MyBatis学习_2_MyBatis的关联映射
  14. 2022年瑞典经济发展研究报告
  15. 【翻译】各种Payload免杀工具集
  16. 【cs231n学习笔记(2017)】—— 神经网络激活函数
  17. WiFi遥控小车(四):简单直流电机驱动及UDP通信程序
  18. 宝塔面板服务器ip地址修改_宝塔,云帮手服务器控制面板,你用的哪一款?
  19. 揭开物联网的神秘面纱--物联网小灯
  20. VS2017非全功能离线安装

热门文章

  1. V2X-ViT:基于Vision Transformer的V2X协同感知
  2. 股票交易一点感悟和程序化交易实战
  3. kaldi跑自己数据遇到的问题合集(持续更)
  4. 倍福 TwinCAT背景知识
  5. fastboot的安装使用
  6. 10周成为数据分析师!
  7. 大数据Apache Druid(四):使用Imply进行Druid集群搭建
  8. ps插件摹客iDoc使用技巧
  9. linux如何合并文件
  10. 假设知道服务器IP,如何查询它绑定的域名?