《大话数据结构》学习笔记 —— 02 算法


基本概念


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

为了解决某个或某类问题,需要把指令表示成一定的操作序列,操作序列包括一组操作,每一个操作都完成特定的功能,这就是算法。



特性

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


输入输出

算法具有零个或多个输入,并且至少有一个输出

绝大多数情况下,算法都需要输入参数,但如果只是打印一句Hello World不需要任何输入参数。

算法是一定要有输出的,不需要输出的话,还要算法干什么。。。


有穷性

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

这里的有穷的概念并不是纯数学意义的,如果一个算法需要十几二十年才得出结果,在数学上是有穷的,但这算法的意义也就不大了。


确定性

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

算法在一定条件下,对于相同的输入只能有唯一的输出结果


可行性

算法中的每一步都是可行的,每一步都能通过执行有限次数完成

算法可以转换成程序在机器上运行并得到正确结果。






算法设计的要求



正确性

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


算法的正确性大题有以下四个层次:
  1. 算法程序没有语法错误;

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

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

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

一般情况下,我们把层次3作为一个算法是否正确的标准。



可读性

算法设计的另一目的是为了便于阅读、理解和交流

我们写代码的目的,一方面是为了让计算机执行,但还有一个重要的目的是为了便于他人阅读。

如果可读性差,时间长了自己都不知道写了些什么。



健壮性

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

这就需要我们做好输入参数的校验,判断数据是否合法,只有合法的数据才能执行后续的程序。



时间效率高和存储量低

在生活中,人们总是希望花最少的钱,用最短的时间,办最大的事。

算法也是一样的思想,最好用最少的存储空间,花最少的时间。

但是由于存储空间的成本较低,所以一般情况下我们会更加注重算法的时间效率。






算法时间复杂度



前言

在正式学习算法时间复杂度前,先补充几个结论。



1. 忽略加法常数

在这里插入图片描述
譬如2n+3跟3n+1,随着n的增长,加法常数对算法复杂度基本无影响,所以我们可以忽略加法常数



2. 与最高次项相乘的常数

在这里插入图片描述
再来看看4n+8和2n2+1,首先我们忽略掉加法常数,那么就变成了4n和2n2的比较。

随着n的增长,我们发现哪怕是去掉与n相乘的常数,对比结果也没有发生变化。

也就是说,与最高次项相乘的常数并不重要



3. 最高次项的指数越大,增长速度越快

在这里插入图片描述
通过观察可以发现,随着n的增长,最高次项指数越大,结果也会变的增长特别快。



4. 关注主项(最高阶项)

在这里插入图片描述
我们观察到随着n的增长,算法G无限趋近于算法I。

于是我们得出结论:判断算法的效率时,函数中的常数和其它次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。



定义

在进行算法分析时,语句的总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。

算法的时间复杂度记作:T(n) = O(fn)

这样用大写O()来提现算法复杂度的记法,被称为大O记法



推导大O阶方法

其实就是前言里面提到的那些结论,这里再次进行总结。

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

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

  • 如果最高阶存在且不为1,则去除与这个项相乘的常数。

注意:

下面谈到的常数阶、线性阶、平方阶都是原作者取的非官方的名称。



常数阶

在这里插入图片描述
这个函数的运行次数是f(n)=3。

根据推导大O阶的方法,第一步把常数3改为1。

最后得出时间复杂度为O(1)。


在这里插入图片描述
事实上无论n为多少,这两段代码的就只有运行3次和12次执行的差异,

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



线性阶

这类型的算法,我们要分析算法的复杂度,关键是要分析循环结构的运行情况

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

这段代码的时间复杂度是O(n),循环体执行次数为n次。



对数阶

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

由2x=n得到x=log2n,所以这个循环的时间复杂度为O(logn)。



平方阶

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

外循环1次,内循环执行n次,所以这个算法的复杂度为O(n2)。


再来看下面的例子
int i,j;
for(i = 0; i < n; i++)
{
	// 注意这里j=i,而不是0
	for(j = i; j < n; j++)
	{
		/* 时间复杂度为o(1)的程序步骤序列 */	
	}
}

总的执行次数为:

n+(n-1)+(n-2)+...+1 = n(n+1)/2 = (n^2)/2 + n/2

使用大O推阶法,保留最高阶项(n^2)/2;再去掉这个项相乘的常数也就是1/2,所以这个算法的复杂度为O(n2)。



发布了223 篇原创文章 · 获赞 13 · 访问量 6万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览