讲的是函数的增长,显然比《算法导论》简洁得多。算法导论用了一章来讲的……我已经翻过算法导论了,所以只是复习一下。
以n^2为例,Ο定义的是最多也才n^2,Ω则是最少那么多。
如果是Θ则定义了上界与下界,是准确加在中间的。
ο则是表示它最多n^2但是达不到n^2,比如一个Θ(n),它肯定是不会超过Ο(n^2)的,也达不到Ο(n^2)。
这种微积分的东西还是要自己看定义才能更清楚、准确地理解。最后讲了几个计算用的结论。
就是RAM模型啦……
提出了最大子序列和问题并用图像和表格告诉你:降低算法的复杂度是很重要的
2.4.1与2.4.2介绍了分析算法复杂度的几个法则,并详细分析了上面说的fib计算的效率达到了指数级。还提到了一句格言:计算任何事情不要超过一次。
2.4.3提出了最大子序列问题的4个解,分别是n^3,n^2,nlogn与n的,通过实例讲解上面提到的分析方法。
n^3的方法是枚举起始点与终点,再用一个循环统计和
n^2的方法是枚举起点与子序列的长度
nlogn的方法很巧妙,书中说:要是没有那个线性的算法,那么这个算法就是体现递归威力的极好的范例了。采用分治的思想,把数据分成两部分,和最大的子序列要么在左边,要么在右边,要么横跨两边,所以递归计算两边,再从中间开始往两边加计算横跨两边的时候的最大子序列和,比较一下返回最大的就行了。
我模仿书中的代码打了一遍,帕斯卡命名法真的好麻烦,要时常按shift打大写字母,规范的变量命名确实让程序好看多了,以前自己乱打的a,b,c命名变量的程序,时间一长自己都看不懂了。看规范的代码简直是一种享受啊。
n的线性算法更NB,常数空间,线性时间就能算出来结果,而且是online algorithm,非常人能够独立想出来的。这个算法也是经典的DP题。思路还是看代码吧。
我把分治的算法与动态规划的解打了一遍,更加理解Vim了,正是恨不得有个shift键的脚踏板。
uses math;
var
a:array[1..10000] of longint;
i,n:longint;
{============================algorithm1==============================}
function MaxSum(left,right:longint):longint;
var
i,mid:longint;
MaxLeftSum,MaxRightSum,LeftMax,RightMax,LeftSum,RightSum:longint;
begin
if left=right then
if a[left]>0 then exit(a[left])
else exit(0);
mid:=(left+right) div 2;
MaxLeftSum:=MaxSum(left,mid);
MaxRightSum:=MaxSum(mid+1,right);
LeftMax:=0; RightMax:=0;
LeftSum:=0; RightSum:=0;
for i:=mid downto left do
begin
LeftSum:=LeftSum+a[i];
if LeftSum>LeftMax then LeftMax:=LeftSum;
end;
for i:=mid+1 to right do
begin
RightSum:=RightSum+a[i];
if RightSum>RightMax then RightMax:=RightSum;
end;
exit( max(LeftMax+RightMax , max( MaxLeftSum , MaxRightSum) ) );
end;
{============================algorithm2==============================}
function MaxSum_dp:longint;
var
i,ThisSum,MaxSum:longint;
begin
ThisSum:=0; MaxSum:=0;
for i:=1 to n do
begin
inc(ThisSum,a[i]);
if ThisSum>MaxSum then MaxSum:=ThisSum
else if ThisSum<0 then ThisSum:=0;
end;
exit(MaxSum);
end;
{=============================main===================================}
begin
readln(n);
for i:=1 to n do read(a[i]);
writeln(MaxSum(1,n));
writeln(MaxSum_dp);
end.
2.4.4讲的是几个复杂度是logn的算法:二分查找,辗转相除法与快速幂,都是非常经典的并且很常见的算法啊,还是打一下吧。
var
m,n:longint;
function gcd(m,n:longint):longint;
var
x:longint;
begin
while (n>0) do
begin
x:=m mod n;
m:=n;
n:=x;
end;
exit(m);
end;
begin
readln(m,n);
writeln(gcd(m,n));
end.
var
a:array[1..10000] of longint;
i,x,n:longint;
function find(x:longint):longint;
var
low,high,mid:longint;
begin
low:=1; high:=n;
var
a:array[1..10000] of longint;
i,x,n:longint;
function find(x:longint):longint;
var
low,high,mid:longint;
begin
low:=1; high:=n;
while low<=high do
begin
mid:=(low+high) div 2;
if a[mid]<x then low:=mid+1
else if a[mid]>x then high:=mid -1
else exit(mid);
end;
end;
begin
readln(n);
for i:=1 to n do read(a[i]);
read(x);
writeln(find(x));
end.
while low<=high do
begin
mid:=(low+high) div 2;
if a[mid]<x then low:=mid+1
else if a[mid]>x then high:=mid -1
else exit(mid);
end;
end;
begin
readln(n);
for i:=1 to n do read(a[i]);
read(x);
writeln(find(x));
end.
快速幂:
var
n,x:longint;
function pow(x,n:longint):longint;
begin
if n=0 then exit(1);
if n=1 then exit(x);
if odd(n) then exit(pow(x*x,n div 2)*x)
else exit(pow(x*x,n div 2));
end;
begin
readln(x,n);
writeln(pow(x,n));
end.
打快速幂的时候竟然又把变量打错了,想起了好几个月才通过的麦森数……
n=1其实不用另外判断的,因为下面当n为奇数的时候的处理是一样的。
要注意不要进入无限递归与重复计算
2.6我做了,是五个主要有for循环与if语句构成的程序段判断运行时间。
2.7挺有意思,生成前N个自然数的随即置换。题目给出了三种方法:
a.产生随机数直到不同于已经填入的数组元素,效率是n^2logn
b.建一个used数组标记已经用过的元素,nlogn
c.for i:=1 to n do swap(a[i],radrom(0,i)),liner
答案提到把radrom(0,i)换成(0,n)就不行了,若n=3,用这种方法有27种可能,而合理的方案有6种,27 mod 6<>0
2.10有必要提一下,高中数学教科书上说的是海伦-秦九昭算法,CLRS与本书都叫做Horner的计算多项式相加的算法,即计算sum(Ai*X^i)的算法;
for i:=n downto 1 do poly:=poly*x+Ai
能有效降低计算的次数,答案说运行的效率是liner的
2.11寻找已排序的A数组中是否有Ai=i,答案是说用变种的二分查找能得到logn的算法,有机会要仔细考虑一下。
2.12题目挺有意思,可是没答案,有时间好好想吧……
2.19查找主要元素不失为一道不错的练习题,有时间要好好研究一下
这一章内容比较多,应该掌握渐进符号(虽然不搞研究的话没什么用),最大子序列和问题和算法分析的基础,还有就是那三个logn的算法,特别常见。