pull/944/head
krahets 2 years ago
parent 460143e878
commit d4622e0ecb

@ -6,7 +6,7 @@ comments: true
## 2.1.1.   算法评价维度 ## 2.1.1.   算法评价维度
在开始学习算法之前,我们首先需要明确算法的设计目标,换句话说,我们应该如何评判算法的优劣。从总体上看,算法设计追求以下两个层面的目标: 从总体上看,算法设计追求以下两个层面的目标:
1. **找到问题解法**。算法需要在规定的输入范围内,可靠地求得问题的正确解。 1. **找到问题解法**。算法需要在规定的输入范围内,可靠地求得问题的正确解。
2. **寻求最优解法**。同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。 2. **寻求最优解法**。同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。
@ -16,7 +16,7 @@ comments: true
- **时间效率**,即算法运行速度的快慢。 - **时间效率**,即算法运行速度的快慢。
- **空间效率**,即算法占用内存空间的大小。 - **空间效率**,即算法占用内存空间的大小。
数据结构与算法的终极目标是“又快又省”。了解如何评估算法效率非常重要,因为只有掌握了评价方法,我们才能进行算法间的对比分析,从而指导算法设计与优化。 简而言之,**我们的目标是设计“既快又省”的数据结构与算法**。掌握评估算法效率的方法则至关重要,因为只有了解评价标准,我们才能进行算法之间的对比分析,从而指导算法设计与优化过程
## 2.1.2.   效率评估方法 ## 2.1.2.   效率评估方法

@ -4,13 +4,13 @@ comments: true
# 3.2.   数据结构分类 # 3.2.   数据结构分类
数据结构主要可根据「逻辑结构」和「物理结构」两种角度进行分类。 数据结构可以从逻辑结构和物理结构两个维度进行分类。
## 3.2.1.   逻辑结构:线性与非线性 ## 3.2.1.   逻辑结构:线性与非线性
**「逻辑结构」反映了数据之间的逻辑关系**。数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。 **「逻辑结构」揭示了数据元素之间的逻辑关系**。在数组和链表中,数据按照顺序依次排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出祖先与后代之间的派生关系;图则由结点和边构成,反映了复杂的网络关系。
我们一般将逻辑结构分为「线性」和「非线性」两种。“线性”这个概念很直观,即表明数据在逻辑关系上是排成一条线的;而如果数据之间的逻辑关系是非线性的(例如是网状或树状的),那么就是非线性数据结构。 逻辑结构通常分为「线性」和「非线性」两类。线性结构比较直观,指数据在逻辑关系上呈线性排列;非线性结构则相反,呈非线性排列,例如网状或树状结构。
- **线性数据结构**:数组、链表、栈、队列、哈希表; - **线性数据结构**:数组、链表、栈、队列、哈希表;
- **非线性数据结构**:树、图、堆、哈希表; - **非线性数据结构**:树、图、堆、哈希表;
@ -23,21 +23,21 @@ comments: true
!!! note !!! note
若感到阅读困难,建议先看完下个章节「数组与链表」,再回过头来理解物理结构的含义。 如若阅读起来有困难,建议先阅读下一章“数组与链表”,然后再回头理解物理结构的含义。
**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储**。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性 **「物理结构」体现了数据在计算机内存中的存储方式**,可以分为数组的连续空间存储和链表的离散空间存储。物理结构从底层决定了数据的访问、更新、增删等操作方法,同时在时间效率和空间效率方面呈现出互补的特点
![连续空间存储与离散空间存储](classification_of_data_structure.assets/classification_phisical_structure.png) ![连续空间存储与离散空间存储](classification_of_data_structure.assets/classification_phisical_structure.png)
<p align="center"> Fig. 连续空间存储与离散空间存储 </p> <p align="center"> Fig. 连续空间存储与离散空间存储 </p>
**所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。 **所有数据结构都是基于数组、链表或二者的组合实现的**。例如,栈和队列既可以使用数组实现,也可以使用链表实现;而哈希表的实现可能同时包含数组和链表。
- **基于数组可实现**:栈、队列、哈希表、树、堆、图、矩阵、张量(维度 $\geq 3$ 的数组)等; - **基于数组可实现**:栈、队列、哈希表、树、堆、图、矩阵、张量(维度 $\geq 3$ 的数组)等;
- **基于链表可实现**:栈、队列、哈希表、树、堆、图等; - **基于链表可实现**:栈、队列、哈希表、树、堆、图等;
基于数组实现的数据结构也被称为「静态数据结构」,这意味着该数据结构在在被初始化后,长度不可变。相反地,基于链表实现的数据结构被称为「动态数据结构」,该数据结构在被初始化后,我们也可以在程序运行中修改其长度 基于数组实现的数据结构也被称为「静态数据结构」,这意味着此类数据结构在初始化后长度不可变。相对应地,基于链表实现的数据结构被称为「动态数据结构」,这类数据结构在初始化后,仍可以在程序运行过程中对其长度进行调整
!!! tip !!! tip
数组与链表是其他所有数据结构的“底层积木”,建议读者一定要多花些时间了解 数组与链表是其他所有数据结构的“底层积木”,建议读者投入更多时间深入了解这两种基本数据结构

@ -6,14 +6,14 @@ comments: true
## 3.1.1. &nbsp; 基本数据类型 ## 3.1.1. &nbsp; 基本数据类型
到计算机中的数据我们能够想到文本、图片、视频、语音、3D 模型等等,这些数据虽然组织形式不同,但都是由各种基本数据类型构成的 及计算机中的数据我们会想到文本、图片、视频、语音、3D 模型等各种形式。尽管这些数据的组织形式各异,但它们都由各种基本数据类型构成
**「基本数据类型」是 CPU 可以直接进行运算的类型,在算法中直接被使用**。 **「基本数据类型」是 CPU 可以直接进行运算的类型,在算法中直接被使用**。
- 「整数」根据不同的长度分为 byte, short, int, long ,根据算法需求选用,即在满足取值范围的情况下尽量减小内存空间占用; - 「整数」按照不同的长度分为 byte, short, int, long 。在满足取值范围的前提下,我们应该尽量选取较短的整数类型,以减小内存空间占用;
- 「浮点数」代表小数,根据长度分为 float, double ,同样根据算法的实际需求选用; - 「浮点数」表示小数,按长度分为 float, double ,选用规则与整数相同。
- 「字符」在计算机中以字符集形式保存char 的值实际上是数字,代表字符集中的编号,计算机通过字符集查表完成编号到字符的转换。占用空间通常为 2 bytes 或 1 byte - 「字符」在计算机中以字符集形式保存char 的值实际上是数字,代表字符集中的编号,计算机通过字符集查表完成编号到字符的转换。
- 「布尔」代表逻辑中的“是”与“否”,其占用空间需根据编程语言确定,通常为 1 byte 或 1 bit - 「布尔」代表逻辑中的“是”与“否”,其占用空间需根据编程语言确定
<div class="center-table" markdown> <div class="center-table" markdown>
@ -30,36 +30,34 @@ comments: true
</div> </div>
!!! tip 以上表格中,加粗项在算法题中最为常用。此表格无需硬背,大致理解即可,需要时可以通过查表来回忆。
以上表格中,加粗项在「算法题」中最为常用。此表格无需硬背,大致理解即可,需要时可以通过查表来回忆。
### 整数表示方式 ### 整数表示方式
整数的取值范围取决于变量使用的内存长度,即字节(或比特)数。在计算机中, 1 字节 (byte) = 8 比特 (bit) 1 比特即 1 个二进制位。以 int 类型为例: 整数的取值范围取决于变量使用的内存长度即字节或比特数。在计算机中1 字节 (byte) = 8 比特 (bit)1 比特即 1 个二进制位。以 int 类型为例:
1. 整数类型 int 占用 4 bytes = 32 bits 因此可以表示 $2^{32}$ 个不同的数字; 1. 整数类型 int 占用 4 bytes = 32 bits ,可以表示 $2^{32}$ 个不同的数字;
2. 将最高位看作符号位,$0$ 代表正数,$1$ 代表负数,从而可以表示 $2^{31}$ 个正数和 $2^{31}$ 个负数; 2. 将最高位视为符号位,$0$ 代表正数,$1$ 代表负数,一共可表示 $2^{31}$ 个正数和 $2^{31}$ 个负数;
3. 当所有 bits 为 0 时代表数字 $0$ ,从零开始增大,可得最大正数为 $2^{31} - 1$ 3. 当所有 bits 为 0 时代表数字 $0$ ,从零开始增大,可得最大正数为 $2^{31} - 1$
4. 剩余 $2^{31}$ 个数字全部用来表示负数,因此最小负数为 $-2^{31}$ ;具体细节涉及到到“源码、反码、补码”知识,有兴趣的同学可以查阅学习; 4. 剩余 $2^{31}$ 个数字全部用来表示负数,因此最小负数为 $-2^{31}$ ;具体细节涉及“源码、反码、补码”的相关知识,有兴趣的同学可以查阅学习;
其它整数类型 byte, short, long 取值范围的计算方法与 int 类似,在此不再赘述。 其它整数类型 byte, short, long 取值范围的计算方法与 int 类似,在此不再赘述。
### 浮点数表示方式 * ### 浮点数表示方式 *
!!! note !!! note
在本书中,标题后的 `*` 符号代表选读章节,如果你觉得理解困难,建议先跳过,等学完必读章节后再单独攻克。 本书中,标题后的 * 符号代表选读章节。如果你觉得理解困难,建议先跳过,等学完必读章节后再单独攻克。
细心的你可能会疑惑: int 和 float 长度相同,都是 4 bytes **但为什么 float 的取值范围远大于 int** ?按说 float 需要表示小数,取值范围应该变小才对。 细心的你可能会发现int 和 float 长度相同,都是 4 bytes但为什么 float 的取值范围远大于 int ?按理说 float 需要表示小数,取值范围应该变小才对。
实,这是因为浮点数 float 采用了不同的表示方式。IEEE 754 标准规定32-bit 长度的 float 由以下部分构成: 际上,这是因为浮点数 float 采用了不同的表示方式。根据 IEEE 754 标准32-bit 长度的 float 由以下部分构成:
- 符号位 $\mathrm{S}$ :占 1 bit - 符号位 $\mathrm{S}$ :占 1 bit
- 指数位 $\mathrm{E}$ :占 8 bits - 指数位 $\mathrm{E}$ :占 8 bits
- 分数位 $\mathrm{N}$ :占 24 bits ,其中 23 位显式存储; - 分数位 $\mathrm{N}$ :占 24 bits ,其中 23 位显式存储;
设 32-bit 二进制数的第 $i$ 位为 $b_i$ ,则 float 值的计算方法定义为 设 32-bit 二进制数的第 $i$ 位为 $b_i$,则 float 值的计算方法定义为
$$ $$
\text { val } = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 \text { val } = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2
@ -90,9 +88,9 @@ $$
\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 \text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
$$ $$
现在我们可以回答开始的问题:**float 的表示方式包含指数位,导致其取值范围远大于 int** 。根据以上计算, float 可表示的最大正数为 $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ,切换符号位便可得到最小负数。 现在我们可以回答最初的问题:**float 的表示方式包含指数位,导致其取值范围远大于 int** 。根据以上计算float 可表示的最大正数为 $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ,切换符号位便可得到最小负数。
**浮点数 float 虽然拓展了取值范围,但副作用是牺牲了精度**。整数类型 int 将全部 32 位用于表示数字,数字是均匀分布的;而由于指数位的存在,浮点数 float 的数值越大,相邻两个数字之间的差值就会趋向越大。 **尽管浮点数 float 扩展了取值范围,但其副作用是牺牲了精度**。整数类型 int 将全部 32 位用于表示数字,数字是均匀分布的;而由于指数位的存在,浮点数 float 的数值越大,相邻两个数字之间的差值就会趋向越大。
进一步地,指数位 $E = 0$ 和 $E = 255$ 具有特殊含义,**用于表示零、无穷大、$\mathrm{NaN}$ 等**。 进一步地,指数位 $E = 0$ 和 $E = 255$ 具有特殊含义,**用于表示零、无穷大、$\mathrm{NaN}$ 等**。
@ -106,18 +104,16 @@ $$
</div> </div>
特别地,次正规数显著提升了小数精度 特别地,次正规数显著提升了浮点数的精度,这是因为
- 最小正正规数为 $2^{-126} \approx 1.18 \times 10^{-38}$ - 最小正正规数为 $2^{-126} \approx 1.18 \times 10^{-38}$
- 最小正次正规数为 $2^{-126} \times 2^{-23} \approx 1.4 \times 10^{-45}$ - 最小正次正规数为 $2^{-126} \times 2^{-23} \approx 1.4 \times 10^{-45}$
双精度 double 也采用类似 float 的表示方法,在此不再赘述。 双精度 double 也采用类似 float 的表示方法,此处不再详述。
### 基本数据类型与数据结构的关系 ### 基本数据类型与数据结构的关系
我们知道,**数据结构是在计算机中组织与存储数据的方式**,它的主语是“结构”,而不是“数据”。如果我们想要表示“一排数字”,自然想到使用「数组」数据结构。数组的存储方式可以表示数字的相邻关系、顺序关系,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char **则与所谓的数据的结构无关了**。 我们知道,**数据结构是在计算机中组织与存储数据的方式**,它的核心是“结构”,而非“数据”。如果想要表示“一排数字”,我们自然会想到使用「数组」数据结构。数组的存储方式可以表示数字的相邻关系、顺序关系,但至于具体存储的是整数 int 、小数 float 、还是字符 char ,则与“数据结构”无关。换句话说,基本数据类型提供了数据的“内容类型”,而数据结构提供了数据的“组织方式”。
换言之,基本数据类型提供了数据的“内容类型”,而数据结构提供数据的“组织方式”。
=== "Java" === "Java"
@ -212,12 +208,12 @@ $$
在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度较快,但容量较小(通常为 GB 级别)。 在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度较快,但容量较小(通常为 GB 级别)。
**算法运行中,相关数据都存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。 **算法运行过程中,相关数据都存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。
**系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。 **系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则为表格中的每个单元格分配编号,确保每个内存空间都有唯一的内存地址。有了这些地址,程序便可以访问内存中的数据。
![内存条、内存空间、内存地址](data_and_memory.assets/computer_memory_location.png) ![内存条、内存空间、内存地址](data_and_memory.assets/computer_memory_location.png)
<p align="center"> Fig. 内存条、内存空间、内存地址 </p> <p align="center"> Fig. 内存条、内存空间、内存地址 </p>
**内存资源是设计数据结构与算法的重要考虑因素**。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。 **在数据结构与算法的设计中,内存资源是一个重要的考虑因素**。内存是所有程序的共享资源,当内存被某个程序占用时,其他程序无法同时使用。我们需要根据剩余内存资源的实际情况来设计算法。例如,算法所占用的内存峰值不应超过系统剩余空闲内存;如果运行的程序很多并且缺少大量连续的内存空间,那么所选用的数据结构必须能够存储在离散的内存空间内。

@ -4,8 +4,8 @@ comments: true
# 3.3. &nbsp; 小结 # 3.3. &nbsp; 小结
- 整数 byte, short, int, long 、浮点数 float, double 、字符 char 、布尔 boolean 是计算机中的基本数据类型,占用空间的大小决定了它们的取值范围 - 计算机中的基本数据类型包括整数 byte, short, int, long 、浮点数 float, double 、字符 char 和布尔 boolean ,它们的取值范围取决于占用空间大小和表示方式
- 在程序运行时,数据存储在计算机的内存中。内存中每块空间都有独立的内存地址,程序是通过内存地址来访问数据的 - 当程序运行时,数据被存储在计算机内存中。每个内存空间都拥有对应的内存地址,程序通过这些内存地址访问数据
- 数据结构主要可以从逻辑结构和物理结构两个角度进行分类。逻辑结构反映了数据中元素之间的逻辑关系,物理结构反映了数据在计算机内存中的存储形式。 - 数据结构可以从逻辑结构和物理结构两个角度进行分类。逻辑结构描述了数据元素之间的逻辑关系,而物理结构描述了数据在计算机内存中的存储方式。
- 常见的逻辑结构有线性、树状、网状等。我们一般根据逻辑结构将数据结构分为线性(数组、链表、栈、队列)和非线性(树、图、堆)两种。根据实现方式的不同,哈希表可能是线性或非线性 - 常见的逻辑结构包括线性、树状和网状等。通常我们根据逻辑结构将数据结构分为线性(数组、链表、栈、队列)和非线性(树、图、堆)两种。哈希表的实现可能同时包含线性和非线性结构
- 物理结构主要有两种,分别是连续空间存储(数组)和离散空间存储(链表),所有的数据结构都是由数组、或链表、或两者组合实现的。 - 物理结构主要分为连续空间存储(数组)和离散空间存储(链表)。所有数据结构都是由数组、链表或两者的组合实现的。

@ -74,11 +74,11 @@ hide:
<h2 align="center"></h2> <h2 align="center"></h2>
两年前,我在力扣上分享了《剑指 Offer》系列题解受到了很多小伙伴的喜爱与支持。在此期间,我也回复了许多读者的评论问题,遇到最多的问题是“如何入门学习算法”。我渐渐也对这个问题好奇了起来 两年前,我在力扣上分享了《剑指 Offer》系列题解受到了许多朋友的喜爱与支持。在此期间,我回答了众多读者的评论问题,其中最常见的一个问题是“如何入门学习算法”。我逐渐也对这个问题产生了浓厚的兴趣
两眼一抹黑地刷题应该是最受欢迎的方式,简单粗暴且有效。然而,刷题就如同玩“扫雷”游戏,自学能力强的同学能够顺利地将地雷逐个排掉,而基础不足的同学很可能被炸的满头是包,并在受挫中步步退缩。通读教材书籍也是常用方法,但对于面向求职的同学来说,毕业季、投递简历、应付笔面试已经占用大部分精力,厚重的书本也因此成为巨大的挑战。 两眼一抹黑地刷题似乎是最受欢迎的方法,简单直接且有效。然而,刷题就如同玩“扫雷”游戏,自学能力强的同学能够顺利地将地雷逐个排掉,而基础不足的同学很可能被炸的满头是包,并在挫折中步步退缩。通读教材书籍也是一种常见做法,但对于面向求职的同学来说,毕业季、投递简历、准备笔试面试已经占据了大部分精力,厚重的书籍往往变成了一项艰巨的挑战。
如果你也有上述烦恼,那么很幸运这本书找到了你。本书是我对于该问题给出的答案,虽然不一定正确,但至少代表一次积极的尝试。这本书虽然不足以让你直接拿到 Offer ,但会引导你探索数据结构与算法的“知识地图”,带你了解不同“地雷”的形状大小和分布位置,让你掌握各种“排雷方法”。有了这些本领,相信你可以更加得心应手地刷题与阅读文献,逐步搭建起完整的知识体系。 如果你也面临类似的困扰,那么很幸运这本书找到了你。本书是我对此问题的给出的答案,虽然不一定正确,但至少是一次积极的尝试。这本书虽然不足以让你直接拿到 Offer ,但会引导你探索数据结构与算法的“知识地图”,带你了解不同“地雷”的形状大小和分布位置,让你掌握各种“排雷方法”。有了这些本领,相信你可以更加自如地应对刷题和阅读文献,逐步构建起完整的知识体系。
<h3 align="left"> 作者简介 </h3> <h3 align="left"> 作者简介 </h3>
@ -88,7 +88,7 @@ hide:
<h2 align="center"> 致谢 </h2> <h2 align="center"> 致谢 </h2>
本书在开源社区的群策群力下逐步成长,感谢每一位撰稿人,是他们的无私奉献让这本书变得更好,他们是(按照 GitHub 自动生成的顺序): 本书在开源社区众多贡献者的共同努力下不断成长。感谢每一位投入时间与精力的撰稿人,是他们无私奉献使这本书越变越好,他们是(按照 GitHub 自动生成的顺序):
<p align="center"> <p align="center">
<a href="https://github.com/krahets/hello-algo/graphs/contributors"> <a href="https://github.com/krahets/hello-algo/graphs/contributors">

Loading…
Cancel
Save