数据结构~算法

算法:

算法是解决特定问题求解步骤的描述,在计算中表现为指令的有限序列,并且每条指令表示一个或多个操作。


数据结构与算法到底有什么关系呢?

如果只谈数据结构,当然是可以的,我们可以在很短的时间就把几种重要的数据结构介绍完。听完后,很可能你没什么感觉,不知道这些数据结构有何用处。但如果我们在把相应的算法也拿来讲一讲,你就会发现,甚至开始感慨:哦,计算机界的前辈们,的确是一些很牛很牛的人,他们使得很多看似很难解决或者没法解决的问题,变得如此美。

 算法的特性

算法有五个基本特性:输入.输出.有穷性.确定性和可行性。

1.输入输出

算法具有零个或多个输入。

2.有穷性

有穷性:是指算法在执行有限的步骤之后,自动结束而不出现无限循环,并且每一个步骤在可接受的时间内完成。

扫描二维码关注公众号,回复: 2056586 查看本文章

3.确定性

确定性:算法的每一步骤都具有确定的含义,不会出现二义性。

4.可行性

可行性:算法的每一步都必须是可行的,也就是说,每一步都能通过有限次数完成。


 算法设计的要求

1.正确性

正确性:算法的正确性是指算法至少应该具有输入.输出和加工处理无歧义性.能正确反映问题的需求.能够得到问题的正确答案。


但算法的“正确”通常在用法上有很大的差别,大体分为以下四个层次:

1.算法程序没有语法错误。

2.算法程序对于合法的输入数据能够产生满足要求的输出结果。

3.算法程序对于非法的输入数据能够得出满足规格说明的输出结果。

4.算法程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果。

2.可读性

可读性:算法设计的另一目的是为了便于阅读.理解和交流。

3.健壮性

健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。

4.时间效率高和存储量低

设计算法应该尽量满足时间效率高和存储量低的需求。

                                         算法效率的度量方法

1.事后统计方法

事后统计方法:这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。

但这种方法显然有很大缺陷的:

* 必须依据算法事先编制好程序,这通常需要花费大量的时间和精力。如果编制出来发现它根本是很糟糕的算法,不是竹篮打水一场空吗?

*时间的比较依赖计算机硬件和软件等环境因素,有时会掩盖算法本身的优劣。要知道,现在的一台四核处理器的计算机,跟当年286·386·486等老爷爷辈的机器相比,在处理算法的运算速度上,是不能相提并论的;而所用的操作系统·编译器·运行框架等软件的不同,也可以影响它们的结果;就算是同一台机器,cpu使用率和内存占用情况不一样,也会造成细微的差异。

*算法的测试数据设计困难,并且程序的运行时间往往还与测试数据的规模有很大的关系,效率高的算法在小的测试数据面前往往得不到体现。

基于事后统计方法有这样那样的缺陷,我们考虑不予采纳。


2.事前分析估算方法

事前分析估算方法在计算机序编制前,依据统计方法对算法进行估算。

经分析,我们发现,一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:

1.算法采用的策略·方法。

2.编译产生的代码质量。

3.问题的输入规模。

4.机器执行指令的速度。

第1条当然是算法好坏的根本,第2条要由软件来支持,第4条要看硬件性能。也就是说,抛开这些与计算机硬件·软件有关的因素,一个程序的运行时间,依赖于算法的好坏和问题的规模输入。所谓问题规模输入是指输入量的多少


在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤。


                                        函数的渐进增长

输入规模n在没有限制的情况下,只要超过一个数值N,这个函数就总是大于另一个函数,我们称函数是渐进增长的。

函数的渐进增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐进快于g(n)。


渐进增长速率与最高次项相乘的常数并不重要。


最高次项的指数大的,函数随着n的增长,结果也会变得增长特别快。


判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。

某个算法,随着n的增大,它会越来越优于另一算法,或者越来越差于另一算法。

                                                  算法时间复杂度

算法时间复杂度定义

在进行算法分析时,语句总的执行次数T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。

这样用大写O()来体现算法复杂度的记法,我们称之为大O记法。

推导大O阶方法

推导大O阶:

1.用常数1取代运行时间中的所有加法常数。

2.在修改后的运行次数函数中,只保留最高阶项。

3.如果最高阶项存在且不是1,则去除与这个项相乘的函数。得到的结果就是大O阶。


常数阶

与问题的大小无关(n的多少),执行时间恒定的算法,我们称之为具有O(1)的时间复杂度,又叫常数阶。

注意:不管这个常数是多少,我们都记做O(1),而不能是O(3)·O(12)等其他任何数字。

对于分支结构而言,无论是真还是假,执行的次数都是恒定的,不会随着n的变大而发生变化,所以单纯的分支结构(不包含在循环结构中),其时间复杂度也是0(1)。


线性阶

我们要分析算法的复杂度,关键就是要分析循环结构的运行状况。

下面这段代码,它的循环的时间复杂度为O(n),因为循环体中的代码要执行n次。

int i;

for(i=0;i<n;i++)

{

/*时间复杂度为O(1)的程序步骤序列*/

}


对数阶

下面这段代码,时间复杂度又是多少呢?

int count=1;

while(count<n)

{

count = count * 2;

/*时间复杂度为O(1)的程序步骤序列*/

}

由于每次 count 乘以 2 之后,就距离 n 更近了一分。也就是说,有多少个2相乘后大于 n,则会退出循环。由 2^x=n 得到  x=log2 n。所以这个循环的时间复杂度为O(logn)


平方阶

下面例子是一个循环嵌套,它的内循环时间复杂度为O( n)。

int i, j;

for(i=0;i<n;i++)

{

for(j=0;j<n;j++)

{

/*时间复杂度为O(1)的程序步骤序列*/

}

}

而对外层循环,不过是内部这个时间复杂度为O(n)的语句,再循环n次。所以这段代码的时间复杂度为O(n^2)。

我们可以得出结论,循环的时间复杂度乘以该循环运行的次数。

                                                                                              常见的时间复杂度

执行次数函数

    阶

非正式术语

12

  O(1)

常数阶

2n+3

O(n)

线性阶

3n^2+2n+1

  O(n^2)

 平方阶

5log2 n+20

  O(logn)

 对数阶

2n+3nlog2 n+19

  O(nlogn)

 nlogn

6n^3+2n^2+3n+4

  O(n^3)

 立方阶

2^n

  O(2^n)

 指数阶

常用的时间复杂度所耗费的时间从小到大依次是:

O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)

                                                                                           最坏情况与平均情况

最坏情况运行时间是一种保证,那就是运行时间将不会再坏了。在应用中,这就是一种最需要的需求,通常,除非特别指定,我们提到的运行时间都是最坏情况的运行时间。

平均运行时间是所有情况中最有意义的,因为它是期望的运行时间。
一般没有特殊说明的情况下,都是指最坏时间复杂度。

                                                                           算法空间复杂度

算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n) = O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。





猜你喜欢

转载自blog.csdn.net/huaijiu123/article/details/77478754