一.什么是数据结构与算法分析?
其分为两个部分:
1.数据结构:是研究大量数据的方法,为数据提供有效模型的支撑;
2.算法分析:是对运行时间的评估。
二.为什么要算法分析?
1.时间复杂度
随着计算机的发展,大数据量变得很普及,而我们不希望看到某个程序在计算一个大数据量花费的时间是很久(如果非给这份时间加个期限的话,我希望是一万年);那么计算出来的结果对于我们有限的生命来说是没有任何意义的,而这个对于时间的考量也就使我们说的算法的时间复杂度。
2.空间复杂度
我们的计算机是有限的资源,我们不期望出现计算一个程序的时候,要分配计算机整个的内存空间给该程序,而使得我们的计算机同时几乎不能做其他任何事情,或者崩溃。
而这个内存上的考量也就是我们说的空间复杂度。
3.相对原则
世界上的任何事物,都有轻重之分,好坏之分,对错之分;也就是说两个事物放在一起总是有分别的。那么我们在研究学习当中,要适当的选择优先级别,而我们今后所讲到的算法效率,一般优先考虑时间复杂度问题;而有些算法甚至用空间换时间。
综上,要使得一个程序变得高效可行,我们就要学习算法分析。
三.什么影响一个程序的效率?
影响一个程序的效率的因素有很多:
1.不同的计算机设备,他们的计算能力是不同的(一个巨型计算机与我们个人的pc机,简直有天壤之别)
2.不同的编译方式也影响到的程序的效率;
3.等等其他因素
4.算法的时间复杂度
前面三条是一些不可确定的问题,或者特殊情况,在研究当中我们应尽量抛开不可确定的以及特殊的干扰,抽象出一般的情况,所以在考究一个算法好坏的时候,我们只根据时间复杂度进行比较。
四.算法的选择
在解决某一问题时,会同时出现多种实现方式,也就是多种算法,我们要如何进行选择,以及在那种情况下用那个算法,也就是说那个算法的时间复杂度最优。
1.任何情况下,时间复杂度,都可以表示为一个时间T(n)的函数
2.我们在数学中知道任何两个函数f1(n),f2(n)比较有个作用域的问题,也就是说在某个作用域时f1(n)>f2(n),在某些作用域f2(n)>f1(n),也就是说当你输入数据规模是n时候,当n的作用域在f1(n)>f2(n)的时候,我们选择时间复杂度为f2(n)的算法;反之依然。
3.几种基本初等函数草图分析
举例:当一个算法时间复杂度f1(n) = log2N,与另一个算法时间复杂度未f2(n) =N进行比较时,由上图显然,当N>1,f1(n)总比f2(n)费时间,也就是说f2(n)的算法更优。
五.一些数学模式的引入
定义一:如果存在正整数c和n, 当 N >= n 时 T(N) <= cf(N) 记为 T(N)=O(f(n));
定义二:如果存在正整数c和n, 当N >= n 时 T(N)>=cf(N)记为T(N)= Ω(f(n));
定义三:当且仅当T(N)=O(f(n))且T(N)= Ω(f(n))时记为T(N) = Θ(f(n));
定义四:当且仅当T(N)=O(f(n))且T(N) != Θ(f(n))时记为T(N)=o(f(n));
上面的四个定义,其意义是在函数的关系上建立一种级别的关系,其实这个概念有点类似于高等数学里的无穷小的比较概念:
定义一类似于同阶无穷小;
定义二类似于高阶无穷小;
定义三类似于等级无穷小;
定义四类似于定义一的子集;
上面四段为仅为模式类似关系。
我们进一步分析这个级别关系是如何产生的,有两个步骤
1.限定算法优先区域即N>=n,即在数据当量时,算法选择的条件;
2.T(N)与cf(N)的相对增长率比较;
为什么要用相对增长率,而不用T(N)与f(N)直接比较大小,下面例子详解下
在N较小时100N比N^2(N*N)大,但N^2以更快的速度增长,因此N^2最终将更大。在N=100时为转折点。第一个定义是说,在增长率不同的情况下,最后总是存在某个点n,从它之后cf(N)至少与T(N)一样大,忽略常数因子,即f(N)至少与T(N)一样大。定义二T(N)的增长率小于f(n),其他同理。
注1:当我们说T(N)=O(f(n))时,我们是在保证函数T(N)是在以不快于f(N)的速度增长;因此f(N)是T(N)的一个上界。同时,T(N)= Ω(f(n))意味着f(N)是T(N)的一个下界。
注2:当我们说T(N)=O(f(n))时,此间的=号并非意味着它们之间的相等,其隐含着小于等于关系,=号的作用类似于指代关系。
法则一:
如果T1(n) = O(f(n)) 且 T2(n) = O(g(n))
(a) T1(n) + T2(n) = max (O(f(n)), O(g(n))),
(b) T1(n) * T2(n) = O(f(n) * g(n)),
法则二:
如果T(N)是一个k次多项式,则
T(x) = Θ(xn) ;
忽略低阶项,以及常数项。
法则三:
对于任意常数k,logk n = O(n) 。其告诉我们对数的增长率是非常的慢,至少小于等于线性N的增长率。
证明?对logk n求导于N的导数比较。
六数学概念应用与算法分析
算法所花费的时间问题,我们经常会涉及两个函数Tavg(n) 和Tworst(n) ,一般来说,算法的时间复杂度由Tworst(n) 来度量。原因如下:
1.最坏的运行时间对应所有的运行情况,包括最差的情况,而平均时间不能;
2.最坏的运行时间包含一个上界的关系,我们可以借助 T(N)=O(f(n))进行分析
3.平均的运行时间设计复杂的数学关系,有些数学只是还未能解答出来,并不能包含最坏的情况;
4.我们所说的时间复杂度评估算法,为事前分析,是个近似相等的过程,不需要精确关系,他们之间是量级关系。
七算法分析的总结
1.通过事前分析,抛弃常量,抛弃低阶,计算O(g(n))的运行时间,即O(g(n))就是该算法的时间复杂度。
2.算法分析能尽早的除去那些不好的算法思想,赢在起跑线。
八简单的例子
求i^3的和,i = 1->n;
unsigned int
sum( int n )
{
unsigned int i, partial_sum;
-
/*1*/ partial_sum = 0;
-
/*2*/ for( i=1; i<=n; i++ )
-
/*3*/ partial_sum += i*i*i;
-
/*4*/ return( partial_sum );
}
分析:
变量声明不计时间,
第一行与第四行各占1个时间,
第三行每次执行占4个时间(两次相乘,一次相加,一次赋值),而执行N次占4N时间,
第二行,i初始化占1时间,i范围比较占N+1个时间,自增运算占N个时间,共2N+2个时间;
我们忽略调用指向函数入口指针运算以及带回返回值运算,总计6N+4个时间;
忽略常数项,此算法的时间复杂度为O(N)。
我们知道T(N)=O(f(n))是一个近似的过程,而我们每次如上诉方法计算,是件非常愚蠢的事情。
相第一行与第四行的相对于for循环的分析可以直接忽略;
第三行我们每次执行的时间复杂度为O(1),没必要计算他是1,2,3,4;由T1(n) * T2(n) = O(f(n) * g(n)),此方法是分步(排列组合知识可知,分步是乘法原则,分类为加法原则),则执行N次的时间复杂度为O(N)*O(1)=O(N);
第二行与第三行关系为加法关系,由T1(n) + T2(n) = max (O(f(n)), O(g(n))),最终时间复杂度为O(N)。
九一般法则:
法则一:For循环:
一次for循环的运行时间至多为该for循环内语句运行时间乘以执行次数N;
法则二:嵌套For循环:
由里向外分析这些循环。在一组嵌套循环内部的一条语句总的运行时间为该语句的运行时间乘以改组所有for循环容量的乘积。
下面时间复杂度为O(n2)
for( i=0; i<n; i++ )
for( j=0; j<n; j++ )
k++;
法则三:顺序语句
将各个语句运行时间求和即可T1(n) + T2(n) = max (O(f(n)), O(g(n))),
下面程序先去O(N)再用去O(n2) ,则时间复杂度为O(n2)。
for( i=0; i<n; i++)a[i] = 0;
for( i=0; i<n; i++ )
for( j=0; j<n; j++ )
a[i] += a[j] + i + j;
法则四:IF/ELSE语句
if(condition)
s1
else
s2;
其时间复杂度为max (O(s1), O(s2));即最大者。