数据结构2_算法和算法评价
1. 算法:
算法:对特定问题的求解步骤的一种描述
算法的特性:
- 有穷性:算法是由若干条指令组成的有穷序列,总是在执行若干次后结束,不可能永不停止;
- 确定性:每条语句有确定的含义,无歧义
- 可行性:算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现
- 输入和输出:有零个输入或多个输入,一个或多个输出
2. 优秀算法的标准:
- 正确性:算法能够满足具体问题的需求,程序运行正常,无语法错误
- 易读性:遵循标识符的命名规则(驼峰规则等),简洁易读易懂,在适当的位置有正确易懂的注释;
- 健壮性:算法应该对非法数据有较好的反应和处理,例如:输入应该是性别,但是输入了12,系统应该提示用户输入错误;
- 高效-衡量时间复杂度:算法运行效率高,即算法运行所消耗的时间短。现代计算机一秒运行数亿条基本操作,因此不能用秒等特定时间的度量单位来衡量。由于在相同配置的计算机运行依次基本操作的时间是一定的,我们可以用算法基本运算的执行次数作为时间复杂度的度量标准;
- 低存储-衡量空间复杂度:算法所占存储空间尽可能低;
前三条实现较为简单,而且真正影响计算机执行算法的效率是后两条,后两条也作为算法效率的度量
2.1 算法度量的标准:
2.1.1 时间复杂度:
一个语言的频度是指该语句在算法中被重复执行的次数。算法中所有语句的频度合记作T(n),他是该算法问题规模n的函数,时间复杂度主要分析T(n)的数量级,算法中基本运算(最深层循环内的语句)的频度与T(n)同数量级,因此通常采用算法中**基本运算的频度f(n)**来分析算法的时间复杂度。因此算法的时间复杂度记为:
T
(
n
)
=
O
(
f
(
n
)
)
—
—
O
的
含
义
是
T
(
n
)
的
数
量
级
T(n) = O(f(n)) ——O的含义是T(n)的数量级
T(n)=O(f(n))——O的含义是T(n)的数量级
其严格的数学定义是,若T(n)和f(n)是定义在正整数集合上的两个函数,则存在正常数C和n0,使得当n>n0时,都满足0<=T(n)<=Cf(n)
先来看一个简单的冒泡排序:
void BubbleSort1(int *arr,int sz){
int i = 0; //运行1次
int j = 0; //运行1次
assert(arr); //运行1次
for(i=0;i<sz-1;i++){ //运行sz+1次 最后一次判断条件不成立结束
for(j=0;j<sz-i-1;j++){ //运行sz*(sz-1)/2+1次
if(arr[j]>arr[j+1]){ //运行sz*(sz-1)/2+1次
int tmp = arr[j]; //运行sz*(sz-1)/2次,下同
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
把算法所有的语句运算次数加起来
1
+
1
+
1
+
s
z
+
1
+
s
z
∗
(
s
z
−
1
)
+
1
+
1
+
s
z
∗
(
s
z
−
1
)
/
2
∗
3
1+1+1+sz+1+sz*(sz-1)+1+1+sz*(sz-1)/2*3
1+1+1+sz+1+sz∗(sz−1)+1+1+sz∗(sz−1)/2∗3
令sz = n,用一个函数T(n)表达:
T
(
n
)
=
5
/
3
n
2
−
2
/
3
n
+
6
T(n) =5/3n^2-2/3n+6
T(n)=5/3n2−2/3n+6
当n足够大时,算法的运行时间主要取决于二次项,后面可以忽略,令f(n) = n^2,当n->无穷大时,T(n)与cf(n)近似相等,因此我们可以用O(f(n))来表示时间复杂度的渐近上界,通常用这种表示法衡量算法的时间复杂度,则冒泡排序的时间复杂度可以表示为O(f(n)) = n^2;其实可以观察到上述算法判断条件内的语句不是一定会执行的,实际上我们计算的是当前算法最坏情况下的的时间复杂度。
在下面这个算法中:
" A[5] = {1, 2, 3, 4, 5}
k 待输入
"
i = 0;
while(i < n){
if(A[i] == k) return i;
i++;
}
循环内的语句不仅与问题的规模n有关,还与待输入的变量k和A[]内元素取值有关,
最坏时间复杂度:A[5]中没有k,循环内语句频度f(n)为n
最好时间复杂度:若A的第一个元素就是k,那语句的频度f(n)就是常数0
平均时间复杂度:是指在所有可能输入实例在等概率出现的情况下,算法的期望运行时间,上述程序的平均执行时间是n+1/2
在实际计算中,我们选取最坏时间复杂度作为衡量标准,并且只考虑对算法运行时间贡献最大的语句,而且往往处于循环内的语句运行次数最多,所以循环次数是考察时间复杂度的重要因素。
在分析一个程序的时间复杂性时,有以下两条规则:
a.加法规则:
T
(
n
)
=
T
1
(
n
)
+
T
2
(
n
)
=
O
(
f
(
n
)
)
+
O
(
g
(
n
)
)
=
O
(
m
a
x
(
f
(
n
)
,
g
(
n
)
)
)
T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))
T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))
b.乘法规则:
T
(
n
)
=
T
1
(
n
)
∗
T
2
(
n
)
=
O
(
f
(
n
)
)
∗
O
(
g
(
n
)
)
=
O
(
f
(
n
)
∗
g
(
n
)
)
T(n)=T1(n)*T2(n)=O(f(n))*O(g(n))=O(f(n)*g(n))
T(n)=T1(n)∗T2(n)=O(f(n))∗O(g(n))=O(f(n)∗g(n))
常见的渐近时间复杂度:
O
(
1
)
<
O
(
l
o
g
2
(
n
)
)
<
O
(
n
)
<
O
(
n
l
o
g
2
(
n
)
)
<
O
(
n
2
)
<
O
(
n
3
)
<
O
(
2
n
)
<
O
(
n
!
)
<
O
(
n
n
)
O(1) < O(log2(n))<O(n)<O(nlog2(n))<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)
O(1)<O(log2(n))<O(n)<O(nlog2(n))<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
2.1.2 空间复杂度:
算法占用的空间大小,一般将算法的辅助空间作为衡量算法空间复杂度的标准,记为:
S
(
n
)
=
O
(
g
(
n
)
)
S(n)=O(g(n))
S(n)=O(g(n))
输入输出占用空间时必须的,算法本身占用空间压缩量也很小,所以我们只计算辅助变量占用的空间,如swap()函数会占用一个单位的辅助空间才存放交换数,递归程序也会占用额外的栈空间来实现递归调用和回归;
算法原地工作是指算法所需的辅助空间为常量O(1);
3.习题:
3.1 n是非负整数,求时间复杂度:log2(n)
x=2;
while(x<n/2)
x=2*x;
3.2 已知长度分别为m和n的两个升序链表,若合并为长度为m+n的一个降序列表,最坏情况下的时间复杂度为(Omax(m,n)
3.3 求时间复杂度:O(n^(1/2))
int i = 0, sum = 0;
while(sum < n) sum+ =++i;
3.4 以下算法中m++语句的执行次数是(n(n+1)):
int m=0,i,j;
for(i = 1; i <= n; i++){
for(j = 1; j <= 2 * i; j++){
m++;
}
}
3.5 下列说法错误的是
- 算法原地工作的含义是指不需要额外的辅助空间;
- 在相同规模n下,复杂度为O(n)的算法在时间上总优于复杂度为O(2^n)的算法;
- 所谓的时间复杂度,指的是在最坏情况下估算算法执行时间的一个上界;
- 同一个算法,实现语言的级别越高,执行效率越低;