目录
一、概述二、数据结构
2.1 数据结构的起源
2.2 基本概念和术语
2.3 逻辑结构和物理结构
2.4 抽象数据类型
三、算法
3.1 算法定义
3.2 算法的特性
3.3 算法设计的要求
3.4 算法的效率估算
3.4.1 事前分析估算方法
3.4.2 函数的渐近增长
3.4.3 算法时间的复杂度
一、概述
数据结构与算法
在程序设计中经常用到,但是如果没有系统学习过,使用过程就变得云里雾里的。此篇文章介绍一些
数据结构与算法
的基础知识,使读者可以对
数据结构
和
算法
有一个大致的认识。
程序设计 = 数据机构 + 算法
数据结构
:相互之间存在一种或多种特定关系的数据元素的集合。算法
:是解决特定问题的求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
二、数据结构
2.1 数据结构的起源
一开始,人们用计算机解决问题时,都是先将问题抽象为数值模型
,如:求解线性方程组、微分方程等。后来,随着计算机地广泛使用,更多的非数值问题
需要计算机去解决,如:图书馆检索系统的表格、井字棋的棋盘格局、多叉交通灯的管理问题。这些问题的数学模型不再是数学方程,而是诸如表、树、图之类的数据结构。因此,数据结构
是一门研究非数值问题
中计算机操作对象
与它们之间的关系和操作
的学科。
1968年,美国一些大学将数据结构
作为一门课程纳入教学计划中,但对课程范围没有明确规定。同年,美国唐·欧·克努特教授在他所著的《计算机程序设计技巧》中系统得阐述了数据的逻辑结构和物理结构及其操作。到20世纪70年代初,出现了大型程序,软件也相对独立,人们越来越重视数据结构
,认为程序设计的实质就是对确定问题选择一个好的数据结构
,加上设计一种好的算法
。
程序设计 = 数据结构 + 算法 |
2.2 基本概念和术语
数据
:是描述客观事物的符号,是计算机中可以操作的对象,是能输入
给计算机识别并处理
的符合集合。 可以理解为:可以输入到计算机,并且能够被计算机处理的任意事物。不仅是整型、浮点型等数值类型,还可以是字符、一段音频、一张图片、一个视频等非数值类型。
数据元素
:是数据的基本单位,在计算机中可以作为一个整体考虑和处理。
例如:一个视频文件中,一帧视频帧就是数据元素。一个图书管理系统表格中,一条图书条目就是数据元素。
数据项
:是数据元素的基本单位,是数据不可分割的最小单位。
例如:在一个视频帧(数据元素)中,视频帧的宽、高、长度等就是数据项。在一条图书条目中,书名、作者名就是数据项。
数据对象
:是性质相同的数据元素的集合,是数据的子集。
我的理解就是将数据按类别分开的的子集。例如:在图片数据中,.jpg图片就是一个数据对象,.png图片是另一个数据对象。
数据结构
:是相互之间存在一种或多种特定关系的数据元素
的集合。
任何问题中,数据元素都不是孤立存在的、而是在它们之间存在着某种关系,这种数据元素相互之间的关系称为结构(逻辑结构)。
2.3 逻辑结构和物理结构
逻辑结构
:数据元素相互之间的关系。分为下面四种: ①集合结构:数据元素同属于一个集合,相互之间没其他关系。②线性结构:数据元素之间是一对一的关系。
③树形结构:数据元素之间是一对多的关系。
④图形结构:数据元素之间是一对多的关系。
物理结构
:数据的逻辑结构在计算机中的存储形式。分为两种: ①顺序存储结构:数据元素存放在地址连续的存储单元里,数据间的逻辑关系与物理关系一致。如图:②链式存储结构:数据元素存放在任意的存储单元里,这组存储单元一般是不连续的。
链式存储比较灵活。
2.4 抽象数据类型
数据类型
:是指一组性质相同的数值的集合以及定义在此集合上的一些操作的总称。一般分为2种:原子类型、结构类型。原子类型
:是指不可以再分解的基本类型,例如:C语言中的int
、char
、short
等内置类型。结构类型
:由若干个类型组合而成的,可以再分解的,例如:C语言中的整型数组(若干个整型组成),结构体。
抽象
:是指抽取出事物具有普遍性的本质。是一种思考问题的方式,隐藏了繁杂的细节,只保留实现目标所必须的信息。
例如:在街上走着,有人过来喊你“靓仔”,这个称呼就是抽象,隐藏了靓不靓的细节,只要看见年轻男性就叫“靓仔”,看见年轻女性就叫“靓女”。
再比如:无论是Windows系统、Linux系统、Mac系统都需要用到整数运算,但它们的实现方式可能不同(有些大端,有些小端),这些实现细节我们都忽略,只保留了“整型”的共同属性:4个字节的,可以加减乘除等操作。
抽象数据类型
:是指一个数学模型及定义在该模型上的操作。
抽象数据类型 和 数据类型 实质上是一个概念,“抽象”的意义在于数据类型的数学抽象特性。另一方面,抽象数据类型 不局限与处理器已定义并实现的数据类型,还包括用户自己定义的数据类型。
三、算法
3.1 算法定义
算法
:是解决特定问题的求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
例如:解决“把大象放进冰箱”这个问题的算法,可以表示为①把冰箱打开;②把大象放进去;③把冰箱关上。
同一个问题可以有多种算法实现。
3.2 算法的特性
算法有5个特性:输入、输出、有穷性、确定性、可行性。
输入输出
:算法具有零个或多个输入,具有一个或多个输出;也就是说,算法可以没有输入,但绝对有输出
。有穷性
:算法必须在执行有限的步骤之后结束,且每个步骤的执行时间也是有限的。确定性
:算法的每一步骤都具有确定的含义,不会出现二义性。任何条件下,相同的输入只能得出相同的结果。可行性
:算法的每个步骤都是可以通过已经实现的基本运算执行有限次来实现的。
3.3 算法设计的要求
一个问题可以有多个算法实现,那怎样的算法才是“好”的算法,这一小节对“好”的算法提了几个要求:
正确性
:算法应该满足具体问题的的需求。
正确性的四个层次:
1、算法不含语法错误;
2、算法对于几组输入,可以得出满足要求的结果;
3、算法对于一些特殊的、有刁难性的输入,可以得出满足要求的结果;
4、算法对于一切的输入,都可以得出满足要求的结果;
当算法达到第三个层次,则可以认为是一个合格算法。
可读性
:是指实现算法的代码是便于阅读、易于理解的。
如果写的代码,他人看起来很困难则不是一个好算法。
健壮性
:对于不合法的输入,算法也能给出反应,而不是输出莫名其妙的结果。
效率与低存储量要求
:好的算法要求整个算法执行时间尽可能短,占用的内存尽可能少。
3.4 算法的效率估算
从上个小节了解“设计良好的算法”的四个要求,其中前三个都很好懂,但效率高低、存储多少需要怎样去衡量呢?下面介绍一种常用的估算方法–事前分析估算方法。
3.4.1 事前分析估算方法
事前分析估算方法
:在编写算法代码之前,依据统计方法对算法进行估算。
高级语言编写的程序在计算机运行所消耗的时间取决于下面四个因素:
1、算法采用的策略、方法。
2、编译器产生的机器代码质量。
3、问题的规模,例如 “求100以内的素数” 还是 “求1000以内的素数”。
4、机器执行指令的速度。
5、编写算法的语言,同一算法,越高级的语言,执行效率越低。
其中,2、4、5是与计算机的硬件或软件相关的因素,不适合用来判断算法的效率,所以,算法的效率只与“采用的策略”、“问题规模” 相关。下面从两个算法来理解这两个因素是怎样影响算法效率的。下面两个算法都是实现从1加到100,看看哪个效率最高
- 算法1:总共执行 2n+3 次
// 算法1:总共执行 2n+3 次
int i=0, sum=0, n=100; // 执行 1 次
for(i=0; i<100; i++) // 执行 n + 1 次
{
sum = sum + i; // 执行 n 次
}
printf("sum = %d\n",sum);// 执行 1 次
- 算法2:总共执行 3 次
// 算法2:总共执行 3 次
int sum=0, n=100; // 执行 1 次
sum = (1+n) * n/2; // 执行 1 次
printf("sum = %d\n",sum);// 执行 1 次
两个算法比较:
“算法1”采用的策略是用循环将1到100的数相加,问题规模n为100,总共执行了 2n+3 次,也就是203次;
“算法2”采用的策略是等差数列求和的算法,问题规模n为100,总共执行了 3 次。
计算运算时间最有效的方法就是:计算对运行时间有消耗的基础操作的执行次数。显然,算法2 比 算法1 的效率高,而且随着问题规模地增加,执行次数差距会越来越大。
在分析一个算法的时间时,最重要的是,将基础操作的数量与问题规模(n)联系起来,即基础操作的数量必须表示成问题规模的函数,以上面两个算法为例: f(n) = 2n + 3
、f(n) = 3
。
事前分析估算方法总结:算法的效率由基础操作
的执行次数决定,次数越少,算法越好;最终需要将基础操作的次数表示成问题规模(n)的函数f(n)。
3.4.2 函数的渐近增长
上个小节的事前分析估算方法
最终会将算法的效率表示为以问题规模n
为变量的函数,那么两个算法对应的效率函数之间怎么判断那个更有效率呢?例如:算法A要做 2n+3
次基础操作,函数表示为f(n)=2n+3
;算法B要做 3n+1
次基础操作,函数表示为f(n)=3n+1
;请问哪个算法更快呢?答案是不一定的,n=1时,算法B更有效率;n=2时,算法A、B同等效率;n>2时,算法A才更有效率。
函数的渐近增长
:给定l两个函数f(n)和g(n),若存在一个整数N,使得对于所有的n>N,f(n)总是大于g(n),那么就说明f(n)的增长渐近快于g(n)。某个算法,随着问题规模n的增大,会越来越优于另一算法;或者越来越差于另一算法。
3.4.3 算法时间的复杂度
算法的时间复杂度
:也就是算法的时间度量,记作T(n)=O(f(n))
,表示随着问题规模n的增大,算法执行时间的增长率和f(n)
的增长率相同。称作算法的渐近时间复杂度,简称时间复杂度。其中,f(n)
是问题规模n的某个函数。
通常,用大写O( )
来表示算法时间复杂度的记法。一般,随着问题规模n的增大,T(n)
增长最慢的为最优算法。推导大O阶
1、用常数 1 取代运行次数函数中的所有加法常数;
2、在修改后的运行次数函数中,只保留最高阶项;
3、若最高阶项存在且不是 1 ,就去除与这个项相乘的常数。
经过这三个步骤,得到的就是大O阶。
下面表格是常见的时间复杂度,用表格中的前三个函数演示怎样推导大O阶:f(n)=12
:用常数 1 取代所有加法常数,得到f(n)=1
,大O阶为 O(1);
f(n)=2n+3
:用常数 1 取代所以加法尝试,得到f(n)=2n+1
,第二步,只保留最高阶项,得到f(n)=2n
,最后去掉与这个项相乘的常数,得到f(n)=n
,大O阶为O(n);f(n)=3n+2n+1:这个没有非1加法项,第一步跳过,第二步,只保留最高阶项,得到 f(n)=3n,最后去掉与这个项相乘的常数,得到 f(n)=n,大O阶为O(n);
执行次数函数 | 大O阶 | 非正式术语 |
f(n)=12 | O(1) | 常数阶 |
f(n)=2n+3 | O(n) | 线性阶 |
f(n)=3n+2n+1 | O(n) | 平方阶 |
f(n)=logn+20 | O(logn) | 对数阶 |
f(n)=2n+3nlogn+19 | O(nlogn) | nlogn阶 |
f(n)=6n+2n+3n+4 | O(n) | 立方阶 |
2 | O(2) | 指数阶 |
常用的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(logn) < (n) < O(nlogn) < O(n) < O(n) < O(2) < O(n!) < O(n)
最坏情况运行时间是一种保证,如果没有特定说明,估算算法时间复杂度时,都是指最坏时间复杂度。
总结:
这篇文章系统详细的讲了数据结构与算法的入门知识,讲了什么是数据结构、数据结构的起源、数据结构的基本术语、逻辑结构和物理结构、抽象数据类型;算法的定义、算法的特性、算法设计的要求、算法的效率估算、算法时间的复杂度等。了解这些知识之后,对后续的学习很有帮助。