算法导论 第一部分 第四章-分治策略
算法导论 第一部分 第四章-分治策略
我们知道分治策略,就是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) = 3T(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)有以下渐近界。
- 如果某个常数 e > 0 有f(n) = Ο(nlogba-e),那么 T(n) = Θ(nlogba);
- 如果 f(n) = Θ(nlogba), 则 T(n) = Θ(nlogbalgn);
- 如果某个常数 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
af(n/b) = 3* f(n/4) = 3/4 * n lg4/n <= c*f(n) 所以成立
算法导论 第一部分 第四章-分治策略相关推荐
- 算法导论读书笔记 第4章 分治策略
在第2章中,归并排序算法使用了分治策略.即在分治策略中,递归地求解一个问题,在每层递归中应包含三个步骤: 分解(Divide)步骤将问题画分为一些子问题,子问题的形式与原问题一样,只是规模更小. 解决 ...
- 第四章 分治策略 4.1 最大子数组问题 (暴力求解算法)
/*** 最大子数组的暴力求解算法,复杂度为o(n2)* @param n* @return*/static MaxSubarray findMaxSubarraySlower(int[] n) {l ...
- 第四章 分治策略 4.4 用递归树方法求解递归式
4.4 用递归数方法求解递归式 一. 1. 在递归树中,每个结点表示一个单一子问题的代价,子问题对应某次递归函数调用.我们将树中每层中的代价求和,得到每层代价,然后将所有层的代价求和,得到所有层次 ...
- 带权中位数-算法导论第三版第九章思考题9-2
带权中位数-算法导论第三版第九章思考题9-2 b 时间复杂度O(nlgn) float find_median_with_weights_b(float *array,int length) {qui ...
- 数据结构与算法python语言实现-第四章答案
数据结构与算法python语言实现-第四章答案 4.1 def findmax(S, index=0):if index == len(S) - 1:return S[index]max=findma ...
- Java 北大青鸟 第一学期 第四章 选择结构(二) 上级练习+课后作业
第一学期 第四章 选择结构二 示例1 实现购物菜单 实现换购的功能 本章练习一 计算器 下载地址 1. 选择结构 基本if选择结构 if(条件){条件成立时执行的代码} if-else选择结构 if( ...
- 算法导论第三版第二章思考题答案
算法导论第三版第二章思考题答案 第二章思考题 算法导论第三版第二章思考题答案 2.1 2.2 2.3 2.4 汇总传送门 2.1 #include<iostream> using name ...
- 我眼中的算法导论 | 第一章——算法在计算中的作用、第二章——算法基础
一个小白的算法学习之路.读<算法导论>第一天.本文仅作为学习的心得记录. 算法(Algorithm) 对于一个程序员来说,无论资历深浅,对算法一词的含义一定会或多或少有自己的体会,在< ...
- 【技术文档】《算法设计与分析导论》R.C.T.Lee等·第4章 分治策略
分治策略有一种"大事化小,小事化了"的境界,它的思想是将原问题分解成两个子问题,两个子问题的性质和原问题相同,因此这两个子问题可以再用分治策略求解,最终将两个子问题的解合并成原问题 ...
最新文章
- 用户组修改工具samusrgrp
- hdu3006 状态压缩+位运算+hash(小想法题)
- 汇编语言--sbb指令
- 图像处理中常用数学知识
- 自由自在珍珠奶茶 喝出缤纷夏季
- Codeforces Round #494 (Div. 3)
- componentsJoinedByString 和 componentsSeparatedByString 的方法的区别
- c语言二叉树构造与输出,C语言数据结构树状输出二叉树,谁能给详细的解释一下...
- java三级报名_java web 学习 --第四天(Java三级考试)
- 【转】linux命令:ifconfig命令
- 20多个Maven命令和选项(备忘单)
- 优化美国服务器,美国服务器性能优化
- php常见错误和解决办法
- oracle 聚合函数 LISTAGG ,将多行结果合并成一行
- python产生随机数
- 《计算机操作系统(慕课版)》(汤小丹著)课后习题答案
- 指针输出与取地址符输出
- 车企号脉,资本试药,出行服务带病也要上场
- mysql 存储百分数_mysql中如何存储百分数
- usertoken_华为手机usertoken已过期