目录
1、数据结构和算法绪论
什么是数据结构?数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科,简单点说就是,程序设计=数据结构+算法,数据结构就是数据元素相互之间存在的一种或多种特定关系的集合
传统上,我们把数据结构分为:逻辑结构和物理结构
1.1. 基本概念和术语
数据Data:是客观事物的符号表示。在计算机科学中指的是所有能输入到计算机中并被计算机程序处理的符号的总称
数据元素(Data Element):是数据的基本单位,在程序中通常作为一个整体来进行考虑和处理
一个数据元素可由若干个数据项(Data Item)组成。数据项是数据的不可分割的最小单位。数据项是对客观事物某一方面特性的数据描述
数据对象(Data Object):是性质相同的数据元素的集合,是数据的一个子集
数据结构(Data Structure):是指相互之间具有一定关系的数据元素的集合。
1.2 逻辑结构
元素之间的相互联系称为逻辑结构。
四大逻辑结构:集合结构、线性结构、树形结构、图形结构
集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他不三不四的关系
线性结构:线性结构中的数据元素之间是一对一的关系
树形结构:树形结构中的数据元素之间存在一种一对多的层次关系
图形结构(或网状结构):图形结构的数据元素是多对多的关系
1.3 物理结构
物理结构:是指数据的逻辑结构在计算机中的存储形式,研究物理结构其实就是研究如何把数据元素存储到计算机的存储器中
存储器主要针对内存而言的,像硬盘、软盘、光盘等外部存储器的数据组织通常用文件结构来描述)。数据元素的存储结构形式有两种:顺序存储和链式存储
顺序存储结构:是指数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。例如c++的数组结构
链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。在每一个数据元素中增加一个存放另一个元素地址的指针(pointer),用该指针来表示数据元素之间的逻辑结构
数据的逻辑结构和物理结构是密不可分的两个方面,一个算法的设计取决于所选定的逻辑结构,而算法的实现依赖于所采用的存储结构
在c语言中,用一维数组表示顺序存储结构,用结构体类型表示链式存储结构
1.4 数据结构的三个组成部分
逻辑结构:数据元素之间逻辑关系的描述,D_S=(D,S)
存储结构:数据元素在计算机中的存储及其逻辑关系的表现称为数据的存储结构或物理结构
数据操作:对数据要进行的运算
下述主要讨论三种逻辑结构及其采用的存储结构如下:
2、算法
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
2.1 算法的基本特征
算法具有五个基本特征:输入、输出、又穷性、确定性、可行性
输入:算法具有零个或多个输入,这些输入取自于某个特定的对象集合
输出:一个算法有一个或多个输出,这些输出时同输入有着某些特定关系的量,输出的形式可以是打印形式输出,也可以是返回一个值或多个值
有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成
确定性:算法的每一个步骤都具有确定的含义,不会出现二义性;算法在一定条件下,只有一条执行路径,在相同的输入只能有唯一的输出结果;算法的每个步骤都应该被精确定义而无歧义
可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。
2.2 算法设计的要求
正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性,能正确反映问题的需求,能够得到问题的正确答案。正确性大体分为以下四个层次:1、算法程序没有语法错误;2、算法程序对于合法输入能够产生满足要求的输出;3、算法程序对于非法程序能够产生满足规格的说明;4、算法程序对于故意刁难的测试输入都有满足要求的输出结果
可读性:算法设计的另一目的是为了便于阅读、理解和交流
健壮性:当输入数据不合法时,算法也能够作出相关处理,而不是产生异常、崩溃、或莫名其妙的结果
时间效率高和存储量低
2.3 算法效率的度量方法
在2.2 节最后说的算法要求中的效率一般指算法的执行时间。在计算机程序编写前,依据统计方法对算法进行估算。
经过前辈总结,我们发现一个高级语言编写的程序在计算机上运行所消耗的时间取决于下列因素:1、算法采用的策略,方案;2、编译产生的代码质量;3、问题的输入规模;4、机器执行指令的速度;
看下面的例子:
第一种算法执行了1+(n+1)+n=(2n+2)次【注意:在第一种算法的for(i=1;i<=n;i++)语句中,i=1初始化操作执行了一次,i<=n的比较操作执行了n+1次,i++执行了n次(从i=2到i=n+1)】
第二种算法执行了1+1=2次
上述两个算法的时间开销其实就是n:1的差距
注意:判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高项)的阶数
2.3.1 算法时间复杂度
算法时间复杂度的定义:在进行算法分析时,语句总的执行次数T(n)时关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。
算法的时间复杂度,也就是算法的时间度量,记作T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度,其中f(n)是问题规模n的某个函数。执行次数约等于时间
这样用大写O()来体现算法时间复杂度的记法,我们称之为大o记法。
一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法
那么如何分析一个算法的时间复杂度呢?可以按照以下攻略:用常数1取代运行时间中的所有加法常数;在修改后的运行次数函数中,只保留最高阶项;如果最高阶项存在且不是1,则去除与这个项相乘的常数;得到的最后结果就是大o阶
常数阶的时间复杂度为O(1)
线性阶(线性阶就是随着问题规模n的扩大,对应计算次数呈直线增长,即单层for循环)的时间复杂度为O(n),
平方阶(如两层for循环)的时间复杂度为O(n^2)
对数阶(例如while循环)
总结图表如下:
常用的时间复杂度所耗费的时间从小到大依次是:O(1) < O(logn) < (n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) O(1),O(logn),O(n),O(n^2)我们前边已经给大家举例谈过了,至于O(nlogn)我们将会在今后的课程中介绍。 而像O(n^3)之后的这些,由于n值的增大都会使得结果大得难以想象,我们没必要去讨论它们。
平均运行时间是期望的运行时间,最坏运行时间是一种保证,在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
2.3.2 算法的空间复杂度
我们在写代码时,可以用空间来换去时间。
举个例子说,要判断某年是不是闰年,你可能会花一点心思来写一个算法,每给一个年份,就可以通过这个算法计算得到是否闰年的结果。 另外一种方法是,事先建立一个有2050个元素的数组,然后把所有的年份按下标的数字对应,如果是闰年,则此数组元素的值是1,如果不是元素的值则为0。这样,所谓的判断某一年是否为闰年就变成了查找这个数组某一个元素的值的问题。
第一种方法相比起第二种来说很明显非常节省空间,但每一次查询都需要经过一系列的计算才能知道是否为闰年。第二种方法虽然需要在内存里存储2050个元素的数组,但是每次查询只需要一次索引判断即可。 这就是通过一笔空间上的开销来换取计算时间开销的小技巧。到底哪一种方法好?其实还是要看你用在什么地方。
算法的空间复杂度通过计算算法所需的存储空间实现,算法的空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。 通常,我们都是用“时间复杂度”来指运行时间的需求,是用“空间复杂度”指空间需求。 当直接要让我们求“复杂度”时,通常指的是时间复杂度。 显然对时间复杂度的追求更是属于算法的潮流
空间复杂度是指算法编写程序中,在计算机中运行时所需存储空间大小的度量,该存储空间一般包括三个方面:1、指令常数变量所占用的存储空间;2、输入数据所占用的存储空间;3、辅助空间
一般地,算法的空间复杂度指的是辅助空间,一维数组a[n]的空间复杂度O(n);二维数组a[n][m]的空间复杂度为O(n*m)
算法和程序是两个完全不同的概念,一个计算机程序是一个算法使用某种程序设计语言的具体实现。
参考:小甲鱼视频