我们继续来学习数据结构与算法,前面我们大概讲了一下数据结构是什么,有了一个大概的认知。
常用数据结构
现在我们来介绍一下我们在接下来的学习中要学习的数据结构:
当然,这里面又可以分成若干个小点,比如线性表又可以分成数组和链表;树可以分成二叉树、一般树;图又可以分成有向图和无向图;听起来好像很多很难,但是只要你们认真看认真学是肯定能够学会的(学不会私聊我亲自教你)。
逻辑结构和物理结构是什么?
这里又要介绍两个概念:逻辑结构和物理结构,他们是描述数据结构的两个方面
先看看啰嗦的数学概念吧:
举个简单的栗子:
定义一个复数的数据结构Complex=(C,R)其中C={C1,C2} //C是两个实数的集合R={} //这个表示C1在前,是该复数的实部;C2在后,是该复数的虚部
像这种定义了先后顺序的关系就叫做逻辑结构,要注意的是逻辑结构是相对于数据元素而言,我上面举的例子中C1、C2都是数据元素。
至于物理结构,其实就是字面意思,就是物理块是怎么组织的。
其实用数组和链表可以很好地解释逻辑结构和物理结构:
数组的逻辑结构是顺序的、物理结构也是顺序的(这就是它可以使用下标快速定位某个数据元素的原因);
链表在逻辑上是连续的但是在物理上却不是连续的(因为链表的结点是通过指针来找到下一块结点的,所以它查找元素比数组要慢得多)
关系的映像方法
既然讲到数组和链表,那就顺带提一下两种映像方法:
代表人物---数组
代表人物---链表
数据类型
数据类型学过C++或者其他语言的同学肯定都接触过,无非就是Char(字符)变量、Int(整形)变量、Double(双精度浮点数)变量等等,这里就不再多叙述了。
抽象数据类型(Abstract Data Type)
抽象数据类型也叫ADT,它是用来描述一个数学模型以及定义在该模型的一组操作,其实它跟伪代码差不多,我们可以用(D,S,P)三元组来表示一个抽象数据类型(ADT),具体是这样的:(建议先看看下面的栗子)
用(D,S,P)三元组表示其中, D 是数据对象, S 是 D 上的关系集, P 是 D 上的基本操作集。 再具体一点: ADT 抽象数据类型名 { 数据对象:〈数据对象的定义〉 数据关系:〈数据关系的定义〉 基本操作:〈基本操作的定义〉} ADT 抽象数据类型名其中基本操作的定义格式为:基本操作名(参数表) 初始条件:〈初始条件描述〉 操作结果:〈操作结果描述〉 参数表可以出现的参数:赋值参数:只为操作提供输入值。引用参数:以&打头,除可提供输入值外,还将返回操作结果(和C++的引用差不多)。初始条件:描述操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并且返回相应的出错信息。操作结果:说明操作正常完成之后,数据结构的变化状况和应返回的结果。若初始条件为空,则省略之。
这玩意在数据结构的书上到处都是,考试的时候也会要求写出来,所以务必要掌握。
看了定义可能会觉得有点懵,下面举个具体栗子:
算法
这里再提一下算法这个概念,值得一提的是当我看完整本数据结构的时候,我发现算法与结构是并存的、息息相关的,也就是说不同的数据结构有不同的算法。
比如对于数组的遍历算法用到的是下标的增长,而链表的遍历算法则是指针的不断变换。
(数组与链表,图来源于网络)
算法的要求(这个有时会出填空题噢)
这小节都是概念性的东西,背一下就好了
(一)算法一定要满足的五个重要的特性:1.有穷性 2.确定性 3.可行性4.有输入 5.有输出1、有穷性,就是你写的算法要能在有限时间内输出结果2、确定性,就是对于相同的输入输出必定相同3、可行性,算法中的所有操作都必须足够基本,即都可以通过已经实现的基本运算执行有限次来实现。4、有输入,作为算法加工的数据对象,通常体现为算法中的一组变量。有些变量需要在算法执行过程中输入,而有的算法表面上可以没有输入,实际上已被嵌入到算法之中。5、有输出,这个不用说了,没输出的话我不知道你写这个算法是图啥(二)算法设计的目标正确性、可读性、健壮性、高效率与低存储量需求
如何评估算法的好坏
一个算法的好坏取决于时间与空间这两个维度
一般有两种评估方法
一种是事后统计法,就是你先跑一下这个程序,拿计时器计时,然后打开你的任务管理器看看你的程序占多少内存。
另一种是事前统计法,我带着大家算一下下面这个栗子就知道了
for(int i=0;i//这里也要执行n+1次,为什么是n+1而不是n呢? for(int j=0;j//这里要执行n(n+1)次,为什么不是(n+1)²呢 }}所以这个程序总共要执行 n+1+n(n+1)次,所以这个程序的时间复杂度是O(n²)问题1:为什么第一层的循环是执行n+1次而不是n次?答:这里我假设n=1,首先j=0的时候会判断一下是否J<1,这里就算一次,然后J自增1,然后继续判断,这里又算一次,此时j不小于1跳出循环,所以总共是2次,即n+1次。也就是算判断的语句也要算进去。但实际上不用算这么清楚,因为时间复杂度只算到最高次就可以了。问题2:为什么第二层的循环是n(n+1)而不是(n+1)²?答:很明显,他的上层循环除去判断操作只会执行n次,所以是n(n+1)问题2:为什么时间复杂度是O(n²)而不是O(n+1+n(n+1))²答:只算最高次,其他低次幂的都被省略了。
至于空间复杂度怎么计算,无非和上面差不多,只不过换成了数用了多少个变量罢了
常见的时间复杂度
在这里引入一个术语:问题规模,通俗地讲就是上面例子中的n,问题规模一般与输入数据量成正比。比如你要遍历一个链表里的所有数,这个链表里有5个结点那么n=5,如果有50个结点那么n=50。当然我们不会写成O(5)或者O(50),而是写成O(n),这样才具有一般性。
要注意的是,O(1)的意思并不是只执行了一次,而是执行了常数次,这个常数是确定的、不随着n的变化而变化,比如数组的查找就是O(1)时间复杂度的,因为只需进行下标的计算然后根据下标找到目标元素即可。
绪论的内容大概是这么多,下一章我们将学习线性表。觉得有用的别忘记转发还有点个在看和赞噢!