【读后感】编程珠玑(第二版)第八章

第八章 算法设计技术

  这一章主要说明一个问题,一个看似复杂的算法有时可以极大的缩短程序的运行时间。
8.1 问题及简单算法
一个数组,有正有负,求出和最大的序列的值。比如1 -2 3 5,答案就是8。
一道很经典的动态规划题,出处是不是在这里呀?
一个简单的算法,三层循环,枚举起点和终点,然后从起点开始加到终点,最后更新max值。看似简单,但是n三次方的复杂度是不能够接受的。

8.2 两个平方算法
接着作者提出了两个平方的算法,主要是针对上一个三次放算法的优化。
1. 因为每次都要计算从起点到终点的sum值,那么可以使用前面的sum值来简化计算这次sum值,这样就少了一层循环。
2. 在进入主体循环之前,计算Ai,代表从0到i的和,这样我需要从3到5的和,那么就可以通过A5-A2来得到。
虽然进行了优化,但算法还是n平方的。

8.3 分治算法
分治(divide and conquer),把原问题划分成2个子问题,然后求解出2个子问题,并将子问题的结果合并,就得到了原问题的解。
这样,对于求一个数组的最大子序列的值,我们就可以分成2个子序列,然后求这两个子序列的最大序列和,最后对求出的子问题的解进行合并,合并的方法有两种,一是两个解不能拼成一个更长的序列,那么就求出二者的最大值;二是两个解可以拼成一个更长的序列,那么答案就是二者的和。
怎么样才能求解出子问题的解呢?
float recmax(int l, int u)
{   int i, m;
float lmax, rmax, sum;
if (l > u)  /* zero elements */
return 0;
if (l == u)  /* one element */
return max(0, x[l]);
m = (l+u) / 2;
/* find max crossing to left */
lmax = sum = 0;
for (i = m; i >= l; i--) {  //注意,这里是从中间往左计算的
sum += x[i];
if (sum > lmax)
lmax = sum;
}
/* find max crossing to right */
rmax = sum = 0;
for (i = m+1; i <= u; i++) {
sum += x[i];
if (sum > rmax)
rmax = sum;
}
return max(lmax + rmax,
max(recmax(l, m), recmax(m+1, u)));
//max中的三个式子分别代表3种情况,左右相连,左最大,右最大。它们三个不可能同时最大
}

float alg3()
{   return recmax(0, n-1);
}

算法公式T(n) = 2T(n/2)+O(n),可推导出T(n) = O(nlogn)

8.4 扫描算法
作者提出一种思路,“假设我们已经解决了x[0...i-1]的问题,那么如何将其扩展为包含x[i]的问题呢?我们使用类似于分治算法的原理:前i个元素中,最大总和子数组要么在前i-1个元素中,要么其结束位置为i”
float alg4c()
{   int i;
float maxsofar = 0, maxendinghere = 0;
for (i = 0; i < n; i++) {
maxendinghere += x[i];
maxendinghere = maxfun(maxendinghere, 0);
maxsofar = maxfun(maxsofar, maxendinghere);
}
return maxsofar;
}
“理解这个程序的关键就在于变量maxendinghere。在循环中的第一个赋值语句之前,maxendinghere是结束位置为i-1的最大子向量的和;复制语句将其修改为结束位置为i的最大子向量的和。若加上x[i]之后结果依然为正值,则该赋值语句使maxendinghere增大x[i];若加上x[i]之后结果为负值,则该赋值语句就将maxendinghere重新设为0。”

8.6 原理
几种重要的算法设计技术。
保存状态,避免重复计算
将信息预处理至数据结构中
分治算法
扫描算法(与数组相关的题目经常可以通过思考(如何将x[0...i-1]的解扩展为x[0...i]的解)?)
累积
下界

原文地址:https://www.cnblogs.com/iammatthew/p/1803949.html