绪论

102 阅读21分钟

introduction-mind

一、绪论

导言

人类文明发展阶段:

1.农业革命:学会农耕,从“动物”到“人类”。

2.工业革命:出现枪炮与机械,导致古文明灭亡。

3.信息革命:始于20世纪下半叶的信息革命,以计算机和互联网的普及为标志,彻底改变了人类获取、处理和传播信息的方式。(高度信息化的世界,导致?)

4.AI与智能革命:正在发生的AI革命,被认为是继农业革命、工业革命和信息革命之后的第四次革命。

数据结构在学什么?(why)

  • 将现实世界的问题转化为计算机可以处理的数据。
  • 如何用计算机高效地处理数据从而创造价值。

数据结构的基本概念

1.数据

什么是数据(data)?

  • 计算机专业人员用数据描述世界,数据艺术家。
  • 数据是计算机程序加工的原料。
  • 数据是信息的表现形式和载体,而信息则是这些数据背后所蕴含的意义和价值。

计算机处理的数据

  • 1.早期的计算机只用于处理纯数值型问题:计算弹道轨迹。
  • 2.现代计算机经常处理非数值型问题:文本处理、图像与视频处理。

数据可以根据多个维度进行分类,以下是几种常见的分类方式

  1. 按数据类型分类:

    • 数值数据:可以是整数或浮点数,如年龄、收入、温度等。
    • 文本数据:包括字母、数字和特殊字符的组合,如姓名、地址、文章等。
    • 日期时间数据:表示时间点或时间段的数据,如出生日期、会议时间等。
    • 二进制数据:图像、音频、视频等多媒体数据通常以二进制形式存储。
    • 布尔数据:只有真(True)或假(False)两种状态的数据。
  2. 按数据结构分类:

    • 结构化数据:具有固定格式或模式的数据,如数据库中的表格数据,每一行和每一列都有明确的含义。
    • 半结构化数据:没有预定义的数据模型,但包含标记或名称的分隔符,如XML和JSON文件。
    • 非结构化数据:没有预定义的数据结构,如电子邮件、社交媒体帖子、图像和视频。
  3. 按数据敏感性分类:

    • 公共数据:可以公开访问的数据,如天气预报、新闻报道等。
    • 敏感数据:包含个人身份信息、商业秘密或受法律保护的数据,如个人健康记录、客户名单、财务数据等。
  4. 按数据处理状态分类:

    • 原始数据:直接从数据源收集的数据,未经任何处理或清洗。
    • 清洗数据:经过初步处理,去除了错误、重复或无关数据的数据。
    • 分析数据:经过深度处理,可能包括统计分析、数据挖掘等,以揭示模式和趋势的数据。

2.数据元素

数据元素(data element)

  • 数据元素是数据的基本单位,可以是一个简单的值,如一个整数、一个字符,也可以是一个复合的数据结构,如一个记录或对象,其中包含了多个相关的数据项。

数据项(data item)

  • 一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位。
  • 组合项
  • 数据项在不同的领域有不同的别名,比如在数据库中通常被称为字段(Field),在面向对象编程中称为属性(Attribute)。

3.数据对象

数据对象(data object)

  • 数据对象是具有相同性质的数据元素的集合,是数据的一个子集。
  • 角色数据对象(例如,教师、学生)。

4.数据结构

数据结构(Data Structure)

  • 数据结构主要关注的是数据在计算机内存中的组织、管理以及存取方式。它涉及如何将数据元素(基本单元)通过某种关系结构化地组织起来,以便更高效地进行存储、检索、更新和操作。数据结构的选择和设计直接影响到算法的效率,以及程序执行时对内存的使用效率。
  • 数据结构是一门研究非数值计算的程序设计中计算机的操作对象以及它们之间的关系和操作的学科。

数据结构举例:

  • 线性数据结构:朴实无华排行榜。
  • 网状数据结构:枯燥朋友圈。

数据结构的三要素

1.逻辑结构

逻辑结构: 数据结构的逻辑结构指的是数据元素之间的逻辑关系,而不涉及它们在计算机中的实际存储方式。

逻辑结构主要分为四类:

  1. 集合(Set):集合中的数据元素之间不存在任何特定的逻辑关系,元素是独立的,集合中的元素不重复。
  2. 线性结构(Linear Structure):数据元素之间存在一对一的先后关系,可以看作是一条线,如数组、链表、栈和队列等。线性结构有两种存储方式:顺序存储和链式存储。
  3. 树形结构(Tree Structure):数据元素之间存在一对多的层次关系,每个元素可以有零个或多个子元素,但只有一个父元素(除了根节点),如二叉树、多叉树等。
  4. 图形结构(Graph Structure):数据元素之间存在多对多的复杂关系,每个元素可以与任意多个其他元素有关联,如无向图、有向图等。

logical structure-

线性结构与非线性结构:

1.线性结构:线性表、栈(特殊线性表)、队列(特殊线性表)、串、数组、广义表。

2.非线性数据结构可以进一步划分为树形结构和网状(图状)结构。

2.数据的运算

数据的运算: 数据的运算是针对逻辑结构的,指出运算的功能;运算的实现是针对存储结构的,指出运算的具体操作步骤。

数据的运算包括: 创(初始化)销、增删改查。

3.物理结构

物理结构: 物理结构反映了数据在计算机内存中的存储方式,可分为连续空间存储(数组)和分散空间存储(链表)。物理结构从底层决定了数据的访问、更新、增删等操作方法,两种物理结构在时间效率和空间效率方面呈现出互补的特点。

所有数据结构都是基于数组、链表或二者的组合实现的

  • 基于数组可实现:栈、队列、哈希表、树、堆、图、矩阵、张量(维度 ≥3 的数组)等。
  • 基于链表可实现:栈、队列、哈希表、树、堆、图等。

虚拟内存和物理内存

分配的虚拟内存是连续空间,而不是说虚拟映射的物理是连续空间。

存储结构主要有两种类型:

  1. 顺序存储结构(Sequential Storage):数据元素在内存中是连续存储的,即每个数据元素占用一块连续的存储空间。这种存储方式适用于数组、向量等数据结构,通过计算基地址和偏移量可以快速定位到任何元素。顺序存储结构的优点是访问速度快,缺点是插入和删除操作可能需要移动大量元素,效率较低。
  2. 链式存储结构(Linked Storage):数据元素在内存中不一定连续存储,每个数据元素由两部分组成,一部分是数据本身,另一部分是指向下一个数据元素的指针。链式存储结构适用于链表、树、图等数据结构,它的好处是插入和删除操作方便,只需改变指针即可,但访问元素时需要沿着链表依次查找,效率相对较低。

除了上述两种基本的物理结构,还有一些混合的存储方式,如索引存储、散列存储等,它们结合了顺序存储和链式存储的优点,以达到更高的存储效率和访问速度。以下是几种常见的混合存储方式:

  1. 索引存储(Indexed Storage):在顺序存储的基础上,为数据集创建一个索引表,索引表记录了数据元素的主键或关键信息以及指向数据元素的实际存储位置的指针。这种方式结合了顺序存储的快速访问优点和链式存储的灵活定位优点,尤其适用于大型数据集的快速查找。例如,数据库管理系统中常用索引存储来提高数据检索的效率。
  2. 散列存储(Hash Storage):使用散列函数将数据元素的关键字映射到一个固定大小的数组(散列表)中,每个数组元素称为桶。散列存储的目标是将关键字均匀分布在整个数组中,以实现接近O(1)的平均查找时间。虽然散列存储理论上可以提供极快的访问速度,但实际应用中需要处理散列冲突,即两个或多个关键字映射到同一个桶的情况。常见的冲突解决策略包括链地址法和开放定址法。
  3. B树存储(B-tree Storage):B树是一种自平衡的树数据结构,广泛应用于数据库和文件系统中。B树结合了链式存储和顺序存储的优点,支持高效的插入、删除和查找操作,即使在磁盘等块存储设备上也能保持良好的性能。B树的每个节点可以包含多个关键字和指针,这使得它能够在单个磁盘读写操作中访问多个数据元素,从而提高了磁盘I/O的效率。
  4. 跳跃表存储(Skip List Storage):跳跃表是一种基于链表的数据结构,通过在链表中添加额外的层级来加速查找过程。每个节点可以有多个指针,指向不同层级的后续节点。跳跃表结合了链表的灵活性和二叉搜索树的快速查找特性,同时避免了树的平衡维护开销,提供了平均O(log n)的查找时间。

4.总结

提示: 定义或者设计是逻辑上做的,要确定功能,具体的实现千差万别。但设计往往也要考虑到具体实现,不能空中楼阁。

three elements

数据类型

数据类型: 数据类型是一组值的集合以及可以在这些值上执行一组操作的总称。

  1. 值的集合(值域):这是指数据类型所允许的所有可能值的范围。例如,整型(int)数据类型可能包括所有从最小到最大整数值的集合,而在一些编程语言中,字符型(char)数据类型则定义了一组特定的字符集(如ASCII或Unicode字符集)。
  2. 一组操作:对于每个数据类型,都会定义一套操作或函数,这些操作能够应用于该类型的所有值上。这些操作定义了如何处理和操作这些值。例如,对于整型数据,常见的操作可能包括加法、减法、乘法、除法以及比较运算(如等于、大于、小于等)。字符型数据可能支持连接操作、比较操作等。

1.数据类型分类

数据类型可以大致分为以下几类:

  1. 基本数据类型(原子类型):这是最简单的数据类型,通常包括整型(如 int)、浮点型(如 float)、字符型(如 char)、布尔型(如 bool)等。每种基本数据类型都有固定的内存大小和范围。
  2. 复合数据类型:由一个或多个基本数据类型组合而成,包括数组、结构体、枚举、联合、类等。复合数据类型允许我们创建更复杂的数据结构,存储和操作不同类型的数据。

2.抽象数据类型

抽象数据类型(Abstract Data Type,ADT): 抽象数据类型是计算机科学中的一个核心概念,它是一种数据类型的数学模型,侧重于描述数据的逻辑结构以及在此结构上允许执行的操作,而独立于这些数据的底层实现细节。

ADT的作用

1.数学模型:ADT可以用数学模型来形式化描述,包括数据对象集和定义在其上的操作集。这种模型使得ADT的概念清晰且易于理解,同时也便于理论分析。

2.实现无关性:ADT的定义不绑定到任何特定的实现,这意味着可以用多种不同的方式来实现同一个ADT,只要这些实现都符合ADT规定的操作接口和行为规范。

ADT的组成部分

  1. 数据对象:定义了ADT中的数据元素,这些元素可以是基本数据类型,也可以是其他ADT。
  2. 数据关系:描述了数据元素之间的逻辑关系,例如在栈中,数据元素遵循后进先出(LIFO)的原则。
  3. 基本操作:定义了可以在数据对象上执行的一组操作,包括创建、查询、修改和销毁等。每个操作都有明确的输入和输出,以及操作的前置条件和后置条件。

ADT形式化定义

ADT 名称 {
    数据对象:数据元素的集合,
    数据关系:数据元素间的关系或约束条件,
    基本操作:
    1. 操作1(参数列表):操作描述及效果,
    2. 操作2(参数列表):操作描述及效果,
    ...
}
ADT Stack {
    数据对象:所有可能的栈元素S = {s | s 是任何有效的数据项},
    数据关系:栈是一个后进先出(Last In First Out, LIFO)的线性结构,没有显式的数据元素间关系,
    基本操作:
    1. push(item):向栈顶添加一个元素item,
    2. pop():移除并返回栈顶元素,若栈为空则返回特殊值(如null或抛出异常),
    3. top():返回栈顶元素但不移除,若栈为空则返回特殊值或抛出异常,
    4. isEmpty():判断栈是否为空,返回布尔值。
}

ADT

算法

算法(Algorithm): 算法是对特定问题求解步骤表示一个或多个操作。

数据结构、算法、程序

  • 尼古拉斯·沃斯,PASCAL语言,提出Algorithms + Data Structures = Programs(算法+数据结构=程式)。程序 = 数据结构(要处理的信息) + 算法(求解问题的步骤)。
  • 数据结构为算法提供了操作的平台,算法是基于数据结构设计的,用于实现对数据的各种操作和处理逻辑。
  • 程序则是将数据结构和算法结合起来,根据实际需求,通过编程语言实现的、可执行的具体解决方案。程序设计过程中,首先根据问题需求选择或设计合适的数据结构,然后根据这个数据结构的特点设计相应的算法,最终通过编码实现这些数据结构和算法,以构建完整的应用程序。

1.算法描述方式

常见的算法描述方式

  1. 自然语言:使用日常语言来描述算法的步骤,这种方式最直观,容易理解,但可能不够精确,容易产生歧义。
  2. 伪代码:介于自然语言和编程语言之间的一种描述方式,它使用类似于编程语言的结构和语法,但省略了语言的细节,如变量声明、数据类型等。伪代码更注重算法的逻辑流程,易于理解和转换为实际代码。
  3. 流程图:使用图形符号和箭头来表示算法的流程和控制结构,如分支、循环等。流程图直观易懂,适合描述较为复杂的逻辑结构,但可能不适合描述非常详细的算法步骤。
  4. 编程语言:直接使用如Python、Java、C++等编程语言来实现算法,这种方式最精确,可以直接编译或解释执行,但可能不如其他描述方式直观易懂。

2.算法的特性

算法的特性

  1. 输入(Input):算法可以接收零个或多个输入值。输入定义了算法处理的数据,这些数据可以是数字、符号、文件、对象等。
  2. 输出(Output):算法至少产生一个输出,这是算法处理的结果。输出可以是数值、数据结构、决策等,用于展示算法处理输入后得到的信息。
  3. 有穷性(Finiteness):算法必须能在有限步骤后终止,不能无休止地运行。这意味着算法不能陷入无限循环,每个执行实例都应在合理的时间内完成。注:算法必须是有穷的,而程序可以是无穷的。
  4. 确定性(Determinism):算法的每一步都必须有明确的定义,不存在二义性。对于相同的输入,算法应该每次都能产生相同的输出,即结果是可以预测的。
  5. 可行性。算法中描述的所有操作都需要能够通过现有的、已实现的基本运算在有限时间内执行完成。这意味着算法不应包含无法实现的抽象步骤,每一个步骤都应该是基础计算操作的组合,如算术运算、比较、数据结构的基本操作(如数组访问、链表遍历)等,这些操作都是计算机可以直接执行的。此外,可行性还隐含了算法的每一步操作都必须在合理的时间范围内是可计算的,即算法的实际执行必须是可行的,不仅仅是理论上的可能性。

3.“好”算法的特质

“好”算法的特质(设计算法时要尽量追求的目标)

  1. 正确性(Correctness):这是最基本的要求,算法必须能正确解决问题,对于所有合法的输入,都能产生预期的、正确的输出结果。
  2. 高效率(Efficiency):时间效率,空间效率。
  3. 可读性(Readability):算法易于理解和维护,代码清晰、结构良好,注释恰当,这有助于团队协作和后期的调试、维护工作。
  4. 健壮性(Robustness):对异常输入或边界条件处理得当,不会因为非法输入而崩溃或产生错误输出,能够给出错误提示或采取适当措施。
  5. 可扩展性(Scalability):随着问题规模的增长,算法性能下降的程度可控,即算法能在更大规模数据上仍然保持有效或通过合理增加资源使用维持效率。

4.算法效率的评估

效率评估方法

效率评估方法主要分为两种:实际测试、理论估算。

把数据量看做 x 值,算法计算 x 值所需时间或空间当做 y 值。计算出来的数据,就是一个横坐标的点(x,y)。把不同的点,连接起来,就会是一条曲线。

迭代与递归

调用栈

尾递归(tail recursion)

递归树(recursion tree)

时间复杂度

时间复杂度(time complexity): 时间复杂度分析统计的不是算法运行时间,而是算法运行时间随着数据量变大时的增长趋势。

运行平台和计算操作类型都与算法运行时间的增长趋势无关。

时间复杂度也存在一定的局限性。时间复杂度用于衡量算法运行时间随数据量增长的趋势,可以有效评估算法效率,但在某些情况下可能失效,如在输入的数据量较小或时间复杂度相同时,无法精确对比算法效率的优劣。

算法的性能问题只有在n很大时才会暴露出来。

函数渐近上界

时间复杂度分析本质上是计算“操作数量 T(n)”的渐近上界,它具有明确的数学定义。

时间复杂度推算方法

  1. 统计操作数量:(1)忽略常数项。(2)省略系数。
  2. 判断渐近上界f(n)

time-rule

常见类型

设输入数据大小为 n ,常见的时间复杂度类型(按照从低到高的顺序排列)为:

O(1)< O(log n) < O(n) < O(n log n) < O(n^2) < O(n^3) < O(2^n) < O(n!) <O(n^n)

常数阶<对数阶<线性阶<线性对数阶<平方阶<指数阶<阶乘阶

技巧:常对幂指阶。

遍历数组和遍历链表等操作的时间复杂度均为线性阶。

1.对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是仅次于常数阶的理想的时间复杂度。log2 n即 2^n的反函数。

2.主流排序算法的时间复杂度通常为O(n log n),例如快速排序、归并排序、堆排序等。

3.在实际算法中,指数阶常出现于递归函数中。

4.阶乘阶对应数学上的“全排列”问题。

算法的三种时间复杂度

  1. 算法的时间效率往往不是固定的,而是与输入数据的分布有关。
  2. “最差时间复杂度”对应函数渐近上界,使用大O记号表示。相应地,“最佳时间复杂度”对应函数渐近下界,用 Ω 记号表示。平均时间复杂度可以体现算法在随机输入数据下的运行效率,用 Θ 记号来表示。
  3. 对于较为复杂的算法,计算平均时间复杂度往往比较困难,因为很难分析出在数据分布下的整体数学期望。在这种情况下,我们通常使用最差时间复杂度作为算法效率的评判标准。
空间复杂度

空间复杂度(space complexity): 空间复杂度用于衡量算法占用内存空间随着数据量变大时的增长趋势。

存储密度 = (结点数据本身所占的存储量)/(结点结构所占的存储总量)

算法相关空间

算法在运行过程中使用的内存空间主要包括以下几种:

  1. 输入空间:

  2. 暂存空间:

    • 暂存数据
    • 栈帧空间
    • 栈帧空间
  3. 输出空间:

在分析一段程序的空间复杂度时,我们通常统计暂存数据、栈帧空间和输出数据三部分。

在迭代中,每轮函数执行完返回,空间不会产生累积。 而在递归中,未返回的的函数会随着“递”的过程不断累积,从而实际影响到空间复杂度。

空间复杂度推算方法

与时间复杂度不同的是,我们通常只关注最差空间复杂度

在递归函数中,需要注意统计栈帧空间。栈帧空间通常仅在递归函数中影响空间复杂度。

常见类型

常见的空间复杂度类型(从低到高排列)。

O(1) < O(log n) < O(n) < O(n^2) < O(2^n)

常数阶< 对数阶< 线性阶< 平方阶< 指数阶

  1. 线性阶 O(n)。递归函数产生的线性阶空间复杂度。
  2. 平方阶 。平方阶常见于矩阵和图,元素数量与n成平方关系。
  3. 指数阶 。指数阶常见于二叉树。
  4. 对数阶 。对数阶常见于分治算法。
权衡时间与空间

在大多数情况下,时间比空间更宝贵,因此“以空间换时间”通常是更常用的策略。当然,在数据量很大的情况下,控制空间复杂度也非常重要。