mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-11 15:06:07 +00:00
deploy
This commit is contained in:
@@ -3456,7 +3456,7 @@
|
||||
<h1 id="34">3.4 字符编码 *<a class="headerlink" href="#34" title="Permanent link">¶</a></h1>
|
||||
<p>在计算机中,所有数据都是以二进制数的形式存储的,字符 <code>char</code> 也不例外。为了表示字符,我们需要建立一套“字符集”,规定每个字符和二进制数之间的一一对应关系。有了字符集之后,计算机就可以通过查表完成二进制数到字符的转换。</p>
|
||||
<h2 id="341-ascii">3.4.1 ASCII 字符集<a class="headerlink" href="#341-ascii" title="Permanent link">¶</a></h2>
|
||||
<p>「ASCII 码」是最早出现的字符集,全称为“美国标准信息交换代码”。它使用 7 位二进制数(即一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的字符。这包括英文字母的大小写、数字 0-9 、一些标点符号,以及一些控制字符(如换行符和制表符)。</p>
|
||||
<p>「ASCII 码」是最早出现的字符集,全称为“美国标准信息交换代码”。它使用 7 位二进制数(即一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的字符。如下图所示,ASCII 码包括英文字母的大小写、数字 0-9 、一些标点符号,以及一些控制字符(如换行符和制表符)。</p>
|
||||
<p><img alt="ASCII 码" src="../character_encoding.assets/ascii_table.png" /></p>
|
||||
<p align="center"> 图:ASCII 码 </p>
|
||||
|
||||
|
||||
@@ -3415,7 +3415,7 @@
|
||||
<p>常见的数据结构包括数组、链表、栈、队列、哈希表、树、堆、图,它们可以从“逻辑结构”和“物理结构”两个维度进行分类。</p>
|
||||
<h2 id="311">3.1.1 逻辑结构:线性与非线性<a class="headerlink" href="#311" title="Permanent link">¶</a></h2>
|
||||
<p><strong>逻辑结构揭示了数据元素之间的逻辑关系</strong>。在数组和链表中,数据按照顺序依次排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出祖先与后代之间的派生关系;图则由节点和边构成,反映了复杂的网络关系。</p>
|
||||
<p>逻辑结构可被分为“线性”和“非线性”两大类。线性结构比较直观,指数据在逻辑关系上呈线性排列;非线性结构则相反,呈非线性排列。</p>
|
||||
<p>如下图所示,逻辑结构可被分为“线性”和“非线性”两大类。线性结构比较直观,指数据在逻辑关系上呈线性排列;非线性结构则相反,呈非线性排列。</p>
|
||||
<ul>
|
||||
<li><strong>线性数据结构</strong>:数组、链表、栈、队列、哈希表。</li>
|
||||
<li><strong>非线性数据结构</strong>:树、堆、图、哈希表。</li>
|
||||
@@ -3432,12 +3432,12 @@
|
||||
<h2 id="312">3.1.2 物理结构:连续与离散<a class="headerlink" href="#312" title="Permanent link">¶</a></h2>
|
||||
<p>在计算机中,内存和硬盘是两种主要的存储硬件设备。硬盘主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。内存用于运行程序时暂存数据,速度较快,但容量较小(通常为 GB 级别)。</p>
|
||||
<p><strong>在算法运行过程中,相关数据都存储在内存中</strong>。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储一定大小的数据,在算法运行时,所有数据都被存储在这些单元格中。</p>
|
||||
<p><strong>系统通过内存地址来访问目标位置的数据</strong>。计算机根据特定规则为表格中的每个单元格分配编号,确保每个内存空间都有唯一的内存地址。有了这些地址,程序便可以访问内存中的数据。</p>
|
||||
<p><strong>系统通过内存地址来访问目标位置的数据</strong>。如下图所示,计算机根据特定规则为表格中的每个单元格分配编号,确保每个内存空间都有唯一的内存地址。有了这些地址,程序便可以访问内存中的数据。</p>
|
||||
<p><img alt="内存条、内存空间、内存地址" src="../classification_of_data_structure.assets/computer_memory_location.png" /></p>
|
||||
<p align="center"> 图:内存条、内存空间、内存地址 </p>
|
||||
|
||||
<p>内存是所有程序的共享资源,当某块内存被某个程序占用时,则无法被其他程序同时使用了。<strong>因此在数据结构与算法的设计中,内存资源是一个重要的考虑因素</strong>。比如,算法所占用的内存峰值不应超过系统剩余空闲内存;如果缺少连续大块的内存空间,那么所选用的数据结构必须能够存储在离散的内存空间内。</p>
|
||||
<p><strong>物理结构反映了数据在计算机内存中的存储方式</strong>,可分为连续空间存储(数组)和离散空间存储(链表)。物理结构从底层决定了数据的访问、更新、增删等操作方法,同时在时间效率和空间效率方面呈现出互补的特点。</p>
|
||||
<p>如下图所示,<strong>物理结构反映了数据在计算机内存中的存储方式</strong>,可分为连续空间存储(数组)和离散空间存储(链表)。物理结构从底层决定了数据的访问、更新、增删等操作方法,同时在时间效率和空间效率方面呈现出互补的特点。</p>
|
||||
<p><img alt="连续空间存储与离散空间存储" src="../classification_of_data_structure.assets/classification_phisical_structure.png" /></p>
|
||||
<p align="center"> 图:连续空间存储与离散空间存储 </p>
|
||||
|
||||
|
||||
@@ -3418,12 +3418,13 @@
|
||||
</div>
|
||||
<h2 id="331">3.3.1 原码、反码和补码<a class="headerlink" href="#331" title="Permanent link">¶</a></h2>
|
||||
<p>在上一节的表格中我们发现,所有整数类型能够表示的负数都比正数多一个,例如 <code>byte</code> 的取值范围是 <span class="arithmatex">\([-128, 127]\)</span> 。这个现象比较反直觉,它的内在原因涉及到原码、反码、补码的相关知识。</p>
|
||||
<p>实际上,<strong>数字是以“补码”的形式存储在计算机中的</strong>。在分析这样做的原因之前,我们首先给出三者的定义:</p>
|
||||
<p>首先需要指出,<strong>数字是以“补码”的形式存储在计算机中的</strong>。在分析这样做的原因之前,我们首先给出三者的定义:</p>
|
||||
<ul>
|
||||
<li><strong>原码</strong>:我们将数字的二进制表示的最高位视为符号位,其中 <span class="arithmatex">\(0\)</span> 表示正数,<span class="arithmatex">\(1\)</span> 表示负数,其余位表示数字的值。</li>
|
||||
<li><strong>反码</strong>:正数的反码与其原码相同,负数的反码是对其原码除符号位外的所有位取反。</li>
|
||||
<li><strong>补码</strong>:正数的补码与其原码相同,负数的补码是在其反码的基础上加 <span class="arithmatex">\(1\)</span> 。</li>
|
||||
</ul>
|
||||
<p>下图展示了原吗、反码和补码之间的转换方法。</p>
|
||||
<p><img alt="原码、反码与补码之间的相互转换" src="../number_encoding.assets/1s_2s_complement.png" /></p>
|
||||
<p align="center"> 图:原码、反码与补码之间的相互转换 </p>
|
||||
|
||||
@@ -3431,9 +3432,9 @@
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
& 1 + (-2) \newline
|
||||
& = 0000 \space 0001 + 1000 \space 0010 \newline
|
||||
& \rightarrow 0000 \space 0001 + 1000 \space 0010 \newline
|
||||
& = 1000 \space 0011 \newline
|
||||
& = -3
|
||||
& \rightarrow -3
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p>为了解决此问题,计算机引入了「反码 1's complement code」。如果我们先将原码转换为反码,并在反码下计算 <span class="arithmatex">\(1 + (-2)\)</span> ,最后将结果从反码转化回原码,则可得到正确结果 <span class="arithmatex">\(-1\)</span> 。</p>
|
||||
@@ -3450,14 +3451,14 @@
|
||||
<p>另一方面,<strong>数字零的原码有 <span class="arithmatex">\(+0\)</span> 和 <span class="arithmatex">\(-0\)</span> 两种表示方式</strong>。这意味着数字零对应着两个不同的二进制编码,其可能会带来歧义。比如在条件判断中,如果没有区分正零和负零,则可能会导致判断结果出错。而如果我们想要处理正零和负零歧义,则需要引入额外的判断操作,其可能会降低计算机的运算效率。</p>
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
+0 & = 0000 \space 0000 \newline
|
||||
-0 & = 1000 \space 0000
|
||||
+0 & \rightarrow 0000 \space 0000 \newline
|
||||
-0 & \rightarrow 1000 \space 0000
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p>与原码一样,反码也存在正负零歧义问题,因此计算机进一步引入了「补码 2's complement code」。我们先来观察一下负零的原码、反码、补码的转换过程:</p>
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
-0 = \space & 1000 \space 0000 \space \text{(原码)} \newline
|
||||
-0 \rightarrow \space & 1000 \space 0000 \space \text{(原码)} \newline
|
||||
= \space & 1111 \space 1111 \space \text{(反码)} \newline
|
||||
= 1 \space & 0000 \space 0000 \space \text{(补码)} \newline
|
||||
\end{aligned}
|
||||
@@ -3506,16 +3507,16 @@ b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0
|
||||
(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}]
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p><img alt="IEEE 754 标准下的 float 表示方式" src="../number_encoding.assets/ieee_754_float.png" /></p>
|
||||
<p align="center"> 图:IEEE 754 标准下的 float 表示方式 </p>
|
||||
<p><img alt="IEEE 754 标准下的 float 的计算示例" src="../number_encoding.assets/ieee_754_float.png" /></p>
|
||||
<p align="center"> 图:IEEE 754 标准下的 float 的计算示例 </p>
|
||||
|
||||
<p>给定一个示例数据 <span class="arithmatex">\(\mathrm{S} = 0\)</span> , <span class="arithmatex">\(\mathrm{E} = 124\)</span> ,<span class="arithmatex">\(\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\)</span> ,则有:</p>
|
||||
<p>观察上图,给定一个示例数据 <span class="arithmatex">\(\mathrm{S} = 0\)</span> , <span class="arithmatex">\(\mathrm{E} = 124\)</span> ,<span class="arithmatex">\(\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\)</span> ,则有:</p>
|
||||
<div class="arithmatex">\[
|
||||
\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
|
||||
\]</div>
|
||||
<p>现在我们可以回答最初的问题:<strong><code>float</code> 的表示方式包含指数位,导致其取值范围远大于 <code>int</code></strong> 。根据以上计算,<code>float</code> 可表示的最大正数为 <span class="arithmatex">\(2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}\)</span> ,切换符号位便可得到最小负数。</p>
|
||||
<p><strong>尽管浮点数 <code>float</code> 扩展了取值范围,但其副作用是牺牲了精度</strong>。整数类型 <code>int</code> 将全部 32 位用于表示数字,数字是均匀分布的;而由于指数位的存在,浮点数 <code>float</code> 的数值越大,相邻两个数字之间的差值就会趋向越大。</p>
|
||||
<p>进一步地,指数位 <span class="arithmatex">\(E = 0\)</span> 和 <span class="arithmatex">\(E = 255\)</span> 具有特殊含义,<strong>用于表示零、无穷大、<span class="arithmatex">\(\mathrm{NaN}\)</span> 等</strong>。</p>
|
||||
<p>如下表所示,指数位 <span class="arithmatex">\(E = 0\)</span> 和 <span class="arithmatex">\(E = 255\)</span> 具有特殊含义,<strong>用于表示零、无穷大、<span class="arithmatex">\(\mathrm{NaN}\)</span> 等</strong>。</p>
|
||||
<p align="center"> 表:指数位含义 </p>
|
||||
|
||||
<div class="center-table">
|
||||
|
||||
Reference in New Issue
Block a user