一、算法的官方定义
- 算法(Algorithm)
- 一个有限指令集
- 接受一些输入(有些情况下不需要输入)
- 产生输出
- 一定在有限步骤之后终止
- 每一条指令必须
- 有充分明确的目标,不可以有歧义
- 计算机能处理的范围之内
- 描述应不依赖于任何一种计算机语言以及具体的实现手段
二、例1:选择排序算法的伪码描述
/* 伪代码描述 */
void SelectionSort (int List[], int N)
{ 将N个整数List[0]...List[N-1]进行非递减排序;
从List[i]到List[N-1]中找最小元,并将其位置赋给MinPosition;
将未排序部分的最小元换到有序部分的最后位置;
}
}
/* c语言实现 */
void SelectionSort (int List[], int N)
{ /* 将N个整数List[0]...List[N-1]进行非递减排序 */
for (i=0; i<N; i++){
MinPosition = ScanForMin(List, i, N-1);
/* 从List[i]到List[N-1]中找最小元,并将其位置赋给MinPosition;*/
Swap(List[i], List[MinPosition]);
/* 将未排序部分的最小元换到有序部分的最后位置; */
}
}
# python语言实现
def selection_sort(lt, n):
for i in range(n):
min_position = scan_for_min(lt, i, n-1)
swap(lt[i], lt[min_position])
抽象 ——
List到底是数组还是链表(虽然看上去像数组)?
Swap用函数还是用宏去实现?
三、什么是好的算法
通常通过下面两个指标衡量算法的好坏
3.1 空间复杂度S(n)
根据算法写成的程序在执行时占用存储单元的长度。这个长度往往与输入数据的规模有关。空间复杂度过高的算法可能导致使用的内存超限,造成程序非正常中断。
3.2 时间复杂度T(n)
根据算法写成的程序在执行时耗费时间的长度。这个长度往往也与输入数据的规模有关。时间复杂度过高的低效算法可能导致我们在有生之年都等不到运行结果。
3.3 0101-例2-空间复杂度
/* c语言实现 */
void PrintN (int N)
{if (N){
PrintN(N - 1);
printf("%d
", N);
}
return;
}
# python语言实现
def print_n(n: int):
if n:
print_n(n - 1)
print(n)
首先内存记录PrintN(100000)
的状态,但由于是递归调用,会继续记录PrintN(99999)
的状态,由于继续在递归调用,会继续记录PrintN(99998)
的状态,……因此内存占用超限。
3.4 0101-例3-时间复杂度
3.4.1 方法1
对于上述的多项式,我们可以使用以下代码实现:
/* c语言实现 */
double f(int n, double a[], double x)
{int i;
double p = a[0]
for (i=1; i<=n; i++)
p += (a[i] * pow(x, i)); /* pow会执行i次乘法 */
return p;
}
# python语言实现
def f(n: int, a_list: list, x: float):
p = a_list[0]
for i in range(1, n):
p += (a_list[i] * pow(x, i))
return p
时间复杂度:
3.4.2 方法2
但是上述的方法极其复杂,我们可以对多项式进行如下化简:
/* c语言实现 */
double f(int n, double a[], double x)
{int i;
double p = a[n];
for (i=n; i>0; i--)
p = a[i-1] + x*p; /* 一次乘法 */
return p
}
# python语言实现
def f(n: int, a_list: list, x: float):
p = a_list[n]
for i in range(0,n,-1):
p = a_list[i-1] + x*p
return p
时间复杂度:
综上:在分析一般算法的效率时,我们经常关注下面两种复杂度
- 最坏情况复杂度 (T_{worst}( n ))
- 平均复杂度 (T_{avg}( n ))
由于平均复杂度的计算难度远远大于最坏情况复杂度,因此通常考虑最快情况复杂度,即(T_{worst}(n)geq{T_{avg}(n)})
四、算法复杂度的渐进表示
对于算法的复杂度,没有必要求出一个精确值,只需要粗略的知道算法的增长趋势即可。
- (T(n)=O(f(n)))表示存在常数(C>0,n_0>0)使得当(ngeq{n_0})时有(T(n)leq{C}f(n))
- (T(n)=Omega(g(n)))表示存在常数(C>0,n_0>0)使得当(ngeq{n_0})时有(T(n)geq{C}g(n))
- (T(n)=Theta(h(n)))表示同时有(T(n)=O(h(n)))和(T(n)=Omega(h(n)))
分析算法效率时,我们总是希望找到(O)时最大的上界;找到(Omega)时最小的下界。
下面三张图表示算法复杂度为不同函数时对应的耗时:
综上:一个专业的程序猿,设计了一个(O(N^2))的算法,应该本能的想到是否能把他改进为(O(Nlog_N))的算法。
五、算法复杂度分析小窍门
-
若两段算法分别有复杂度(T_1(n)=O(f_1(n)))和(T_2(n)=O(f_2(n))),则
- (T_1(n)+T_2(n))=max(O(f_1(n)),O(f_2(n))))
- (T_1(n)×T_2(n))=max(O(f_1(n)×f_2(n)))
-
若(T(n))是关于(n)的(k)阶多项式,那么(T(n)=Theta{(n^k)})
-
一个for循环的时间复杂度等于循环次数乘以循环体代码的复杂度
-
if-else结构的复杂度取决于if的条件判断复杂度和两个分支部分的复杂度,总体复杂度取三者中最大