1 基本概念
1.1 什么是数据结构
例1 如何在书架上摆放图书?
例2 传入一个正整数N,顺序打印从1到N的全部正整数:
//循环实现
void printN(int N){
int i;
for(i=1;i<=N;i++)
printf("%d\n",i);
}
//递归实现
void PrintN(int N){
if(N){
PrintN(N-1);
printf("%d\n",N);
}
}
循环实现能够成功打印,但是递归不能
递归程序占用空间大
解决问题方法的效率,跟空间的利用效率有关
第2个函数标准
使用clock()来测试程序运行时间
但是,有的程序跑完所用时间会很小,约等于0,此时,可以重复
执行程序
什么是数据结构
逻辑结构:线性结构(一对一)、树(一对多)、图(多对多)
物理存储结构:在计算机中是用数组来存还是链表来存
抽象数据类型
对于a是float还是double都不管,ElementType就是a的类型
当将矩阵的类型由int改为double后,虽然可以使用replace将int替换为double,但是可能有的不需要替换,要小心,ElementType的好处就在这体现出来了
1.2 什么是算法
算法不像操作系统,os只要不关机永远在与运行
什么是好算法
时间复杂度T(n)
根据算法写成的程序在执行时耗费时间的长度
。这个长度往往也与输入数据的规模有关。过高会导致等不到结果。
空间复杂度S(n)
根据算法写成的程序在执行时占用存储单元的长度
。这个长度往往与输入数据的规模有关。过高会导致使用的内存超限,造成程序非正常中断。
S(N)=C*N
100000、99999、99998。。。不是指它占这么大的内存,而是指printN(100000)、printN(99999)、printN(99998)。。。在相同里存的一块内容,应该是printN(n)这个函数所有的当前的状态。
递归每次调用时都会占用一定的内存空间来存储信息,而for循环只用了临时变量和for循环,没有设计任何程序调用的问题,所以他占用的空间是个常量。
机器对于加减法运算的时间比乘除法运算的时间要少的多,所以加减法运算时间可以忽略不计。
对于上面第一个程序,每次for循环里,pow(x,i)执行i-1次乘法,再加上前面的a[i]*,一共是i次乘法
复杂度的渐进表示法
分析算法的复杂度,没必要把一步步的数,把哪个操作做了多少次都数明白了。
关心的只是,随着要处理的数据的规模的增大,它的复杂度增长的性质会怎么样。比如说,在比较前面2个算法时,我们只要知道,当n很大时,第一个算法的时间复杂度,基本上就是n平方在起主要作用,第二个算法是n在起主要作用。当n充分大时,第一个算法肯定比第二个算法慢。
只要粗略的知道增长趋势就完了,于是有了渐进表示法
函数的上界和下界不一定只有1个,不管是上界还是下界,都尽可能地跟它的真实情况贴的越近越好,所以取得是我们能找到的最小的上界和最大的下界
log n不管以什么为底他都只是差一个常数倍而已,它的底无关紧要
当看到一个算法是n2 时,下意识地看能不能将其降为nlogn
logn非常小
1.3 应用例子:最大子序和问题
例子:
算法1:暴力法。三层嵌套for循环,可以证明是1个N3 * 1个常数
由上图可知,当知道i到j之和时,再加1个数就可以了,所以k那个循环可以省略
要尽可能的把n2 改进为nlogN
算法3:分治法,把1个比较大的复杂的问题切分成小的块,然后分头去解决它们,最后再把结果合并起来
把它分成2部分,左半部分递归查找最大的,右半部分递归查找最大的,在查找中间部分最大的
计算时间复杂度:想象,当解决的整个问题里有N个数字的时候,如果复杂度记作T(N),那么可得半边数字的复杂度为T(N/2),因为规模减半了。
红色的中间部分:因为要从中间扫描左边然后扫描右边,每一个元素都被扫描了1次,所以复杂度应该是N的一个常数倍
正确性不是很明显(不好理解)