数据结构与算法--算法

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

两种算法的比较

求解一个:
求1+2+3+…+100结果的程序:
第一种算法:

C语言

int i,sum = 0,n = 100;
for(i = 1;i <= n;i++){
	sum = sum + i;
}
printf("%d",sum);

这是最简单的计算机算法之一,他就是一种算法。

高斯算法求和:

sum = 1+2+3+。。。+99+100
sum = 100+99+98+。。。+2+1
2*sum = 101+101+101+。。。+101+101 共100个
所以sum = 5050
用程序实现:

int i,sum = 0,n = 100;
sum = (1+n)*n/2;
printf("%d",sum);

算法定义

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

算法的特性

五个特性:输入、输出、有穷性、确定性和可行性。

输入输出

算法具有零个或多个输入。
**算法至少有一个或多个输出。**算法是一定要输出的。

有穷性

**指算法在执行有限的步骤之后,会自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。**现实生活中经常写出循环的代码,这就是不满足有穷性。

确定性

算法的每一步骤都具有确定的含义,不会出现二义性。
算法在一定条件下,只有一条路径,相同的输入只能由唯一的输出结果。算法的每一个步骤
被精确定义而无歧义。

可行性

算法的每一步都必须是可行的,也就是说,每一步都能够执行有限次数完成。
可行性意味着算法可以转换为程序上机运行,并得到正确的结果。尽管在目前计算机界也存在那种没有实现的极为复杂的算法,不是说理论上不能实现,而是因为过于复杂,我们当前的编程方法、工具和大脑限制了这个工作。

算法设计的要求

正确性

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

算法的“正确”通常在用法上有很大的差别,大体分为四个层次:
1、算法程序没有语法的错误。
2、算法程序对于合法的输入数据能够产生满足要求的输出结果。
3、算法程序对于非法的输入数据能够得出满足规格说明的结果。
4、算法程序对于精心选择的、甚至刁难的测试数据都有满足要求的输出结果。

因此算法的正确性在大部分情况下都不可能程序来证明,而是用数学方法证明的。证明一个复杂的算法在所有层次上都是正确的,代价非常昂贵。所以一般情况下把层次3作为一个算法是否正确的标准。
容易理解也是一个好的算法必须满足的特征。

可读性

算法设计的另一个目的是为了方便阅读、理解和交流。
可读性高有助于人们理解算法,晦涩难懂的算法往往隐含错误,不易被发现,并且难于调试和修改。

健壮性

一个好的算法还应该对输入数据不合法的情况做合适的处理。比如输入的时间或者距离不应该是负数等。
健壮性:当输入数据不合法时,算法也能做出相应处理,而不是产生异常或莫名其妙的结果。

时间效率和存储量低

好的算法还应该具备时间效率高和存储量低的特点。
时间效率指的是算法的执行时间,对于同一个问题,如果有多个算法能够解决,执行时间短的算法效率高,执行时间长的效率低。
存储量需要指的是算法在执行过程中需要的最大存储空间,主要指算法程序运行时占用的内存或者外部硬盘存储空间。
设计算法应该尽量满足时间效率高和存储量低的需求。

综上所述:好的算法,具有正确性、可读性、健壮性、高效率和地存储量的特性。

算法效率的度量方法

设计算法要提高效率。算法的效率指的是算法的执行时间。

事后统计方法

这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较的,从而确定算法效率的高低。
缺点:
必须依据算法事先编制好程序,这通常需要花费大量的时间和精力。如果算法编制出来后发现不太好,那不就是凉凉了么。
时间的比较依赖计算机硬件和软件等环境。有时候会掩盖算法本身的优点。
算法的测试数据设计困难,并且程序的运行时间往往还与测试数据的规模有很大的关系,效率高的算法在校的测试数据面前往往得不到体现。比如10个数字进行排序,不管用啥算法,他的差异几乎都是零。但是如果是100个,1000个甚至更多的时候,再进行排序,不同算法的差异就很大了。

事前分析估算方法

在计算机程序编制前,依据统计方法对算法进行估算。
经过分析,一个高级程序语言编写的程序再计算机上运行时所消耗的时间取决于以下:
1、算法采用的策略、方法。
2、编译产生的代码质量
3、问题的输入规模
4、机器执行指令的速度。
由此可见,抛开计算机的性能。一个程序的运行时间,依赖于算法的好坏和问题的输入规模。问题输入规模是指输入量的多少。

例如:
两种求和的算法
第一种算法:

int i,sum = 0,n = 100; /*执行1次*/
for(i = 1;i < = n;i++){/*执行n+1次*/  
	sum  = sum + i;/*执行n次*/
	
}
printf("%d",sum);/*执行1次*/

第二种算法:

int sum = 0,n = 100;/*执行1次*/
sum = (1+n)*n/2;/*执行1次*/
printf("%d",sum);/*执行1次*/

第一种算法,执行了1+(n+1)+n+1次=2n+3次;第二种算法,是执行3次,事实上两种算法的第一条和最后一条语句是一样的,所以我们关注的代码其实是中间的那部分,我们把循环看作是一个整体,忽略头尾循环判断的开销,那么这两个算法其实就是n次与1次的差距。

函数的渐进增长

函数的渐近增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们就说f(n)的增长渐近快于g(n)。
并且,我们可以忽略加法常数、最高次项的常数、其他次要项。

算法的时间复杂度

算法时间复杂度定义

在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间度量,
T(n) = O(f(n)).表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。

用大写0()来体现算法时间复杂度的记法,我们称之为大O记法。
一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。

推导大O阶方法

1、用常数1取代运行时间中的所有假发常数。
2、再修改后的运行次数函数中,只保留最高阶。
3、如果最高阶存在且不是1.则取出与这个项相乘的常数,得到的就是大0阶。

常数阶

不管常数为多少,都记作O(1),而不是0(3)、0(5)等其他的。
对于分支结构,无论是真、还是假,执行的次数都是恒定的,不会随着n的变大而发生改变,所以单纯的分支结构,其时间复杂度也是0(1).

线性阶

线性阶的循环结构会复杂很多,因此**,我们要分析算法的复杂度,关键是分析循环结构的运行情况。**

int i;
for(i = 0;i < n;i++){
	执行循环体。
}

对数阶

int count = 1;
while(count < n){
	count = count * 2;
}

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

平方阶

嵌套循环`

int i,j;
for(i = 0;i < n;i ++){
	for(j= 0;j < n;j++){
		/*时间复杂度为0(1)的程序步骤序列*/
	}
}	

对于内层循环,时间复杂度为O(n),对于外层循环,就是内层这个时间复杂度为0(n)的语句,在循环n次,所以这段代码的时间复杂度为0(n^2)
如果外循环的循环次数改为了m,时间复杂度就变为了O(mxn)

常见的时间复杂度

时间复杂度所耗费的时间从大到小依次是:
O(1)< O(logn) <O(n) < O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)

最坏情况与平均情况

最坏情况运行时间是一种保证,那就是运行时间将不会再坏了。在应用中,这是一种最重要的需求,通常,除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
平均运行时间是所有情况中最有意义的,因为他是期望的运行时间。
也就是说,在运行一段代码时,是希望看到平均运行时间的。可是现实中,平均运行时间很难通过分析得到,一般都是通过运行一定数量的实验数据后估算出来的。

算法的空间复杂度

写代码时,可以用空间来换取时间。

比如:要判断某某年是不是闰年,你可能需要花一些时间来写一个算法,当你输入一个年份时,通过计算得出是否是闰年。还有另一种方法,就是实现建立一个2050个元素的数组(年数比现实多一点),然后把所有的年份按照下标的数字对应,如果是闰年,此数据项的值就是1,如果不是值为0.这样所谓的判断某一年是否是闰年就变成了查找这个数组的某一项的值是多少了,此时,我们的运算是最小化了,但是硬盘上或者内存中需要存储这2050个0和1.
算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式:S(n) = O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。

一般情况下,一个程序在计算机上执行时,除了要存储程序本身的指令,常数,变量和输入数据外,还要存储对数据操作的存储单元。若输入数据所占空间只取决于问题本身,和算法无关,这样只要分析该算法在实现时所需的辅助单元即可。若算法执行时所需的辅助空间相对于输入数据而言是一个常数,则称此算法为原地工作,空间复杂度为O(1).

总结

算法的定义:算法是解决特定问题求解步骤的描述,在计算机中为指令的有限序列,并且每一条指令表示一个或多个操作。
算法的特性:有穷性、确定性、可行性、输入、输出。
算法的设计要求:正确性、可读性、健壮性、高效率和低存储量
算法的度量方法:事后统计方法、事前分析方法
函数的渐近增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我i们就说f(n)的增长渐近于g(n).
得出一个结论:判断算法好与不好,只通过商量的数据是不能做出准确判断的,如果我们可以对比算法的关键执行次数函数的渐近增长性,基本就可以分析出:某个算法,随着n的变大,他会越来余额优于另一个算法,或者越来越差于另一个算法。
算法时间复杂度的定义和推到大O阶的步骤:
推到大O阶;
用常数1取代运行时间中的所有加法常数
在修改后的运行次数函数中,只保留最高阶项。
如果最高项存在且不是1,则去除与这个项相乘的常数。
得到的结果就是大O阶

常见的时间复杂度所消耗时间的大小排序:
O(1)< O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(n3)<O(2n)<O(n!)<O(n^n)
最坏情况和平均情况的概念。以及空间复杂度的概念。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值