1.1 什么是数据结构
clock():捕捉从程序开始运行到clock()被调用时所耗费的时间。这个时间单位是clock tick,即“时钟打点”。
常数:CLK_TCK:机器时钟每秒所走的时钟打点数。
常用模板:
#include<stdio.h>
#include<time.h>
clock_t start,stop;
// clock_t是clock()函数返回的变量类型
double duration;
// 记录被测函数运行时间,以秒为单位
int main()
{ // 不在测试范围内的准备工作写在clock()调用之前
start = clock(); //开始计时
MyFuction(); //把被测函数加在这里
stop = clock(); //停止计时
duration = ((double)(stop - start))/CLK_TCK;
// 计算运行时间
// 其他不在测试范围的处理写在后面,例如输出duration的值
return 0;
}
若要让程序重复运行充分多次,使得测出的总的时钟打点间隔充分长,最后计算被测函数平均每次运行的时间即可!
#define MAXK 1e7 // 被测函数最大重复调用次数
int main()
{ ......
start = clock();
for(i=0;i<MAXK;i++) // 重复调用函数以获得充分多的时钟打点数
f1(maxn-1,a,1,1);
stop = clock();
duration = ((double)(stop - start))/CLK_TCK/MAXK; // 计算函数单次运行的时间
......
return 0;
}
什么是数据结构?
- 数据对象在计算机中的组织方式:逻辑结构、物理存储结构;
- 数据对象必定与一系列加在其上的操作相关联;
- 完成这些操作所用的方法就是算法。
抽象数据类型(Abstract Data Type)
- 数据类型
- 数据对象集
- 数据集合相关联的操作集
- 抽象:描述数据类型的方法不依赖于具体实现
- 与存放数据的机器无关
- 与数据存储的物理结构无关
- 与实现操作的算法和编程语言均无关
1.2 什么是算法
算法(Algorithm)
- 一个有限指令集
- 接受一些输入(有些情况下不需要输入)
- 产生输出
- 一定在有限步骤之后终止
- 每一条指令必须:
- 有充分明确的目标,不可以有歧义
- 计算机能处理的范围之内
- 描述应不依赖于任何一种计算机语言以及具体的实现手段
好算法
- 空间复杂度 S ( n ) S(n) S(n)——根据算法写成的程序在执行时占用存储单元的长度。这个长度往往与输入数据的规模有关。空间复杂度过高的算法可能导致使用的内存超限,造成程序非正常中断。
- 时间复杂度 T ( n ) T(n) T(n)——根据算法写成的程序在执行时耗费时间的长度。这个长度往往也与输入数据的规模有关。时间复杂度过高的低效算法可能导致我们在有生之年都等不到运行结果。
- 在分析一般算法的效率时,我们经常关注下面两种复杂度
- 最坏情况复杂度 T w o r s t ( n ) T_{worst}(n) Tworst(n)
- 平均复杂度 T a v g ( n ) T_{avg}(n) Tavg(n)
- T a v g ( n ) ≤ T w o r s t ( n ) T_{avg}(n)≤T_{worst}(n) Tavg(n)≤Tworst(n)
复杂度的渐进表示法
- T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n))表示存在常数 C > 0 , n 0 > 0 C>0,n_0>0 C>0,n0>0使得当 n ≥ n 0 n≥n_0 n≥n0时有 T ( n ) ≤ C ⋅ f ( n ) T(n)≤C·f(n) T(n)≤C⋅f(n)
- T ( n ) = Ω ( g ( n ) ) T(n)=\Omega (g(n)) T(n)=Ω(g(n))表示存在常数 C > 0 , n 0 > 0 C>0,n_0>0 C>0,n0>0使得当 n ≥ n 0 n≥n_0 n≥n0时有 T ( n ) ≤ C ⋅ g ( n ) T(n)≤C·g(n) T(n)≤C⋅g(n)
- T ( n ) = Θ ( h ( n ) ) T(n)=\Theta (h(n)) T(n)=Θ(h(n))表示同时有 T ( n ) = O ( h ( n ) ) T(n)=O(h(n)) T(n)=O(h(n))和 T ( n ) = Ω ( h ( n ) ) T(n)=\Omega (h(n)) T(n)=Ω(h(n))
复杂度分析小窍门
- 若两段算法分别由复杂度
T
1
(
n
)
=
O
(
f
1
(
n
)
)
T_1(n)=O(f_1(n))
T1(n)=O(f1(n))和
T
2
(
n
)
=
Ω
(
f
2
(
n
)
)
T_2(n)=\Omega (f_2(n))
T2(n)=Ω(f2(n))
- T 1 ( n ) + T 2 ( n ) = m a x ( O ( f 1 ( n ) ) , O ( f 2 ( n ) ) ) T_1(n)+T_2(n)=max(O(f_1(n)),O(f_2(n))) T1(n)+T2(n)=max(O(f1(n)),O(f2(n)))
- T 1 ( n ) × T 2 ( n ) = O ( f 1 ( n ) × f 2 ( n ) ) T_1(n)×T_2(n)=O(f_1(n)×f_2(n)) T1(n)×T2(n)=O(f1(n)×f2(n))
- 若 T ( n ) T(n) T(n)是关于 n n n的 k k k阶多项式,那么 T ( n ) = Θ ( n k ) T(n)=\Theta(n^k) T(n)=Θ(nk)
- 一个 f o r for for循环的时间复杂度等于循环次数乘以循环体代码的复杂度
- i f − e l s e if-else if−else结构的复杂度取决于 i f if if的条件判断复杂度和两个分枝部分的复杂度,总体复杂度取三者中最大。
1.3 应用实例:最大子列和问题
问题描述:给定
n
n
n个整数的序列
a
1
,
a
2
,
.
.
.
,
a
n
{a_1,a_2,...,a_n}
a1,a2,...,an,求函数
f
(
i
,
j
)
=
m
a
x
{
0
,
∑
k
=
i
j
a
k
}
f(i,j)=max\left \{0,\sum_{k=i}^{j}a_k\right \}
f(i,j)=max{0,∑k=ijak}的最大值。
算法:分而治之。(“分治法”)
基本思路是将原问题拆分成若干小型问题,分别解决后再将结果合而治之,用递归实现非常方便。
在本题中,我们可以把问题理解为:如果我们把原始序列一分为二,那么最大子列或者在左半边、或者在右半边、或者是横跨中分线的一段。分治法的概要描述为:
第一步:将序列从中分为左右两个子序列;
第二步:递归求得两子列的最大和
S
左
S_左
S左和
S
右
S_右
S右;
第三步:从中分点分头向左、右两边扫描,找出跨过分界线的最大子列和
S
中
S_中
S中;
第四步:
S
m
a
x
=
m
a
x
{
S
左
,
S
中
,
S
右
}
S_{max}=max\left \{S_左,S_中,S_右\right \}
Smax=max{S左,S中,S右}
代码给出了这个算法的实现。
int Max3(int A, int B, int C)
{ // 返回3个整数中的最大值
return A > B ? A > C ? A : C : B > C ? B : C; // ???
}
int DivideAndConquer( int List[], int left, int right )
{ // 分治法求List[left]到List[right]的最大子列和
int MaxLeftSum, MaxRightSum; // 存放左右子问题的解
int MaxLeftBorderSum, MaxRightBorderSum; // 存放跨分界线的结果
int LeftBorderSum, RightBorderSum;
int center, i;
if( left == right ) { // 递归的终止条件,子列只有1个数字
if( List[left] > 0) return List[left];
else return 0;
}
// 下面是“分”的过程
center = (left + right ) / 2; // 找到中分点
// 递归求得两边子列的最大和
MaxLeftSum = DivideAndConquer( List, left, center );
MaxRightSum = DivideAndConquer( List, center+1, right );
// 下面求跨分界线的最大子列和
MaxLeftBorderSum = 0; LeftBorderSum = 0;
for( i =center; i>=left; i--){ // 从中线向左扫描
LeftBorderSum +=List[i];
if( LeftBorderSum > MaxLeftBorderSum )
MaxLeftBorderSum = LeftBorderSum;
} // 左边扫描结束
MaxRightBorderSum = 0; RightBorderSum = 0;
for( i =center; i<=Right; i--){ // 从中线向右扫描
RightBorderSum +=List[i];
if( RightBorderSum > MaxRightBorderSum )
MaxRightBorderSum = RightBorderSum;
} // 右边扫描结束
// 下面返回“治”的结果
return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum );
}
int MaxSubseqSum3( int List[], int N )
{ // 保持与前2种算法相同的函数接口
return DivideAndConquer( List, 0, N-1 );
}