首先,我们要明白一个程序与参与它的算法和数据结构有如下关系:
算法,顾名思义就是计算机在解决问题时用到的方法,或者说人为地提供给计算机的一种思路。而数据结构呢?笔者认为,可以从中分开,分为“数据”和“结构。“数据结构”即特定的结构包含着的数据。因此,当计算机要处理这些数据时运用了特定的算法,就构成了一个程序。
那么程序的优劣就是处理数据算法的优劣,而如何探求时间短、占用空间小、优秀的算法,则是我们学习的目标。
01.01.01数据结构与算法
1.数据结构
数据结构(data structure):一种或多种特定关系的数据元素的集合
学习数据结构,我们能理解算法中思路的具体实现形式,并了解要处理数据的组织和存储形式。
因此我们将从数据的逻辑结构和数据的物理结构两方面来学习。
1.1数据的逻辑结构
数据的逻辑结构,换句话说,即结构中的数据互相之间存在的关系(或者是从操作对象中抽象而出的数学模型)。由数据之间的关系我们的算法才有着可明之处,程序才有着运行的基础。
我们通常把逻辑结构分为以下四种:
1.集合结构
集合结构:数据元素同属于一个集合,除此之外无其他关系。
就像数学中的集合,集合结构中的数据是无序的、唯一的。如图所示,极为松散,因此,也可用其他的结构来表示集合结构。
2.线性结构
线性结构:结构中的数据元素之间存在一对一的关系。
线性结构中的数据就像一条线,或者说就像几个人首尾不相连的拉手一样。除了第一个和最后一个,每个人的左手和右手都拉着一个人,并且在这一列中每个人都有自己的位置和顺序。
线性结构类型包括:数组、链表,以及由它们衍生出来的栈、队列、哈希表。
3.树形结构
树形结构:结构中的数据元素之间存在一对多的关系。
这是图论中提到的一种结构。最上面的节点1可以看作“根”,而节点2和节点3则可以看作左子树和右子树。当然左子树和右子树由有各自的子树。
最简单的树形结构是二叉树,如下图所示:
(引用自:二叉树!!!数据结构与算法 看完这篇文终于搞明白了 - 知乎 (zhihu.com))
4.图形结构
图形结构(或网状结构):结构中的数据元素之间存在多对多的关系。
图形结构,和图论中的图有异曲同工之处。如果把每个数据看作一个结点,那么基于某种关系,当两节点间连线时,就表明这两者满足这个关系。当一个节点和多个节点满足这个关系时就可用多条线连接起来。(比如图中的节点2和节点1、4、5、6满足关系)
图形结构类型包括:无向图、有向图、连通图等。
1.2数据的物理结构
物理结构(Physical Structure):数据的逻辑结构在计算机中的表示。
物理结构又称存储结构。在计算机中常用到顺序存储结构、链式存储结构。
1.顺序存储结构
顺序存储结构(Sequential Storage Structure):将数据元素存放在一片地址连续的存储单元里,数据元素之间的逻辑关系通过数据元素的存储地址来直接反映。
顺序存储结构可以看作是线性结构在物理上的具体实现。它的特点也大致相同即:逻辑上相连的数据在存储时也必然相联。也可以联想为一列轨道上的火车,火车有一个火车头,当然也有火车尾,中间载客车厢也是顺序连接起来。
优点是:实际占用的空间最少,可以和线性结构对照理解。
缺点是:需要在内存里开辟一片地址连续的存储空间并且要实现进行。在进行数据操作时的灵活性也较低。
2.链式存储结构
链式存储结构(Linked Storage Structure):将数据元素存放在任意的存储单元里,存储单元可以连续,也可以不连续。
链式存储结构中,一般将每个数据元素占用的若干单元的组合称为一个链结点。每个链结点不仅要存放一个数据元素的数据信息,还要存放一个指出这个数据元素在逻辑关系的直接后继元素所在链结点的地址,该地址被称为指针。各数据结构依靠指针来相互连接。每个数据元素在物理地址上的位置是不固定的,数据元素的地址可以很随机,但是每个链结点之间一定会有一个指针来指向下一个(尾结点——>null)
优点是:存储空间不必提前分配;可以申请空间避免空间浪费;进行数据操作时的灵活性也比较强。
缺点是:数据本身以及指针都要占用存储空间,占用空间比顺序存储结构更大。
2.算法
算法(Algorithm):解决特定问题求解步骤的准确而完整的描述,在计算机中表现为一系列指令的集合,算法代表着用系统的方法描述解决问题的策略机制。
简单地说,算法就是解决问题的方法。
但是详细地说,算法是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作。可以使用自然语言、编程语言、伪代码、流程图描述。
2.1算法的基本特性
- 输入:对于待解决的问题,都要以某种方式交给对应的算法。在算法开始之前最初赋给算法的参数称为输入。一个算法可以有多个输入,也可以没有输入。
- 输出:算法是为了解决问题存在的,最终总需要返回一个结果。所以至少需要一个或多个参数作为算法的输出。
- 有穷性:算法必须在有限的步骤内结束,并且应该在一个可接受的时间内完成。比如示例 1,如果我们选择五一从上海到北京去旅游,结果五一纠结了三天也没决定好怎么去北京,那么这个旅游计划也就泡汤了,这个算法自然也是不合理的。
- 确定性:组成算法的每一条指令必须有着清晰明确的含义,不能令读者在理解时产生二义性或者多义性。就是说,算法的每一个步骤都必须准确定义而无歧义。
- 可行性:算法的每一步操作必须具有可执行性,在当前环境条件下可以通过有限次运算实现。也就是说,每一步都能通过执行有限次数完成,并且可以转换为程序在计算机上运行并得到正确的结果。
2.2算法追求的目标
当我们设计一个算法来解决问题时应做到以下三点的特性:
1.正确性。这是算法设计出来的终极目标,算法的设计应该能解决问题的需求,并且不出错误。
2.可读性:算法设计出来后,还要考虑可读性,可读性良好是一个算法被他人以及过了一段时间后回顾理解的基础。应当有适当的注释以及保持良好的命名规则。
3.健壮性:简单来说,就是算法对非常规数据以及非法数据的应对能力,要有比较好的反应和处理。
一个好的算法并不是占用空间大而时间短,也不是时间长而占用空间小。而是各占所长。一个好的算法应要:1.所需运行时间少;2.占用空间小。这是我们评判一个算法是否优秀的标准。我们在设计算法时,应当从这两方面去考虑,当两者不可兼得时,也可考虑这种方案,设计一种合适的算法。
01.01.02算法复杂度
算法复杂度(Algorithm complexity):在问题的输入规模为n的条件下,程序的时间使用情况和空间使用情况。
简单来说算法的复杂度可以分为时间复杂度(渐近时间复杂度)以及空间复杂度。我们在判定一个算法的优劣时常常比较算法复杂度,也就是比较上面提过的运行时间是否少,占用空间是否小。
我们通常运用两个方法比较事后统计、预先估算。
事后统计:即先把该算法所编成的程序执行一遍,统计所用的时间以及空间,再和别的算法进行比较
预先估算:即在算法设计时,根据算法的步骤、结构以及输入的数据规模大小,来估算算法的复杂度
接下来,介绍时间复杂度和空间复杂度
2.时间复杂度
时间复杂度:一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作 T(n) = O(f(n))
它表示随着规模n的变大,算法执行时间的增长率和f(n)的增长率相同,其中O是一种渐近符号。
相关的的渐进 Θ 渐进紧确界符号、O渐进上界符号、Ω 渐进下界符号。可以看看这位作者,写的很详细(渐进符号详解 - 知乎 (zhihu.com))
2.1时间复杂度的计算
一般计算时间复杂度有三大步、两原则
三大步:
-找出算法中的基本操作(基本语句)
-计算基本语句执行次数的数量级
-用O表示法表示时间复杂度
两原则:
-加法原则:总的时间复杂度等于量级最大的基本语句的时间复杂度
-乘法原则:循坏嵌套代码的复杂度等于嵌套内外基本语句的时间复杂度乘积
3.空间复杂度
空间复杂度:算法所需存储空间的量度记作 S(n) = O(f(n))
其中,n为问题的规模。它表示的是随着问题规模的增大算法所占空间的增长趋势跟f(n)相同
相对与计算时间复杂度,空间复杂度更容易计算。
总结
本文主要介绍了算法与数字结构中的基础内容。先从程序与算法、数据结构的关系入题,点明三者之间的关系,然后阐明学习的目标。然后开始进入数据结构的学习,学习了四种数据的逻辑结构(集合、线性、树形、图形)和两种数据的物理结构(顺序、链式)。
然后介绍了算法的概念,算法有四个特性(有穷性、确定性、可行性、输入、输出),当设计一个算法时应该注意:正确性、可读性、健壮性。
最后如何判定一个算法是否优秀,主要从所需运行时间少、占用空间小这两方面进行评估。
参考与引用的资料:
二叉树!!!数据结构与算法 看完这篇文终于搞明白了 - 知乎 (zhihu.com)
LeetCode 算法笔记——Datawhale01.01.01 数据结构与算法(第 01 ~ 02 天) (datawhalechina.github.io)
数据结构(C语言版)——严蔚敏、吴伟民
离散数学引论——王义和