C
(
n
)
=
∑
i
=
0
n
−
2
∑
j
=
i
+
1
n
−
1
1
C(n) = \sum_{i=0}^{n-2}{\sum_{j=i+1}^{n-1}} 1
C(n)=∑i=0n−2∑j=i+1n−11
Θ(
n
2
n^2
n2)
没有用到额外辅助空间
不稳定
在位
执行次数(求和表达式结果):
C
(
n
)
=
∑
i
=
0
n
−
2
[
(
n
−
1
)
−
(
i
+
1
)
+
1
]
=
∑
i
=
0
n
−
2
(
n
−
1
−
i
)
=
n
(
n
−
1
)
2
C(n)=\sum_{i=0}^{n-2}[(n-1)-(i+1)+1]=\sum_{i=0}^{n-2}(n-1-i) =\frac{n(n-1)}{2}
C(n)=∑i=0n−2[(n−1)−(i+1)+1]=∑i=0n−2(n−1−i)=2n(n−1)
结论:对于任何输入,选择排序都是Θ(
n
2
n^2
n2)的算法。 优点:键的交换次数仅为Θ(
n
n
n),精确说是
n
−
1
n-1
n−1次,这个特点使得选择排序优于其他许多排序算法。
冒泡排序算法分析
输入规模:n(长度为n的数组)
基本操作:if(A[j] < A[j+1])
是否与具体输入有关:有关()
键的比较次数与选择排序相同,但是交换次数平均情况和最坏情况都是
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)
是一个垃圾排序算法,除了名字可爱一无是处。
键的比较次数
基本操作执行次数
时间复杂度
空间复杂度
稳定性
在位性
平均情况
C
(
n
)
=
∑
i
=
0
n
−
2
∑
j
=
i
+
1
n
−
1
1
C(n) = \sum_{i=0}^{n-2}{\sum_{j=i+1}^{n-1}} 1
C(n)=∑i=0n−2∑j=i+1n−11
Θ(
n
2
n^2
n2)
没有用到额外辅助空间
不稳定
在位
键的交换次数
最大次数
时间复杂度
空间复杂度
稳定性
在位性
平均 / 最坏情况
C
(
n
)
=
n
(
n
−
1
)
2
ϵ
Θ
(
n
2
)
C(n) = \frac{n(n-1)}{2}\epsilon \Theta(n^2)
C(n)=2n(n−1)ϵΘ(n2)
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)
不稳定
在位
蛮力字符串匹配
最近对问题
第四章-减治法
求解问题思路,找整个问题的解和子问题的解
定义:利用一个问题给定实例的解和同样问题较小实例的解的某种关系
自顶向下求解:通常递归
自底向上求解:通常迭代(非递归比较好)
三种:
减常量(通常减一法):插入排序,拓扑排序
减常因子(通常减
1
2
\frac{1}{2}
21,俗称减半法):二分检索,假币问题
减可变规模:欧几里得算法(辗转相除法求最大公约数),选择算法
区分减半法与分治法:(以计算
a
n
a^n
an为例)
减一法:
a
n
=
a
∗
a
n
−
1
a^n=a*a^{n-1}
an=a∗an−1
分治法:
a
n
=
a
n
2
∗
a
n
2
a^n=a^{\frac{n}{2}}*a^{\frac{n}{2}}
an=a2n∗a2n
F
(
a
,
n
)
=
{
r
e
t
u
r
n
(
a
)
,
n
=
1
r
e
t
u
r
n
(
F
(
a
,
n
2
)
∗
F
(
a
,
n
2
)
)
,
n
≠
1
F(a,n)=\begin{cases} return(a),n=1\\ return (F(a,\frac{n}{2})*F(a,\frac{n}{2})), n\neq1\end{cases}
F(a,n)={return(a),n=1return(F(a,2n)∗F(a,2n)),n=1
基本操作执行次数(迭代次数)
{
M
(
n
)
=
M
(
n
2
∗
2
+
1
)
M
(
1
)
=
0
\begin{cases} M(n)=M(\frac{n}{2}*2+1)\\ M(1)=0\end{cases}
{M(n)=M(2n∗2+1)M(1)=0
减半法:
a
n
=
a
n
2
∗
a
n
2
a^n=a^{\frac{n}{2}}*a^{\frac{n}{2}}
an=a2n∗a2n(虽然递推式看不出区别,但是算法实现起来有区别)
F
(
a
,
n
)
=
{
r
e
t
u
r
n
(
a
)
,
n
=
1
r
e
t
u
r
n
(
F
(
a
,
n
2
)
2
)
,
n
≠
1
F(a,n)=\begin{cases} return(a),n=1\\ return (F(a,\frac{n}{2})^2), n\neq1\end{cases}
F(a,n)={return(a),n=1return(F(a,2n)2),n=1
基本操作执行次数(迭代次数)
{
M
(
n
)
=
M
(
n
2
+
1
)
M
(
1
)
=
0
\begin{cases} M(n)=M(\frac{n}{2}+1)\\ M(1)=0\end{cases}
{M(n)=M(2n+1)M(1)=0
这个求解
a
n
a^n
an的案例中,减半法的原来规模与减半后规模的关系是:
原
来
规
模
=
减
半
规
模
2
原来规模={减半规模}^2
原来规模=减半规模2
如果n为奇数,
a
n
=
(
a
n
−
1
2
)
2
×
a
a^n=(a^{\frac{n-1}{2}})^2\times a
an=(a2n−1)2×a
算法时间复杂度为
Θ
(
log
n
)
\Theta(\log n)
Θ(logn)
减常数(减1法)(√)
插入排序(减一法,递归)(但是迭代效率显然更高
算法思想:对于一串长度为
n
−
1
n-1
n−1已经排好序的数组
A
[
0
,
n
−
2
]
A[0,n-2]
A[0,n−2],每次将第
n
−
1
n-1
n−1个元素插入到排好序的数组间形成长度为
n
n
n的排好序数组,插入到合适位置后,后面的元素要依次往后面挪一位。
C
(
n
)
=
∑
i
=
1
n
−
1
∑
j
=
0
i
−
1
1
=
∑
i
=
1
n
−
1
i
=
n
(
n
−
1
)
2
C(n) = \sum_{i=1}^{n-1}{\sum_{j=0}^{i-1}} 1=\sum_{i=1}^{n-1}i=\frac{n(n-1)}{2}
C(n)=∑i=1n−1∑j=0i−11=∑i=1n−1i=2n(n−1)
Θ(
n
2
n^2
n2)
额外辅助空间常数个
稳定
在位
最优情况
C
(
n
)
=
∑
i
=
1
n
−
1
1
=
n
−
1
C(n) = \sum_{i=1}^{n-1} 1=n-1
C(n)=∑i=1n−11=n−1
Θ(
n
n
n)
额外辅助空间常数个
稳定
在位
平均情况
C
(
n
)
=
1
N
∑
i
=
n
−
1
n
(
n
−
1
)
2
≈
n
2
4
C(n) =\frac{1}{N}\sum_{i=n-1}^{\frac{n(n-1)}{2}}\approx\frac{n^2}{4}
C(n)=N1∑i=n−12n(n−1)≈4n2
算法思想:(前提:在有序数组中进行查找),通过比较键
K
K
K与数组中间元素
A
[
m
]
A[m]
A[m]来完成查找工作,如果它们相等,则算法结束。否则,如果
K
<
A
[
m
]
K<A[m]
K<A[m]则对数组前半部分执行该操作,如果
K
>
A
[
m
]
K>A[m]
K>A[m]则对数组后半部分执行该操作。
折半查找效率标准方法:计算找键和数组的元素的比较次数。
键的比较次数
基本操作执行次数(递推式)
时间复杂度
最坏情况
C
w
r
o
s
t
(
n
)
=
C
w
r
o
s
t
(
⌊
n
2
⌋
)
+
1
,
C
w
r
o
s
t
(
1
)
=
1
C_{wrost}(n) =C_{wrost}(\lfloor \frac{n}{2} \rfloor)+1,C_{wrost}(1)=1
Cwrost(n)=Cwrost(⌊2n⌋)+1,Cwrost(1)=1(递推式结果为
C
w
r
o
s
t
(
n
)
=
⌊
log
2
n
⌋
+
1
=
⌈
log
2
n
+
1
⌉
C_{wrost}(n)=\lfloor \log_2n \rfloor+1=\lceil \log_2{n+1} \rceil
Cwrost(n)=⌊log2n⌋+1=⌈log2n+1⌉)
Θ(
log
n
\log n
logn)
平均情况
Θ(
log
2
n
\log_2n
log2n)
计算上述递推式详细过程:需要先采用平滑规则(即对
n
n
n取
n
=
2
k
n=2^k
n=2k),再运用反向替换法。
当
k
>
0
k>0
k>0时,
A
(
2
k
)
=
A
(
2
k
−
1
)
+
1
A(2^k)=A(2^{k-1})+1
A(2k)=A(2k−1)+1,
A
(
2
0
)
=
1
A(2^0)=1
A(20)=1。这是经过平滑后的递推式,可以很轻松的利用反向替换法求解了。
反向替换法,
A
(
2
k
)
=
A
(
2
k
−
1
)
+
1
=
(
A
(
2
k
−
2
)
+
1
)
+
1
=
A
(
2
k
−
2
)
+
2
A(2^k)=A(2^{k-1})+1=(A(2^{k-2})+1)+1=A(2^{k-2})+2
A(2k)=A(2k−1)+1=(A(2k−2)+1)+1=A(2k−2)+2,即将
A
(
2
k
−
1
)
A(2^{k-1})
A(2k−1)用
A
(
2
k
−
2
)
+
1
A(2^{k-2})+1
A(2k−2)+1替换,一直替换最终得到
A
(
2
k
)
=
A
(
2
k
−
k
)
+
k
=
A
(
2
0
)
+
k
=
1
+
k
A(2^k)=A(2^{k-k})+k=A(2^0)+k=1+k
A(2k)=A(2k−k)+k=A(20)+k=1+k。
再将
n
n
n换回来,
∵
n
=
2
k
∴
k
=
log
2
n
\because n=2^k \therefore k=\log_2n
∵n=2k∴k=log2n,替换回去就得到上面表格
C
w
r
o
s
t
(
n
)
=
⌊
log
2
n
⌋
+
1
C_{wrost}(n)=\lfloor \log_2n \rfloor+1
Cwrost(n)=⌊log2n⌋+1,因为
n
n
n一定是整数,所以还得有一个向下取整的过程
⌊
k
⌋
\lfloor k \rfloor
⌊k⌋。
二分查找基本思想:将所有硬币分成两堆,每一堆有
⌊
n
2
⌋
\lfloor \frac{n}{2} \rfloor
⌊2n⌋个硬币。如果
n
n
n是奇数,则将最后一枚留下,剩下的均分为两堆,如果重量相同,则留下的这一枚是假币;如果重量不同,则再以同样的方式判断被均分后的两堆硬币(递归思想)。
递推式:
W
(
n
)
=
W
(
⌊
n
2
⌋
)
+
1
,
W
(
1
)
=
0
W(n) =W(\lfloor \frac{n}{2} \rfloor)+1,W(1)=0
W(n)=W(⌊2n⌋)+1,W(1)=0,与最坏情况下的折半查找比较次数的递推式除了初始条件外基本一致。 原因:两种算法都是基于相同的设计技术:把问题规模减半。
import java.util.*;/**
* 类 Coin 是假币问题的二分求解方法,基于减治法的减常因子法。
* 采用的递归求解
*/publicclassCoin{publicstaticint[]coins;privatestatic Scanner in =newScanner(System.in);publicstaticvoidrun(){
System.out.println("-------------------------");
System.out.print("请输入硬币的数目:");int n = in.nextInt();//n Coins Numberwhile(n<3){
System.out.println("两枚及以下数量硬币无法判断真假,请重新输入硬币数量:");
n = in.nextInt();}
System.out.println("-------------------------");
coins =newint[n];//n = (int) (Math.random() * n);
Random random =newRandom();int m = random.nextInt(n-1);//m表示假币位置int weight_T = random.nextInt(10);//生成真硬币重量while(weight_T<=0){
weight_T = random.nextInt(10);}int weight_F = random.nextInt(10);//生成假硬币重量while(weight_F<=0|| weight_F==weight_T){
weight_F = random.nextInt(10);}for(int i =0; i<n; i++) coins[i]= weight_T;
System.out.println("随机生成的假币重量为:"+ weight_F);
System.out.println("随机生成的真币重量为:"+ weight_T);
System.out.println("随机生成的假币位置为:"+ m);
coins[m]= weight_F;
System.out.println("二分查找到的假币位置为:"+fun(0, coins.length-1));}privatestaticintfun(int from,int to){//[form,to]if(to - from ==1){//硬币数量为2if(coins[from]==coins[to])return-1;else{if(from==0){if(coins[from]==coins[to+1])return to;elsereturn from;}else{if(coins[from]==coins[from-1])return to;elsereturn from;}}}int length = to - from +1;if(length %2!=0){
length--;}int a =0;//上半部分硬币重量for(int i = from; i < from + length /2; i++){
a += coins[i];}int b =0;//下半部分硬币重量for(int i = from + length /2; i < from + length; i++){//奇数:最后一个硬币没加上
b += coins[i];}if(a == b && length != to - from && coins[to]!= coins[to -1]){//奇数枚硬币,最后一枚是假的return to;}if(a != b){int res =fun(from, from + length /2);if(res !=-1){return res;}int res1 =fun(from + length /2, to);if(res1 !=-1){return res1;}}return-1;}}
俄氏乘法
基本思想:
n
×
m
=
n
2
×
2
m
n\times m=\frac n2\times 2m
n×m=2n×2m,对于
n
n
n 为奇数,则
n
×
m
=
n
−
1
2
×
2
m
+
m
n\times m=\frac {n-1}2\times 2m + m
n×m=2n−1×2m+m,直到
1
×
m
=
m
1\times m=m
1×m=m,该算法使得硬件实现的速度非常快,仅仅使用二进制移位和基本的加法。
约瑟夫问题
J
(
n
)
J(n)
J(n) 求解方法,将
n
n
n 化成二进制,进行一次向左循环移位即可得到结果。
通用分治递推式:假设一个规模为n的实例可以被划分为b个规模为
n
b
\frac{n}{b}
bn的实例,其中a个实例需要求解。为了简化分析,假设n是b的幂,对于算法的运行时间有如下递推式:
T
(
n
)
=
a
T
(
n
b
)
+
f
(
n
)
T(n)=aT(\frac{n}{b})+f(n)
T(n)=aT(bn)+f(n),其中
f
(
n
)
f(n)
f(n)是一个函数,表示将问题分解为小问题和将结果合并起来所消耗的时间。
应用主定理可以大大简化以上公式的计算:
如果在递推式中==
f
(
n
)
ϵ
Θ
(
n
d
)
f(n)\epsilon\Theta(n^d)
f(n)ϵΘ(nd)==,其中
d
≥
0
d\geq0
d≥0,那么会有
T
(
n
)
ϵ
{
Θ
(
n
d
)
,
当
a
<
b
d
时
Θ
(
n
d
log
n
)
,
当
a
=
b
d
时
Θ
(
n
log
b
a
)
,
当
a
>
b
d
时
T(n)\epsilon \begin{cases} \Theta(n^d),当a<b^d时\\ \Theta(n^d\log n),当a=b^d时\\ \Theta(n^{\log_ba}),当a>b^d时 \end{cases}
T(n)ϵ⎩⎪⎨⎪⎧Θ(nd),当a<bd时Θ(ndlogn),当a=bd时Θ(nlogba),当a>bd时
有了该结论,无需再对递推式求解,只要列出递推式就可以知道该算法的效率。
结论对于其他两个渐进符号也是成立的。
难点就在于怎么求
f
(
n
)
f(n)
f(n)
归并排序
思路
对于一个需要排序的数组
A
[
0..
n
−
1
]
A[0..n-1]
A[0..n−1],合并排序把它一分为二:
A
[
0..
⌊
n
2
⌋
−
1
]
A[0..\lfloor \frac{n}{2} \rfloor-1]
A[0..⌊2n⌋−1]和
A
[
⌊
n
2
⌋
.
.
n
−
1
]
A[\lfloor \frac{n}{2} \rfloor..n-1]
A[⌊2n⌋..n−1],并对每个子数组递归排序,然后把这两个排好序的子数组合并为一个有序数组。这是递归部分
设Merge算法中进行比较的时
l
e
n
g
t
h
1
length1
length1 和
l
e
n
g
t
h
2
length2
length2
最好情况下每一轮比较次数为
l
e
n
g
t
h
1
<
l
e
n
g
t
h
2
?
l
e
g
n
t
h
1
:
l
e
n
g
t
h
2
length1<length2 ?legnth1:length2
length1<length2?legnth1:length2 ,两个被划分到数组中长度较短的那一个。
最坏情况下每一轮比较次数为
n
−
1
n-1
n−1,也就是
l
e
n
g
t
h
1
+
l
e
n
g
t
h
2
−
1
length1+length2-1
length1+length2−1 次比较。
根据分治法的通用递推公式和主定理可以快速求解出该排序算法的时间效率。
本算法基本操作是合并中的键值不断进行比较,所以
C
m
e
r
g
e
(
n
)
C_{merge}(n)
Cmerge(n)求解的是合并阶段进行键值比较的次数。
最坏情况下:例如
B
[
4
]
=
B[4]=
B[4]= {1,3,5,7},
C
[
4
]
=
C[4]=
C[4]={2,4,6,8},需要比较7次。所以可以得到最坏情况下n个元素分成两组进行比较次数为
n
−
1
n-1
n−1 次。记住这
C
m
e
r
g
e
(
n
)
C_{merge}(n)
Cmerge(n)仅为一轮递归中的比较次数,而非完整一个归并排序中所有的比较次数之和。
当
n
>
1
n>1
n>1时,
C
(
n
)
=
2
C
(
n
2
)
+
C
m
e
r
g
e
(
n
)
,
C
(
1
)
=
0
C(n)=2C(\frac{n}{2})+C_{merge}(n),C(1)=0
C(n)=2C(2n)+Cmerge(n),C(1)=0
最坏情况下:
C
m
e
r
g
e
(
n
)
=
n
−
1
C_{merge}(n)=n-1
Cmerge(n)=n−1
由主定理可知,
a
=
2
,
b
=
2
,
d
=
1
,
∴
a
=
b
d
,
C
w
o
r
s
t
ϵ
Θ
(
n
d
log
n
)
a=2,b=2,d=1,\therefore a=b^d,C_{worst}\epsilon\Theta(n^d\log n)
a=2,b=2,d=1,∴a=bd,CworstϵΘ(ndlogn),
C
(
n
)
ϵ
Θ
(
n
log
n
)
C(n)\epsilon\Theta(n\log n)
C(n)ϵΘ(nlogn)
快速排序的划分,使得
A
[
s
]
A[s]
A[s]左边的元素都大于/小于
A
[
s
]
A[s]
A[s],右边的元素都小于/大于
A
[
s
]
A[s]
A[s]。建立此划分即得到了
A
[
s
]
A[s]
A[s]在整个数组中的最终位置,然后继续对
A
[
s
]
A[s]
A[s]左边和右边的两组划分进行分治(递归思想)。
快速排序中,算法的主要工作在于划分阶段,不需要再去合并子问题的解了。
划分阶段采用的算法是霍尔算法:
霍尔算法将当前数组
A
[
f
r
o
m
,
t
o
]
A[from,to]
A[from,to] 的第一个元素作为划分元素
m
=
A
[
f
r
o
m
]
m = A[from]
m=A[from]。
两个指针
i
,
j
i,j
i,j,一个指向当前数组第二个元素,另一个指针指向当前数组最后一个元素,相向进行扫描。
当
i
i
i 指向的元素大于/小于划分元素
m
m
m 且(&&)
j
j
j 指向的元素小于/大于划分元素
m
m
m 时(注意:两个指针的移动不一定是同步的,有可能某一个指针指向大于/小于元素
m
m
m 的值,但是另一个指针还未指向小于/大于元素
m
m
m ,这时需要继续移动另一个,直到两个指针都满足条件),交换两个指针指向的元素 (注意:不是交换指针)
当
i
≥
j
i\geq j
i≥j时,本轮以
m
=
A
[
f
r
o
m
]
m = A[from]
m=A[from]为划分元素的划分结束
此时
j
j
j 指针指向的位置就是划分元素
m
m
m 的最终位置,交换
∗
j
*j
∗j 与
m
m
m,即完成。
再对两个被
m
m
m 划分的数组部分进行分治(划分)。
时间复杂度
快速排序中也是通过比较次数来衡量。
快速排序中比较次数与具体输入情况有关。
最优情况,是每一轮划分元素的位置处于当前数组
A
[
f
r
o
m
,
t
o
]
A[from,to]
A[from,to] 的中间。这样仅需要后面每一个元素与划分元素比较一次,由于最后
i
≥
j
i\geq j
i≥j ,肯还会多出 1~2 比较,所以比较次数
ϵ
Θ
(
n
)
\epsilon\Theta(n)
ϵΘ(n),即
d
=
1
d=1
d=1。
最坏情况,是每一轮划分元素的位置处于当前数组
A
[
f
r
o
m
,
t
o
]
A[from,to]
A[from,to] 的某一端。例如一个非降排序,则初始用第一个(也就是整个数组最小元素)作为划分元素,得到划分结果是其中一个是空集,另一个是剩下所有元素。最坏情况下的快速排序退化成了选择排序。
递推公式同样可以套用分治法通用递推公式,只需要找到
f
(
n
)
f(n)
f(n) ,其中
n
=
t
o
−
f
r
o
m
+
1
n=to-from+1
n=to−from+1表示当前数组长度。
最优情况下的递推式,每一轮比较次数为
n
n
n,按照通用递推式写出快速排序最优情况下的:
C
b
e
s
t
(
n
)
=
2
C
b
e
s
t
(
n
2
)
+
n
,
C
b
e
s
t
(
1
)
=
0
C_{best}(n)=2C_{best}(\frac{n}{2})+n,C_{best}(1)=0
Cbest(n)=2Cbest(2n)+n,Cbest(1)=0
最坏情况下退化成选择排序的最坏情况,所以
C
w
o
r
s
t
=
(
n
+
1
)
+
n
+
…
…
+
3
=
(
n
+
1
)
(
n
+
2
)
2
−
3
ϵ
Θ
(
n
2
)
C_{worst}=(n+1)+n+……+3=\frac{(n+1)(n+2)}{2}-3\epsilon\Theta(n^2)
Cworst=(n+1)+n+……+3=2(n+1)(n+2)−3ϵΘ(n2)。
平均情况下,假设分裂点最终位于每个位置的概率都是
1
n
\frac{1}{n}
n1 ,递推式为
1
n
∑
s
=
0
n
−
1
[
(
n
+
1
)
+
C
a
v
g
(
s
)
+
C
a
v
g
(
n
−
1
−
s
)
]
,
C
a
v
g
(
0
)
=
0
,
C
a
v
g
(
1
)
=
1
\frac{1}{n}\sum_{s=0}^{n-1}[(n+1)+C_{avg}(s)+C_{avg}(n-1-s)],C_{avg}(0)=0,C_{avg}(1)=1
n1∑s=0n−1[(n+1)+Cavg(s)+Cavg(n−1−s)],Cavg(0)=0,Cavg(1)=1,结果为
C
a
v
g
≈
2
n
ln
n
≈
1.39
n
log
2
n
C_{avg}\approx2n\ln n\approx 1.39n\log_2n
Cavg≈2nlnn≈1.39nlog2n。
快速排序代码
快速排序总结
快速排序在平均情况下,仅比最优情况多执行
39
%
39\%
39% 的比较操作
其内层循环效率非常高,在处理随机排列的数组时,速度比合并排序(归并排序)快。
缺点:不稳定,还需要一个栈存储未被排序的子数组参数??(相较于堆排序空间效率
O
(
1
)
O(1)
O(1)比较差)。
按照列出乘法竖式手算的逻辑,乘数的每一位都要与被乘数进行一次
n
n
n 位和
1
1
1 位的乘法运算,这样一共需要
n
∗
m
n*m
n∗m 次位乘。
Strassen(斯特拉森)矩阵乘法
传统的矩阵相乘,所需要的惩罚次数
n
3
n^3
n3,加法次数
n
3
n^3
n3 和
n
3
−
n
2
n^3-n^2
n3−n2。
而斯特拉森算法计算
2
∗
2
2*2
2∗2 矩阵只需要进行7次乘法,这里用
M
(
n
)
M(n)
M(n) 表示乘法次数,用
A
(
n
)
A(n)
A(n) 表示加法次数,得到递推公式:
乘法次数:
M
(
n
)
=
7
∗
M
(
n
2
)
,
M
(
1
)
=
1
M(n)=7*M(\frac n2),M(1)=1
M(n)=7∗M(2n),M(1)=1,得到
a
=
7
,
b
=
2
,
d
=
0
a=7,b=2,d=0
a=7,b=2,d=0,根据大定理,满足
a
>
b
d
∴
M
(
n
)
ϵ
Θ
(
n
l
o
g
2
7
)
=
Θ
(
n
2.81
)
a>b^d \therefore M(n)\epsilon\Theta(n^{log_27})=\Theta(n^{2.81})
a>bd∴M(n)ϵΘ(nlog27)=Θ(n2.81)。
加法次数:
A
(
n
)
=
7
∗
A
(
n
2
)
+
18
(
n
2
×
n
2
)
,
A
(
1
)
=
0
A(n)=7*A(\frac n2)+18(\frac n2\times\frac n2),A(1)=0
A(n)=7∗A(2n)+18(2n×2n),A(1)=0,的都
a
=
7
,
b
=
2
,
d
=
2
a=7,b=2,d=2
a=7,b=2,d=2,根据大定理,满足
a
>
b
d
∴
A
(
n
)
ϵ
Θ
(
n
l
o
g
2
7
)
=
Θ
(
n
2.81
)
a>b^d\therefore A(n)\epsilon\Theta(n^{log_27})=\Theta(n^{2.81})
a>bd∴A(n)ϵΘ(nlog27)=Θ(n2.81)。
由于加法次数与乘法次数是相同的,所以Strassen斯特拉森矩阵乘法属于
Θ
(
n
2.81
)
\Theta(n^{2.81})
Θ(n2.81)。
霍纳法则的加法次数与乘法次数均为
n
n
n 次,算法效率
ϵ
Θ
(
n
)
\epsilon\Theta(n)
ϵΘ(n)。
//系数数组存储的系数是按照 高位到低位,即[2,-1,3,1,-5]// [x^4 ~ x^0]intHorner(int*A,n,x){//n表示这是一个n次多项式,x表示代入计算的值int result = A[0];for(int i =1; i<=n; i++){
result = result*x+A[i];}return result;}
问题化简
求最小公倍数(lcm,least common multiple)
基本思路:将利用质因数分解求解最小公倍数转换成先利用欧几里得算法计算最大公约数
g
c
d
(
m
,
n
)
gcd(m,n)
gcd(m,n),再利用公式
l
c
m
(
m
,
n
)
=
m
×
n
g
c
d
(
m
,
n
)
lcm(m,n)=\frac{m\times n}{gcd(m,n)}
lcm(m,n)=gcd(m,n)m×n
基本思路:(求某一个函数的最大值的问题将其称为最大化问题,同理也有最小化问题),如果已知求函数最大值的算法,可以利用公式
m
i
n
f
(
x
)
=
−
m
a
x
[
−
f
(
x
)
]
minf(x)=-max[-f(x)]
minf(x)=−max[−f(x)] ,即求最小值可以转换成求其负函数(即与
x
x
x 轴对称的函数)的最大值。
问题描述:给定
n
n
n 个重量为
w
1
,
w
2
,
…
,
w
n
w_1,w_2,…,w_n
w1,w2,…,wn,价值为
v
1
,
…
,
v
n
v_1,…,v_n
v1,…,vn 的物品和一个承受能力为
W
W
W 的背包,求怎样选择物品放进背包,让背包中的价值最大,且不超重。(假设物品重量和背包承重为整数,而物品数量不一定是整数)
最优解的形式:二元组
A
=
(
{
1
,
2
,
…
,
n
}
,
W
)
=
{
n
∈
A
n
∉
A
A=(\{1,2,…,n\},W)=\begin{cases} n\in A \\ n \notin A \end{cases}
A=({1,2,…,n},W)={n∈An∈/A ,所以构建一个
n
×
W
n\times W
n×W的表格(矩阵)
得到递推式,
i
i
i 表示第
i
i
i 个物品,
j
j
j 表示背包容量大小。
F
(
i
,
j
)
=
{
m
a
x
{
F
(
i
−
1
,
j
)
,
F
(
i
−
1
,
j
−
w
i
)
+
v
i
}
,
j
−
w
i
≥
0
F
(
i
−
1
,
j
)
,
j
−
w
i
<
0
F(i,j)=\begin{cases}max\{F(i-1,j),F(i-1,j-w_i)+v_i\},j-w_i\geq0\\F(i-1,j)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ,j-w_i<0 \end{cases}
F(i,j)={max{F(i−1,j),F(i−1,j−wi)+vi},j−wi≥0F(i−1,j),j−wi<0
递推式初始条件:
F
(
0
,
j
)
=
0
,
F
(
i
,
0
)
=
0
F(0,j)=0,F(i,0)=0
F(0,j)=0,F(i,0)=0,即表格(矩阵)的第一行第一列全为0。
物品
重量
价值
1
2
2
2
12
12
12
2
1
1
1
10
10
10
3
3
3
3
20
20
20
4
2
2
2
15
15
15
j
i
j\\i
ji
0
1
2
3
4
5
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
0
0
0
0
0
0
12
12
12
12
12
12
12
12
12
12
12
12
2
0
0
0
10
10
10
12
12
12
22
22
22
22
22
22
22
22
22
3
0
0
0
10
10
10
12
12
12
22
22
22
30
30
30
32
32
32
4
0
0
0
10
10
10
15
15
15
25
25
25
30
30
30
37
37
37
回溯过程:比较
F
(
i
,
j
)
和
F
(
i
−
1
,
j
)
F(i,j)和F(i-1,j)
F(i,j)和F(i−1,j) 。
不相等一定最终结果一定包含第
i
i
i 个物品,相等则还需要比较
F
(
i
−
1
,
j
)
和
F
(
i
−
1
,
j
−
w
i
)
+
v
i
F(i-1,j)\ 和\ F(i-1,j-w_i)+v_i
F(i−1,j)和F(i−1,j−wi)+vi
如果
F
(
i
−
1
,
j
)
>
F
(
i
−
1
,
j
−
w
i
)
+
v
i
F(i-1,j)\ >\ F(i-1,j-w_i)+v_i
F(i−1,j)>F(i−1,j−wi)+vi,则包含第
i
−
1
i-1
i−1 个物品。
如果相等则存在这样的可能:第
i
i
i 个物品存在的价值总和与第
i
i
i 个物品不存在但是第
i
−
1
i-1
i−1 个物品存在的价值总和相等,那么则出现了有多种不同的最优子集。
对于背包问题的效率分析
填表阶段:
{
时
间
效
率
∈
Θ
(
n
W
)
空
间
效
率
∈
Θ
(
n
W
)
\begin{cases} 时间效率\in \Theta(nW) \\ 空间效率 \in \Theta(nW) \end{cases}
{时间效率∈Θ(nW)空间效率∈Θ(nW)
回溯求最优子集组成元素的过程,
时
间
效
率
∈
O
(
n
)
时间效率\in \Omicron(n)
时间效率∈O(n),
O
\Omicron
O小于等于
Warshell算法与Floyd算法
Warshell算法
(考试要求:Warshell算法要会填表)
基本思路:
n
n
n 个节点的有向图(
n
n
n 阶邻接矩阵),求解传递闭包。循环遍历第
i
i
i 列,让第
i
i
i 列为
1
1
1 的那一行
x
x
x 与第
i
i
i 行进行并运算,改变第
x
x
x 行的值。 i++ 直到i<n。
算法效率:
∈
Θ
(
n
3
)
\in \Theta(n^3)
∈Θ(n3)
Floyd算法
时间效率:
∈
Θ
(
n
3
)
\in \Theta(n^3)
∈Θ(n3)
第九章-贪婪技术
贪婪技术总述
求解问题的思路:仅能运用于最有问题
三个条件(goodenotes跳转P256)
可行的:即必须满足问题的约束。
局部最优:它是当前步骤中所有可行选择中最佳的局部选择。
不可取消:即一旦做出选择,在后面的算法步骤中就无法改变了。
Prim算法
算法基本思想:有向连通图
G
=
<
V
,
E
>
G=<V,E>
G=<V,E>,不断归并顶点。
算法效率:(取决于数据结构)
如果是邻接矩阵,则是
Θ
(
∣
V
∣
2
)
\Theta(|V|^2)
Θ(∣V∣2),表示顶点集合数量的平方。
如果是邻接链表,则是
Θ
(
∣
E
∣
log
∣
V
∣
)
\Theta(|E|\log|V|)
Θ(∣E∣log∣V∣)。
第一章-绪论什么是算法算法定义:算法是一系列解决问题的明确指令,也就是说,对于符合一定规范的输入,就能在有限时间内获得要求的输出。第二章-算法效率分析基础Ο-小于等于读作OΘ-等于读作(theta)Ω-大于等于读作(omega)第三章-蛮力法问题的描述一种简单直接地解决问题的方法,常常直接基于问题的描述和所涉及的概念定义。力是指计算机计算的能力选择排序与冒泡排序选择排序(每从第 i 个元素开始扫描,扫描一次找到第 i 小/大的元素,和第 i 个元素进行交换)