算法导论 第一部分 第四章-分治策略

我们知道分治策略,就是3个步骤,分解、解决、合并
子问题足够大,需要递归解决,叫做递归情况
子问题足够小,就进入了基本情况


递归式

递归式可以很方便的表示算法运行时间。比如,我们用以下递归式表示了归并排序的运行时间 T(n);

     {Θ(1)    如果  n = 1;    }
T(n) =  {                           }{2T(n/2)+ Θ(n) 如果 n > 1}

三种解递归式的方法,

  • 代入法,我们猜测一个界,然后用数学归纳法证明是对的
  • 递归树法,把递归式转化为一棵树,节点表示不同层次节点的递归产生的代价,然后用边界和技术求解。
  • 主方法,T(n) = aT(n/b) + f(n), 就是生成a个子问题,每个子问题规模是原问题的1/n,分解和合并花费时间是f(n)

最大子数组问题

买卖股票时机,输入为
[100,113,110,85,105,102,86,63,81,101,94,106,101,79,94,90,97]
索引是第几天的股价。

暴力法求解,取出所有的组合,显然运行时间是 Ω(n2)

分治策略
把源输入,变成每日的价格变化,变为
[13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]
我们找到数组的中间位置mid, 一个子数组 A[i,j]必有3个情况,
数组的左边部分 A[low, mid]
数组的右边部分 A[mid+1, high]
包含mid点
左右部分的解,可以递归解出。

第三种情况的解法。

function find_max_cross_sub_array(arr, low, mid, high) {// 求出左半部分的最大子数组let left_sum = -Infinity;let sum = 0;let max_left = mid;for(let i = mid; i >= low; i--)   {sum+=arr[i];if(sum > left_sum){left_sum = sum;max_left = i;}}// console.log(`max_left=${max_left} left_sum=${left_sum}`)// 求出right半部分的最大子数组let right_sum = -Infinity;sum = 0; let max_right = mid + 1;for(let j = mid+1; j <= high; j++){sum+=arr[j];if(sum > right_sum){right_sum = sum;max_right = j;}}// console.log(`max_right=${max_right} right_sum=${right_sum}`)// 返回两个边界、两个部分的和 return [max_left, max_right, left_sum + right_sum]}

合并得到结果

   function find_max_sub_array(arr, low, high) {if(low === high){return [low, high, arr[low]];}let mid = Math.floor((low+high)/2);let [left_low, left_high, left_sum] = find_max_sub_array(arr, low, mid);let [right_low, right_high, right_sum] = find_max_sub_array(arr, mid + 1, high);let [cross_low, cross_high, cross_sum] = find_max_cross_sub_array(arr, low, mid, high);if(left_sum >= right_sum && left_sum >= cross_sum){return [left_low, left_high, left_sum];}else if (right_sum >= left_sum && right_sum >= cross_sum){return [right_low, right_high, right_sum]}else {return [cross_low, cross_high, cross_sum];}}

补充 - 更简单的解法

var maxProfit = function(prices) {let min = prices[0];let max = 0;for(let i = 1; i < prices.length; i++) {min = Math.min(min, prices[i]);max = Math.max(max, prices[i] - min);}return max;
};

补充 - 连续数组最大的和

var maxSubArray = function(nums) {let max = nums[0];let maxSum = max;for(let i = 1; i < nums.length;i++){let c = nums[i];max = Math.max(max, c, maxSum + c);maxSum = Math.max(c, maxSum + c)}return max;
};

矩阵

矩阵是矩形的数组


A = [ 1 2 3 ][ 4 5 6 ]

是一个 2 * 3的矩阵A = (aij) ,矩阵中的元素为aij
通过交换行和列,得到矩阵A的转置AT

   [ 1 4 ][ 2 5 ][ 3 6 ]

向量是一维数组

   {  2  }
x = {  3  }{  5  }

是一个大小是3的向量,长度为n的向量为n向量,
xT = (2,3,5)

矩阵的乘法,就是两个相容的矩阵A和B,就是A的列数和B的行数一样

对于一个 n * n的矩阵乘法,普通的解法如下

function matrix_multiply(ma, mb) {let l = ma.length;let r = [];for (let x = 0; x < l; x++) {r[x] = [];for (let y = 0; y < l; y++) {let z = 0;for (let k = 0; k < l; k++) {z += (ma[x][k] + mb[k][y]);}r[x][y] = z;}}return r;}

明显需要花费Θ(n3)时间

使用分治法,分解原矩阵为子矩阵。

  // ma 和 mb都是n的矩阵function matrix_multiply_recurse(ma, mb) {let n = ma.length;let mc = [];if (n === 1) {mc[1][1] = ma[1][1] * mb[1][1];} else {//mc11 = matrix_multiply_recurse(ma11, mb11) + matrix_multiply_recurse(ma12, mb21)//mc12 = matrix_multiply_recurse(ma11, mb12) + matrix_multiply_recurse(ma12, mb22)//mc21 = matrix_multiply_recurse(ma21, mb11) + matrix_multiply_recurse(ma22, mb21)//mc22 = matrix_multiply_recurse(ma21, mb12) + matrix_multiply_recurse(ma22, mb22)}return mc;}

使用递归式来表示上述方法,对于n=1,的情况
T(1) = Θ(1)
当n > 1,把一个n问题规模分解为n/2,所以调用一次递归是T(n/2),总共8次, 每个子矩阵有n2/4个元素,每次矩阵加法需要Θ(n2) 的时间
T(n) = 8T(n/2)+ Θ(n2)
最后得到 T(n) = Θ(n3) ,并不优于上述直接的matrix_multiply方法。

以下为具体的实现,略复杂

 // A 和 B 都是n的矩阵,n是2的幂
function f(A, B) {let n = A.length;let C = [];if (n === 1) {C[0] = [];C[0][0] = A[0][0] * B[0][0];return C;}function add(A, B) {let r = [];let n = A.length;for (let a = 0; a < n; a++) {r[a] = [];for (let b = 0; b < n; b++) {r[a][b] = A[a][b] + B[a][b];}}return r;}const m = n / 2;// 分割两个矩阵let A11 = [], A12 = [], A21 = [], A22 = [];let B11 = [], B12 = [], B21 = [], B22 = [];A.slice(0, m).forEach(l => {A11.push(l.slice(0, m));A12.push(l.slice(m, n));});A.slice(m, n).forEach(l => {A21.push(l.slice(0, m));A22.push(l.slice(m, n));});B.slice(0, m).forEach(l => {B11.push(l.slice(0, m));B12.push(l.slice(m, n));});B.slice(m, n).forEach(l => {B21.push(l.slice(0, m));B22.push(l.slice(m, n));});let r1 = add(f(A11, B11), f(A12, B21));let r2 = add(f(A11, B12), f(A12, B22));let r3 = add(f(A21, B11), f(A22, B21));let r4 = add(f(A21, B12), f(A22, B22));C = [...r1, ...r3];let c2 = [...r2, ...r4];for (let i = 0; i < C.length; i++) {C[i] = [...C[i], ...c2[i]];}return C;}

strassen方法

就是用常数次矩阵乘法的代价较少了一次矩阵乘法。T(n) = Θ(nlg7)

递归树解递归式

每个节点表示单一子问题的代价,子问题对应某次递归调用。把树中每层的代价求和,就是总的代价。

由 T(n) = 3T(n/4) + cn2;把n替换为n/4得到
得到T(n/4) = 3
T(n/16) + c(n/4)2;

主方法解递归式

主方法就是T(n) = a(n/b) + f(n) 就是把规模是n的子问题划分为a个规模是n/b的子问题,
主定理就是:使得a >= 1和b > 1是常数,f(n)是一个函数T(n)是定义在非负整数上的递归式 T(n) = a(n/b) + f(n)
其中是 n/b 是向上取整或者是向下取整。那么T(n)有以下渐近界。

  1. 如果某个常数 e > 0 有f(n) = Ο(nlogba-e),那么 T(n) = Θ(nlogba);
  2. 如果 f(n) = Θ(nlogba), 则 T(n) = Θ(nlogbalgn);
  3. 如果某个常数 e > 0 有f(n) = Ω(nlogba+e),且对某个常数 c<1 和足够大的n 有af(n/b) <= cf(n) ,则 T(n) = Θ(f(n));

就是我们对比了,f(n)和 nlogba,两个函数较大者决定了递归式的解。如果后者大,就是情况1,如果前者大,就是情况3,如果情况相当,就是情况2.

具体分析,第1种情况,不是 f(n) 小于 nlogba 就够了, 而是多项式意义的小于。也就是说f(n)需要渐近小于 nlogba,要相差一个因子 ne, 其中e是大于0的常数。
第3种情况同样也是。注意,这3种情况并未覆盖f(n)的所有情况,情况1和情况2之间有间隙,2和3之间也有。如果这种情况下,就不可以使用主方法来求递归式。

使用主方法

例子1
T(n) = 9T(n/3) + n
a = 9;
b = 3;
logba = 2;
f(n) = n;
当e=1时,适用第一种情况
那么结果是 T(n) = Θ(n2);

例子2
T(n) = T(2n/3) + 1
a = 1;
b = 1.5;
logba = 0;
f(n) = 1;
适用第二种情况
结果是T(n) = Θ(lgn)

例子3
T(n) = 3T(n/4) + nlgn
a = 3;
b = 4;
logba = log43 = 0.8(约等于)
f(n) = nlgn;
f(n)= n0.8+e,当e=0.2时,可成立情况3
当c = 3/4时,成立。
cf(n) = 3/4 * nlgn
a
f(n/b) = 3* f(n/4) = 3/4 * n lg4/n <= c*f(n) 所以成立

算法导论 第一部分 第四章-分治策略相关推荐

  1. 算法导论读书笔记 第4章 分治策略

    在第2章中,归并排序算法使用了分治策略.即在分治策略中,递归地求解一个问题,在每层递归中应包含三个步骤: 分解(Divide)步骤将问题画分为一些子问题,子问题的形式与原问题一样,只是规模更小. 解决 ...

  2. 第四章 分治策略 4.1 最大子数组问题 (暴力求解算法)

    /*** 最大子数组的暴力求解算法,复杂度为o(n2)* @param n* @return*/static MaxSubarray findMaxSubarraySlower(int[] n) {l ...

  3. 第四章 分治策略 4.4 用递归树方法求解递归式

    4.4 用递归数方法求解递归式 一. 1.   在递归树中,每个结点表示一个单一子问题的代价,子问题对应某次递归函数调用.我们将树中每层中的代价求和,得到每层代价,然后将所有层的代价求和,得到所有层次 ...

  4. 带权中位数-算法导论第三版第九章思考题9-2

    带权中位数-算法导论第三版第九章思考题9-2 b 时间复杂度O(nlgn) float find_median_with_weights_b(float *array,int length) {qui ...

  5. 数据结构与算法python语言实现-第四章答案

    数据结构与算法python语言实现-第四章答案 4.1 def findmax(S, index=0):if index == len(S) - 1:return S[index]max=findma ...

  6. Java 北大青鸟 第一学期 第四章 选择结构(二) 上级练习+课后作业

    第一学期 第四章 选择结构二 示例1 实现购物菜单 实现换购的功能 本章练习一 计算器 下载地址 1. 选择结构 基本if选择结构 if(条件){条件成立时执行的代码} if-else选择结构 if( ...

  7. 算法导论第三版第二章思考题答案

    算法导论第三版第二章思考题答案 第二章思考题 算法导论第三版第二章思考题答案 2.1 2.2 2.3 2.4 汇总传送门 2.1 #include<iostream> using name ...

  8. 我眼中的算法导论 | 第一章——算法在计算中的作用、第二章——算法基础

    一个小白的算法学习之路.读<算法导论>第一天.本文仅作为学习的心得记录. 算法(Algorithm) 对于一个程序员来说,无论资历深浅,对算法一词的含义一定会或多或少有自己的体会,在< ...

  9. 【技术文档】《算法设计与分析导论》R.C.T.Lee等·第4章 分治策略

    分治策略有一种"大事化小,小事化了"的境界,它的思想是将原问题分解成两个子问题,两个子问题的性质和原问题相同,因此这两个子问题可以再用分治策略求解,最终将两个子问题的解合并成原问题 ...

最新文章

  1. 用户组修改工具samusrgrp
  2. hdu3006 状态压缩+位运算+hash(小想法题)
  3. 汇编语言--sbb指令
  4. 图像处理中常用数学知识
  5. 自由自在珍珠奶茶 喝出缤纷夏季
  6. Codeforces Round #494 (Div. 3)
  7. componentsJoinedByString 和 componentsSeparatedByString 的方法的区别
  8. c语言二叉树构造与输出,C语言数据结构树状输出二叉树,谁能给详细的解释一下...
  9. java三级报名_java web 学习 --第四天(Java三级考试)
  10. 【转】linux命令:ifconfig命令
  11. 20多个Maven命令和选项(备忘单)
  12. 优化美国服务器,美国服务器性能优化
  13. php常见错误和解决办法
  14. oracle 聚合函数 LISTAGG ,将多行结果合并成一行
  15. python产生随机数
  16. 《计算机操作系统(慕课版)》(汤小丹著)课后习题答案
  17. 指针输出与取地址符输出
  18. 车企号脉,资本试药,出行服务带病也要上场
  19. mysql 存储百分数_mysql中如何存储百分数
  20. usertoken_华为手机usertoken已过期

热门文章

  1. 实时系统RTX之理解一
  2. ScaleGestureDetector官方代码有坑,缩放图片会不停抖动解决方法
  3. 营员招募|心怀世界的AI青年们,联合国需要你为可持续发展助力!
  4. 什么是异质性群体?(32)
  5. 大学生为长经验“白打工”,值vs不值!
  6. 网络结构搜索之梯度可微
  7. 机器学习笔记(1)——线性回归
  8. 会声会影X5安装与调试记录
  9. 仓库摆放示意图_仓库货物摆放标准精编版.docx
  10. Intent MIME 打开各种类型的文件