前言
本文为笔者学习王道数据结构的笔记,将来会不定期更新,希望与大家共同进步。
关于王道计算机考研大家可以去mooc官网订阅,支持正版!王道链接
提示:以下是本篇文章正文内容,下面案例可供参考
一、数据结构在学什么
所谓创造价值,也就是把信息变成money,我们下面就看3个现实中把问题信息化的例子:
我们从自己生活的变化就很明显的可以感受到世界正在快速信息化
第一个例子,比如我们以前的存钱:钱拿在口袋里沉甸甸的
而现在存钱,钱在微信里空荡荡
第二个例子:我们以前排队打饭
但是现在如果我们去吃海底捞,是可以在网上叫号来等待的
第三个例子,我们以前交朋友基本都是生活有交集的,距离近的
但是现在,我们交朋友只需要打开社交软件,点点红心
而我们在学习数据结构之前,也一定已经学习过了C语言,那么我们试着想一想,c语言该怎么实现上面3个功能呢?
第一个存钱的例子:我们只需要把钱设置成一个浮点型变量即可
第二个排队的例子:我们可以设置一个数组,用数组下标来表示桌号,数组元素大小表示该桌子人数…而学完数据结构这门课,我们会知道比数组更好的解决办法
第三个关注/互相关注的例子:我们将来会在学习“图”部分得到答案
所以回到最初的问题:数据结构在学什么?
我们就可以得到答案了,在学完这门课后,我们就知道怎么用程序代码把现实中的问题给信息化,并且如何用计算机高效的处理这些信息,从而创造价值
最后,我们来阐述一下数据结构和计算机组成原理、操作系统、计算机网络这三门课的关系,比如我们现在有一个电脑或者手机,这些东西底层就是由CPU、内存、硬盘等组成的
计算机组成原理:就是研究计算机底层这些硬件的工作原理
操作系统:计算机硬件上我们还会架设一个操作系统:比如安卓、ios,操作系统所研究的就是这些操作系统是怎么管理你的手机或者电脑
数据结构:我们的手机安装了操作系统之后,我们会下载各种各样的软件,比如QQ、微信等,大多数计算机的同学将来都是要从事最上层的这种软件开发。而这些软件本质也是为了解决现实中的一些问题,所以我们要有扎实的数据结构基础,然后知道怎么把现实里的问题信息化,最后交给计算机来处理。
计算机网络:该门学科就是学习如何各个计算机或者手机的互通
二、数据结构的基本概念
2.1知识总览
2.2基本概念
2.2.1数据
数据是信息的载体,是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料
2.2.2数据元素、数据项
数据元素是数据的基本单位,通常作为一个整体进行考虑和处理。
一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位
其实什么数据元素,什么是数据项需要判断到底需要计算机来处理什么样的问题。要根据实际的业务需求来确定什么是数据元素,什么是数据项
来举个具体的例子来帮助大家理解:我们去吃海底捞
对于排号系统来说,所谓的数据元素就是每波顾客,
而每波顾客在取号系统中又会有:号数、取号时间、就餐人数
再比如,我们玩一些社交软件,基本都会有一些基本信息
对于微博这一应用,它的数据元素就是每一个账号,每个账号又包含昵称、性别、生日等信息,这就是它的数据项
ps:对于一些复杂的数据项,比如生日:可以拆分为年-月-日这种多个数据项,这种多个数据项组合起来表示整体的信息,可以表示为组合项
2.2.3数据结构、数据对象
数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
数据对象是具有相同性质的数据元素的集合,是数据的一个子集
举个例子:
2.2.4数据类型、抽象数据类型(ADT)
数据类型是一个值的集合和定义在此集合上的一组操作的总称
抽象数据类型(Abstract Data Type,ADT):是抽象数据组织及与其相关的操作
当我们定义一个抽象数据类型,其实也就是用数学化的语言定义数据的逻辑结构、定义运算,与具体的实现无关(不涉及物理结构)
定义了一个ADT也就是定义数据的逻辑结构、数据的运算,也就是定义了一个数据结构
2.3三要素
2.3.1逻辑结构
逻辑结构——数据元素之间的关系是什么?
集合:
线性结构:
树型结构:
图结构:
2.3.2物理结构
物理结构(存储结构)——如何用计算机表示数据元素的逻辑关系
顺序存储:
链式存储:
索引存储:
散列存储:
注:数据的存储结构,除了顺序存储,其他的三种都是非顺序存储
绪论部分只需要理解三点:
1.若采用顺序存储,则各个数据元素在物理上必须是连续的;
若采用非顺序存储,则各个数据元素在物理上可以是离散的。
2.数据的存储结构会影响存储空间分配的方便程度
比如下图中有人有紧急情况想插队,就要后面的元素分别向后移动一位
而对于下图这种,如果有人紧急情况想插队,
只需要给他一个更早的号,然后让他随便找个位置坐下就可以
3.数据的存储结构会影响对数据运算的速度
还是刚才的两张图,如果我们想找第三个人
由于左边是顺序存储,我们很容易就能找到第三个人。
而对于右边这种离散存储,我们只能叫号,没办法直接知道第三个人在哪里
2.3.3数据的运算
数据的运算——施加在数据上的运算包括运算的定义和实现。
运算的定义是针对逻辑结构的,指出运算的功能;
运算的实现是针对存储结构的,指出运算的具体操作步骤
2.4小结
三、算法的基本概念
3.1什么是算法
举个例子:
对于计算机来说,
数据结构就是给它提供要加工的食材,
算法就是描述了这个计算机要如何处理这些食材,描述了具体的步骤
举个具体的例子:将下列线性表按年龄递增排序
3.2算法的五个特性
3.2.1有穷性
有穷性:一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成
算法必须是有穷的,程序可以是无穷的。
比如我们要解决一个问题,用一个算法肯定不能永远的死循环而问题得不到解决。
又比如微信(一个程序而非算法),如果条件允许可以一直运行下去
注:如果你写了一个死循环,这个是不能叫作算法的(不满足有穷性)
3.2.2确定性
确定性:算法中每条指令必须有确切的含义,对于相同的输入只能有相同的输出
我们举刚才的例子,按年龄升序排列是有可能得到下面两种结果的(两人年龄相同),但是我们写一个算法它必须一个相同输入只能得到一个相同的结果,否则就不能叫作算法。可能会有同学问,那我具体遇到这种年龄相同的怎么排序?解决方法有很多,比如谁的钱多排前面、年龄相同时,谁原本在前面就在前面…方法很多,只要保证一个相同输入得到一个相同输出即可。
3.2.3可行性
可行性:算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现
可行性说白了就是你的实现方案可以由计算机代码来实现
最后算法还需要有输入和输出这两个因素
集合
3.2.4输入
输入:一个算法有0个或多个输入,这些输入取自于某个特定对象的
3.2.5输出
输出:一个算法有1个或多个输出,这些输出是与输入有某种特定关系的量
上面5个特性缺一不可,缺少一个都不能叫作算法
3.3“好”算法的特质
3.3.1正确性
正确性:算法应能正确的解决问题
ps:如果一个算法没有正确的解决问题,只能说明这个算法比较拉、不是一个好算法,不能说这个算法不是一个算法。
3.3.2可读性
可读性:算法应具有良好的可读性,方便人们理解
ps:我们可以用自然语言来描述算法,也可以用伪码、c语言、java等等来描述算法,我们除了自己能看懂自己的代码,也最好能让别人看懂。
这就涉及到注释的一个笑话:我最恨那些不写注释的人,更恨那些让我写注释的人/doge
大家以后学习工作最好还是写代码,不然你同学和同事看你代码真的有一种想把你刀了的冲动
3.3.3健壮性
健壮性:用户输入非法数据时能够及时的做出反应,而不是输出莫名其妙的结果
比如我们要求输入几个数字来进行运算,有人不小心输入了英文字母,这个时候我们的程序应该是对非法输入做出反应,提醒输入者输入错误。
3.3.4高效率和低存储量需求
这个也很好理解,和我们生活一样,都希望最少的成本得到最多的回报
3.4小结
四、算法的时间复杂度
4.1引子
我们如何评价算法的时间开销呢?
是先让算法跑一遍,然后统计时间吗?
但是这样的话同样的算法由于运算机器的不同会有差异(比如超级计算机和单片机)
同样的算法也会由于编程语言的不同产生差异(越高级的语言效率越低)
另外,算法的运行时间也与我们编译产生的机器指令质量有关
还有就是有些算法根本就不可能事后统计,比如导弹控制算法
举个例子:爱你三千遍
void loveYou(int n) {
int i = 1;//爱你的次数
while (i <= n)
{
i++;//每次+1
printf("i love you %d", i);
}
printf("i love you more than %d", n);
}
int main() {
loveYou(3000);
}
对于上述代码我们提出以下两个问题:
1.是否可以忽略表达式的某些部分
2.如果有好几千行代码是否需要一行一行数呢?
4.2问题1:是否可以忽略表达式的某些部分
我们用下面三个进行举例,
这个其实和高数里面的极限是同理的,n趋于无穷,我们只看最高阶就可以了
其实我们评价一个算法的优劣时,甚至可以把最高阶前面的系数也给去掉,比如T(n)=3n,变成T(n)=n。因为假设n=3000,T(n)由9000变为3000其实对计算机来说没什么区别的,都是一个数量级。
综上,我们只需要知道对于时间复杂度的度量,只需要关注它表达式最高阶的一项,然后把系数变为1,再在前面套一个大0即可,比如T(n)=3n+21变成O(n)=n
加法规则
乘法规则
比较大小,大家就记住从小到大“常对幂指阶”
4.2问题2:如果有好几千行代码是否需要一行一行数呢?
对于顺序执行的代码,不管多少行,其实都是常数阶的,到时候都可以算成O(1),而这些顺序执行的如果加上循环的,比如O(n),O(1)+O(n)最后还是O(n),我们在进行时间复杂度计算的时候,只需要关注循环即可
而循环内又是有多个语句,比如下面代码
只考虑循环的三条语句应该是O(3n),但是最后把系数去掉也还是变成O(n),所以循环内我们也只关注一条语句
有同学会问,如果有嵌套的循环怎么办呢?我们用下面的代码举例
对于时间复杂度我们进行如下总结:
4.3实战练习
计算上述算法时间复杂度T(n):
假设最深层循环语句频度(总共循环了多少次)为x,
i:2,4,8,16,32…(每执行1次,2变为原来2倍)
也就是2^x>n
两边取对数可得x>log2n
因为x为整数,x=log2n+1
T(n)=O(x)=O(log2n)
计算上述算法时间复杂度T(n):
这题因为是查找数组中某一个数,而这个数又是乱序排放的,所以要分情况讨论
最好情况:要查找元素在第一个位置——最好时间复杂度O(1)
最坏情况:要查找元素在最后一个位置——最坏时间复杂度O(n)
平均情况:要查找元素在每一个位置概率相同,均为1/n
4.4小结
五、算法的空间复杂度
5.1引子
空间复杂度:空间开销(内存开销)与问题规模n之间的关系
我们用上述代码举例:
首先,当这个程序运行之前,需要把这个程序相关的程序代码放到内存中(实际放到内存的程序代码肯定不是我们上述这样的代码,而是经过编译之后形成的相应的机器指令),我们编写完这样一个程序之后,其实这个程序它放到内存需要占多大的空间这个是已经确定的。
我们假设程序代码占8b,而变量i和n又都是整形各占4b,所以总计是108b
注意:这里不管n传过来的值是多少,一共也就2个变量,该题中n不影响变量创建有多少
如果一个算法所需要的空间和问题规模n没有关系,也就是说空间复杂度为O(1),
我们就可以称这种算法可以原地工作,这个术语可能会在选择题中进行考察。
在这个算法中,我们定义了一个int型的数组,数组长度为n,n也就是我们的问题规模
很显然,n的变化是会影响到数组大小的
所以在这个算法中,它运行的过程中所需的内存空间大小就会和这个问题规模n有关
一个int型4字节,n是4字节,i是4字节,然后数组flag是4n字节,共4n+8字节
和时间复杂度类似,当我们探讨一个算法的空间复杂度时,我们只需要关注它所需要消耗的空间是什么数量级(什么阶数)就行,我们同样用大O表示法表示。
比如上面的S(n)=O(4n+8)=O(n)
经过上面的例子,大家应该就可以体会到:如果在函数中,我们定义了某些变量,但这个变量它所占空间和问题规模n没有关系,那这种类型的变量最多在我们的表达式中增加一个常数项,但由于我们最终要转换成大O表示法,所以这种常数项对我们的结果不会产生影响。因此,我们在分析一个算法的空间复杂度时就只需要关注它所需要的存储空间大小和问题规模相关的变量就可以了。
5.2实战例题
例1:
在这个例题中,我们定义了二维数组,它的大小n*n。根据刚才的分析,我们就知道n和i这两个变量就不需要考虑(存储这两个变量也就是增加一个常数项),唯一和问题规模n相关的就是flag这个数组,问题规模为n时存储这个数组所需要的内存空间就是4 * n * n。
所以该算法的空间复杂度为O(n^2)
例2:
该例中我们定义了一个二维数组flag,大小n*n;一个一维数组other大小n,还有一个变量i
根据前面所学,只考虑最高阶,也就是O(n^2)
5.3函数递归调用带来的内存开销
比如上述代码,假设loveyoun函数传参过去为5,5会往下变成4继续调用loveyou函数…直到n变为1然后if不满足,打印语句,然后一层一层的往上返回。
在这个递归过程中,我们每调用一次loveyou函数就会产生3个变量(递归调用产生的变量是存放在不同空间的)如下图
每次调用一共产生16个字节(n4字节,abc各4字节,共16字节),那么我们就可以简单认为每层递归调用会需要k个字节的空间(k为常数),n为5时共产生了5次调用,因此递归调用的层数和我们的问题规模n是刚好相等的,所以问题规模为n时,它所需要的内存大小就是kn字节。
如果用大O表示法只关注阶数,我们就可以把这个k给去掉,
所以该程序空间复杂度为S(n)=O(n)
考研中大多数考空间复杂度的题都与该题类似,大家记住:空间复杂度=递归调用的深度
刚才这题每层递归调用所需要的空间都可以用一个常量k表示,但是也有一些算法,它的每层递归调用所需要的内存空间大小是不一样的。比如下面的题目:
我们每层调用都会开辟一个n大小的数组,也就是说第一次调用时n大小为5,那么数组长度为5;第二次调用时n大小为4,那么数组长度为4…总之,由于每层调用的数组大小和问题规模n有关,因此每层调用用于存放变量大小的空间也不一样。
综合来看,各级递归调用存储flag的空间是这样一个值
ps:绝大多数情况不会遇到这么复杂的,考研中空间复杂度一般都是前面说的那种每层都是k个大小