算法导论笔记<2>
- 目录:
第三章 函数的增长
3.1 渐进记号
Θ 记号
Θ(g(n))={f(n):存在正常量c1、c2和n0,使得对于所有的n≥n0,有0≤c1g(n)≤f(n)≤c2g(n)}
O 记号
O(g(n))={f(n):存在正常量c1和n0,使得对于所有的n≥n0,有0≤f(n)≤c2g(n)} -
Ω
记号
O(g(n))={f(n):存在正常量c1和n0,使得对于所有的n≥n0,有0≤c2g(n)≤f(n)}
-
Ω
记号
Θ
记号被称为渐进紧确界,而
O
记号被称为渐进上界,
f(n)=Θ(g(n)) 当且仅当 f(n)=O(g(n)) 且 f(n)=Ω(g(n)) .
3.2 标准记号与常用函数
不得不佩服中国的基础教育,作为一个普通大学生看懂还是毫无压力的。
略
第四章 分支策略
4.1 最大字数组问题
额,书上的引入感觉表述的略微不清楚,按照我的理解来就是,你可以知晓一支股票未来几天的价格,但是你买入卖出只有一次机会,怎样才能收入最大化的问题。
《数据结构与算法分析-C语言描述》的第二章就详细的讲述了这个问题,但感觉这个问题在这有点不合时宜,因为分治法并非是这个题目最好的解法,以下是总结:
首先将这个问题转换为一个求最大子数组的问题,每一天相对于前一天股价的变化就是数组里边的每一个数,那么我们要求买卖收入最大的时机就是确定一段字数组相加结果最大。
那么如何求一个最大字数组呢?
方法有三:
1. 暴力法
这个不用多说,设i为起始天数,j为终止天数,将 n(n−1)2 种情况列出来就是
2.分治法
分治法的三步奏就是:分解、解决、合并,以下是详细说明:
- 分解:将一个数组分为左子串和右子串
- 解决: 分别求左右子串中的最大值
- 合并 :除左右子串中,最大子数组也可能存在于左右子串的中间,求三者的最大值并返回
代码如下:
#include<iostream>
using namespace std;
int MaxSubSum(int* Num,int Left,int Right)
{
int MaxLeftSum,MaxRightSum;
int LeftBorderSum,RightBorderSum;
int MaxLeftBorderSum,MaxRightBorderSum;
int Center,Max;
if(Left==Right)
if(Num[Right]<0)
return 0;
else
return Num[Right];
Center=(Right+Left)/2;
MaxLeftSum=MaxSubSum(Num,Left,Center);//计算左子串的最大连续值
MaxRightSum=MaxSubSum(Num,Center+1,Right);//计算右子串的最大连续值
LeftBorderSum=0,MaxLeftBorderSum=0;
RightBorderSum=0,MaxRightBorderSum=0;
for(int i=Center;i>=Left;i--)//从中间向左边求最大值
{
LeftBorderSum+=Num[i];
if(LeftBorderSum>MaxLeftBorderSum)
MaxLeftBorderSum=LeftBorderSum;
}
for(int i=Center+1;i<=Right;i++)//从中间向右边求最大值
{
RightBorderSum+=Num[i];
if(RightBorderSum>MaxRightBorderSum)
MaxRightBorderSum=RightBorderSum;
}
if(MaxLeftSum>MaxRightSum)//将三者进行比较
Max=MaxLeftSum;
else
Max=MaxRightSum;
if(Max<MaxLeftBorderSum+MaxRightBorderSum)
Max=MaxLeftBorderSum+MaxRightBorderSum;
return Max;//返回最大子串值
}
int main()
{
int Num[100];
int Length;
cin>>Length;
for(int i=0;i<Length;i++)
cin>>Num[i];
cout<<MaxSubSum(Num,0,Length-1)<<endl;
return 0;
}
3.贪心法
我们可以可以将数组中的正数看做一个水滴,而将数组的正数看做一段干涸的陆地(两个水滴之间想要融合肯定要损耗一定的水分),那么求最大子串就可以看做求最大融合水滴的问题,想想看,我们有一些水滴,之间可能夹杂着一些陆地,那么怎么将水滴融合才会最大呢?
首先,相邻水滴之间肯定可以融合,因为不需要付出任何代价,只会使水滴更大,那么相隔着陆地的两个水滴要不要融合呢?那要看穿过之间陆地要损耗多少水了,根据我们直觉,我们衡量的标准是损耗的水是否比较小的那个水滴大,如果较小的水滴穿过陆地后还有水的话,那么坑定是值得融合的了。(想想为什么?)那么这样到最后只会融合出几个较大的水滴了,那么此时只要选出他们最大的就是答案了。
代码如下:
#include<iostream>
using namespace std;
int MaxSubSum(int* Num,int Len)
{
int Max=0;
int Sum=0;
for(int i=0;i<Len;i++)
{
Sum+=Num[i];
if(Sum>Max)
Max=Sum;
else if(Sum<0)
Sum=0;
}
return Max;
}
int main()
{
int Num[100];
int Length;
cin>>Length;
for(int i=0;i<Length;i++)
cin>>Num[i];
cout<<MaxSubSum(Num,Length)<<endl;
return 0;
}
这种算法几乎是完美的算法, O(1) 的时间复杂度,并且也可以是 O(1) 的空间复杂度,不仅如此,他还是一个联机算法(任何时候都可给出答案)。
4.2 矩阵乘法的Strassen算法
Strassen的算法基于这样的原理:
拿复数乘法举例吧,
(a+bi)∗(c+di)
,我们如何用三次乘法得出结果实部
ac−bd
和虚部
ad+bc
,方法是我们可以求出
(a+b)∗(c−d)
,在求出
ad
和
bc
,因为
(a+b)∗(c−d)=ac+bc−ad−bd
,那么实部
ac−bd=(a+b)∗(c−d)−
bc+ad
,而虚部
ad+bc=ad+bc
.
这样做有什么好处呢?
在一维矩阵上看不出来,而对于多维矩阵来说,矩阵之间的乘法是 O(N3) 的,而矩阵的加法却是 O(N2) 。
4.3代入法求解递归式
代入法你妹啊!明明就是猜测法,先猜出可能的结果后证明,通常用数学归纳法证明。
以下是一些书中提到的注意点:
- 证明一个弱上界比证明一个强上界更困难,所以我们难以证明时,一般将用 T(n)−n 证明而非 T(n)+n
- 如下证明是错误的 T(n)≤cn+n 是错的,我们要证明 T(n)=O(n) 时,要显示的证明 T(n)≤cn
- 有时候我们不熟悉的递归式,如 T(n)=2T(n−−√)+lgn ,这时令 n=2m 可以将公式转换为 S(m)=2S(m/2)+m ,得出解后在代换回来.
4.4递归树求解递归式
没什么好说的,画出递归树就好了。
4.5用主方法求解递归式
公式太多了,哪天强迫症犯了在来写吧
总结
个人不是很喜欢分治,因为往往我不知道分治到底是怎样减少求解问题的复杂度的,可能和我愚笨和没有真正理解分治有关吧。
补充:
前几天Stitp老师和我们讲图像语义标注首先进行分割处理的时候,忽然一阵灵光划过了我的脑海,这不就
是分治么,思考之后,感悟如下:
分治法一般将问题分解成规模较小的问题,利用已经求解的子问题的内在关系,降低问题复杂度。