算法是特定问题求解步骤的描述ios
在计算机中表现为指令的有限序列算法
算法是独立存在的一种解决问题的方法和思想。数组
对于算法而言,语言并不重要,重要的是思想。缓存
数据结构只是静态的描述了数据元素之间的关系数据结构
高效的程序须要在数据结构的基础上设计和选择算法函数
程序=数据结构+算法 学习
总结:测试
算法是为了解决实际问题而设计的spa
数据结构是算法须要处理的问题载体设计
数据结构与算法相辅相成
输入
算法具备0个或多个输入
输出
算法至少有1个或多个输出
有穷性
算法在有限的步骤以后会自动结束而不会无限循环
肯定性
算法中的每一步都有肯定的含义,不会出现二义性
可行性
算法的每一步都是可行的
一、过后统计法
比较不一样算法对同一组输入数据的运行处理时间
缺陷
为了得到不一样算法的运行时间必须编写相应程序
运行时间严重依赖硬件以及运行时的环境因素
算法的测试数据的选取至关困难
过后统计法虽然直观,可是实施困难且缺陷多。
二、事前分析估算
依据统计的方法对算法效率进行估算
影响算法效率的主要因素
算法采用的策略和方法
问题的输入规模
编译器所产生的代码
计算机执行速度
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> //算法最终编译成具体的计算机指令 //每个指令,在具体的计算机上运行速度固定 //经过具体的n的步骤,就能够推导出算法的复杂度 long sum1(int n) { long ret = 0; int* array = (int*)malloc(n * sizeof(int)); int i = 0; for(i=0; i<n; i++) { array[i] = i + 1; } for(i=0; i<n; i++) { ret += array[i]; } free(array); return ret; } long sum2(int n) { long ret = 0; int i = 0; for(i=1; i<=n; i++) { ret += i; } return ret; } long sum3(int n) { long ret = 0; if( n > 0 ) { ret = (1 + n) * n / 2; } return ret; } void mytest() { printf("%d\n", sum1(100)); printf("%d\n", sum2(100)); printf("%d\n", sum3(100)); return; } int main() { mytest(); system("pause"); return 0; }
int func(int a[], int len) { int i = 0; int j = 0; int s = 0; for(i=0; i<len; i++) n { for(j=0; j<len; j++) n { s += i*j; //n*n } } return s; } //n*n
注意1:判断一个算法的效率时,每每只须要关注操做数量的最高次项,其它次要项和常数项能够忽略。
注意2:在没有特殊说明时,咱们所分析的算法的时间复杂度都是指最坏时间复杂度。
二、大O表示法
算法效率严重依赖于操做(Operation)数量
在判断时首先关注操做数量的最高次项
操做数量的估算能够做为时间复杂度的估算
O(5) = O(1)
O(2n + 1) = O(2n) = O(n)
O(n2+ n + 1) = O(n2)
O(3n3+1) = O(3n3) = O(n3)
常见时间复杂度
关系
三、算法的空间复杂度
算法的空间复杂度经过计算算法的存储空间实现
S(n) = O(f(n))
其中,n为问题规模,f(n))为在问题规模为n时所占用存储空间的函数
大O表示法一样适用于算法的空间复杂度
当算法执行时所须要的空间是常数时,空间复杂度为O(1)
空间与时间的策略
多数状况下,算法执行时所用的时间更使人关注
若是有必要,能够经过增长空间复杂度来下降时间复杂度
同理,也能够经过增长时间复杂度来下降空间复杂度
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> /* 问题: 在一个由天然数1-1000中某些数字所组成的数组中,每一个数字可能出现零次或者屡次。 设计一个算法,找出出现次数最多的数字。 */ // 方法1: 排序,而后找出出现次数最多的数字 // 排序,而后找出出现次数最多的数字 // 方法2: 把每一个数字出现的次数的中间结果,缓存下来;在缓存的结果中求最大值 void search(int a[], int len) { int sp[1000] = {0}; int i = 0; int max = 0; for (i = 0; i < len; i++) { int index = a[i] - 1; sp[index]++; } for (i = 0; i < 1000; i++) { if (max < sp[i]) { max = sp[i]; } } for (i = 0; i < 1000; i++) { if (max == sp[i]) { printf("%d\n", i + 1); } } } void mytest() { int array[] = {1, 1, 3, 4, 5, 6, 6, 6, 2, 3}; search(array, sizeof(array)/sizeof(array[0])); return; } int main() { mytest(); system("pause"); return 0; }
1 | 大部分程序的大部分指令之执行一次,或者最多几回。若是一个程序的全部指令都具备这样的性质,咱们说这个程序的执行时间是常数。 |
logN | 若是一个程序的运行时间是对数级的,则随着N的增大程序会渐渐慢下来,若是一个程序将一个大的问题分解成一系列更小的问题,每一步都将问题的规 模缩减成几分之一 ,通常就会出现这样的运行时间函数。在咱们所关心的范围内,能够认为运行时间小于一个大的常数。对数的基数会影响这个常数,但改变不会太 大:当N=1000时,若是基数是10,logN等于3;若是基数是2,logN约等于10.当N=1 00 000,logN只是前值的两倍。当N时原来的两倍,logN只增加了一个常数因子:仅当从N增加到N平方时,logN才会增加到原来的两倍。 |
N | 若是程序的运行时间的线性的,极可能是这样的状况:对每一个输入的元素都作了少许的处理。当N=1 000 000时,运行时间大概也就是这个数值;当N增加到原来的两倍时,运行时间大概也增加到原来的两倍。若是一个算法必须处理N个输入(或者产生N个输出), 那么这种状况是最优的。 |
NlogN | 若是某个算法将问题分解成更小的子问题,独立地解决各个子问题,最后将结果综合起来 ,运行时间通常就是NlogN。咱们找不到一个更好的形容, 就暂且将这样的算法运行时间叫作NlogN。当N=1 000 000时,NlogN大约是20 000 000。当N增加到原来的两倍,运行时间超过原来的两倍,但超过不是太多。 |
N平方 |
若是一个算法的运行时间是二次的(quadratic),那么它通常只能用于一些规模较小的问题。这样的运行时间一般存在于须要处理每一对输入 数据项的算法(在程序中极可能表现为一个嵌套循环)中,当N=1000时,运行时间是1 000 000;若是N增加到原来的两倍,则运行时间将增加到原来的四倍。 |
N三次方 | 相似的,若是一个算法须要处理输入数据想的三元组(极可能表现为三重嵌套循环),其运行时间通常就是三次的,只能用于一些规模较小的问题。当N=100时,运行时间就是1 000 000;若是N增加到原来的两倍,运行时间将会增加到原来的八倍。 |
2的N次方 | 若是一个算法的运行时间是指数级的(exponential),通常它很难在实践中使用,即便这样的算法一般是对问题的直接求解。当N=20时,运行时间是1 000 000;若是增加到原来的两倍时,运行时间将是原时间的平方! |
log log N 能够看做是一个常数:即便N不少,两次去对数以后也会变得很小
之前光看了n多排序算法,知道仅经过比较的排序算法一共两种复杂度
O(N2)或O(N*lgN),
因为高数学的很差,以前一看到后者就放弃了思考,没有真正研究为何会有个lgN,
这两天工做不是很忙,看了一下基础知识,有了必定的认识,算是初步搞清楚了缘由.
写在这里算是一个记录,若是有问题也请你们指正.
说到N*lgN的算法大体上有几种:堆排序,归并排序,快速排序.
因为学习数据结构的时候老师讲过快速排序,(其实各类都讲过,我只记住了这种)我如今仍是很是有印象的,
这几种排序实际都有一个共同点,这个共同点让他们有了lgN的特性.
都是使用了分治算法,把大集合经过分治,造成小集合,小集合的排序再次递归更小集合,直到1个(还多是2个或3个)元素为止.
这样对于整个集合来说,每次递归都是处理2个元素数量是1/2当前元素数量的新集合,
f(x) = f(x/2) + a (有限极小数)
这个特色在堆排序和归并排序中尤其突出,他们是绝对的遵循2分法的.而快速排序结果是随机的,若是点背,可能出现O(N*N)的可能性
下面用堆排序为例说明一下NlgN:
为了让更多人明白,我简单把堆排序说一下:
堆排序就是把原来输入的值串,当成一棵彻底2叉树,每次找最大值的时候,都是把数的左右子节点比较,把大于本身的最大的一个跟本身互换,找到一个后,从新找剩下的n-1个,一直到最终找完全部节点.
因为递归使用的是深度优先,每次都会从最底层往上找起,每次找的次数假设是F(x),则其须要找两个子树F(x/2)而且等两个子树处理完后,比较2次(子树的根比较一次,跟本身比较一次)若是连移动都算上,是3次操做
值的注意的是,因为最初排列过了之后,找子树的时候只要找那颗被破坏了的子树便可,
另外一颗排过序了的不须要再找了. (这个我本身都感受说的不明白,若是实在不行,你们再去看看相关资料吧)
这样分析下来,找第x个节点的操做次数为: f(x) <= f((x-1)/2) +3 (固然,我理解算成 + 2也行,x-1是把根去掉)
因为当x为2的整次方倍 + 1 的时候,正好是这个数值,当其余的状况 也不大于这个值
因此咱们能够就使用最大值的状况 f(x) = f((x-1)/2) + 3; 为了计算更容易
直接 f(x) = f(x/2) + 3
f(x/2) = f(x/4) + 3
......
f(x/2m-1) = f(x/2m) + 3 (2m>=x) <= f(1)+3 < 3;
因为一共m个算式 加起来是 f(x) = 3m 而 m = log(2)(x)
f(x)=3log(2)(x);
而计算f(1)+f(2) + ... + f(n)的时候,咱们把他分城 m段(m= log(2)(n)) (分别为1,2,4,8,...2m-1)个元素(固然最后一端可能没有那么多)
求他们的和的话就是
2m-1(m-1) + 2m-2(m-2) + ....<2m-1m + 2m-2m + ... < 2mm 而m = log(2)(N)
2mm = 2log(2)(n)*log(2)(n) = N * log(2)(N)
得证 哈哈