mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-24 20:16:06 +00:00
deploy
This commit is contained in:
@@ -1768,9 +1768,9 @@
|
||||
|
||||
|
||||
<h1 id="42">4.2. 链表<a class="headerlink" href="#42" title="Permanent link">¶</a></h1>
|
||||
<p>内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间往往是散落在内存各处的。上节讲到,<strong>存储数组的内存空间必须是连续的</strong>,当我们需要申请一个非常大的数组时,系统不一定能够分配这么大的连续内存空间。</p>
|
||||
<p>相对地,链表则更加灵活,可以被存储到非连续的内存空间。「链表 Linked List」是一种线性数据结构,其中每个元素都是单独的对象,各个元素(即结点)之间通过指针连接。由于结点中记录了连接关系,因此链表的存储方式相比于数组更加灵活,系统可将结点分散在内存各处,而不必保证内存地址的连续性。</p>
|
||||
<p>链表的「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」(或称「引用 Reference」)。</p>
|
||||
<p>内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当我们需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。</p>
|
||||
<p>与数组相比,链表更具灵活性,因为它可以存储在非连续的内存空间。「链表 Linked List」是一种线性数据结构,其每个元素都是一个结点对象,各个结点之间通过指针连接,从当前结点通过指针可以访问到下一个结点。由于指针记录了下个结点的内存地址,因此无需保证内存地址的连续性,从而可以将各个结点分散存储在内存各处。</p>
|
||||
<p>链表「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」,或称指向下一结点的「引用 Reference」。</p>
|
||||
<p><img alt="链表定义与存储方式" src="../linked_list.assets/linkedlist_definition.png" /></p>
|
||||
<p align="center"> Fig. 链表定义与存储方式 </p>
|
||||
|
||||
@@ -1904,12 +1904,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p><strong>尾结点指向什么?</strong> 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 <code>null</code> / <code>nullptr</code> / <code>None</code> 。在不引起歧义下,本书都使用 <code>null</code> 来表示空。</p>
|
||||
<p><strong>链表初始化方法</strong>。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。</p>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>我们通常将头结点当作链表的代称,例如头结点 <code>head</code> 和链表 <code>head</code> 实际上是同义的。</p>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">尾结点指向什么?</p>
|
||||
<p>我们将链表的最后一个结点称为「尾结点」,其指向的是“空”,在 Java, C++, Python 中分别记为 <code>null</code>, <code>nullptr</code>, <code>None</code> 。在不引起歧义的前提下,本书都使用 <code>null</code> 来表示空。</p>
|
||||
</div>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">如何称呼链表?</p>
|
||||
<p>在编程语言中,数组整体就是一个变量,例如数组 <code>nums</code> ,包含各个元素 <code>nums[0]</code> , <code>nums[1]</code> 等等。而链表是由多个结点对象组成,我们通常将头结点当作链表的代称,例如头结点 <code>head</code> 和链表 <code>head</code> 实际上是同义的。</p>
|
||||
</div>
|
||||
<p><strong>链表初始化方法</strong>。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的头结点(即首个结点)出发,通过指针 <code>next</code> 依次访问所有结点。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="2:10"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Java</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Python</label><label for="__tabbed_2_4">Go</label><label for="__tabbed_2_5">JavaScript</label><label for="__tabbed_2_6">TypeScript</label><label for="__tabbed_2_7">C</label><label for="__tabbed_2_8">C#</label><label for="__tabbed_2_9">Swift</label><label for="__tabbed_2_10">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -1965,12 +1968,11 @@
|
||||
<a id="__codelineno-13-5" name="__codelineno-13-5" href="#__codelineno-13-5"></a><span class="nx">n2</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">NewListNode</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
|
||||
<a id="__codelineno-13-6" name="__codelineno-13-6" href="#__codelineno-13-6"></a><span class="nx">n3</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">NewListNode</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
|
||||
<a id="__codelineno-13-7" name="__codelineno-13-7" href="#__codelineno-13-7"></a><span class="nx">n4</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">NewListNode</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
|
||||
<a id="__codelineno-13-8" name="__codelineno-13-8" href="#__codelineno-13-8"></a>
|
||||
<a id="__codelineno-13-9" name="__codelineno-13-9" href="#__codelineno-13-9"></a><span class="c1">// 构建引用指向</span>
|
||||
<a id="__codelineno-13-10" name="__codelineno-13-10" href="#__codelineno-13-10"></a><span class="nx">n0</span><span class="p">.</span><span class="nx">Next</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">n1</span>
|
||||
<a id="__codelineno-13-11" name="__codelineno-13-11" href="#__codelineno-13-11"></a><span class="nx">n1</span><span class="p">.</span><span class="nx">Next</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">n2</span>
|
||||
<a id="__codelineno-13-12" name="__codelineno-13-12" href="#__codelineno-13-12"></a><span class="nx">n2</span><span class="p">.</span><span class="nx">Next</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">n3</span>
|
||||
<a id="__codelineno-13-13" name="__codelineno-13-13" href="#__codelineno-13-13"></a><span class="nx">n3</span><span class="p">.</span><span class="nx">Next</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">n4</span>
|
||||
<a id="__codelineno-13-8" name="__codelineno-13-8" href="#__codelineno-13-8"></a><span class="c1">// 构建引用指向</span>
|
||||
<a id="__codelineno-13-9" name="__codelineno-13-9" href="#__codelineno-13-9"></a><span class="nx">n0</span><span class="p">.</span><span class="nx">Next</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">n1</span>
|
||||
<a id="__codelineno-13-10" name="__codelineno-13-10" href="#__codelineno-13-10"></a><span class="nx">n1</span><span class="p">.</span><span class="nx">Next</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">n2</span>
|
||||
<a id="__codelineno-13-11" name="__codelineno-13-11" href="#__codelineno-13-11"></a><span class="nx">n2</span><span class="p">.</span><span class="nx">Next</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">n3</span>
|
||||
<a id="__codelineno-13-12" name="__codelineno-13-12" href="#__codelineno-13-12"></a><span class="nx">n3</span><span class="p">.</span><span class="nx">Next</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">n4</span>
|
||||
</code></pre></div>
|
||||
</div>
|
||||
<div class="tabbed-block">
|
||||
@@ -2066,7 +2068,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="421">4.2.1. 链表优点<a class="headerlink" href="#421" title="Permanent link">¶</a></h2>
|
||||
<p><strong>在链表中,插入与删除结点的操作效率高</strong>。比如,如果我们想在链表中间的两个结点 <code>A</code> , <code>B</code> 之间插入一个新结点 <code>P</code> ,我们只需要改变两个结点指针即可,时间复杂度为 <span class="arithmatex">\(O(1)\)</span> ,相比数组的插入操作高效很多。</p>
|
||||
<p><strong>链表中插入与删除结点的操作效率高</strong>。例如,如果我们想在链表中间的两个结点 <code>A</code> , <code>B</code> 之间插入一个新结点 <code>P</code> ,我们只需要改变两个结点指针即可,时间复杂度为 <span class="arithmatex">\(O(1)\)</span> ;相比之下,数组的插入操作效率要低得多。</p>
|
||||
<p><img alt="链表插入结点" src="../linked_list.assets/linkedlist_insert_node.png" /></p>
|
||||
<p align="center"> Fig. 链表插入结点 </p>
|
||||
|
||||
@@ -2159,7 +2161,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>在链表中删除结点也很方便,只需要改变一个结点指针即可。如下图所示,虽然在完成删除后结点 <code>P</code> 仍然指向 <code>n1</code> ,但实际上 <code>P</code> 已经不属于此链表了,因为遍历此链表是无法访问到 <code>P</code> 的。</p>
|
||||
<p>在链表中删除结点也非常方便,只需改变一个结点的指针即可。如下图所示,尽管在删除操作完成后,结点 <code>P</code> 仍然指向 <code>n1</code>,但实际上 <code>P</code> 已经不再属于此链表,因为遍历此链表时无法访问到 <code>P</code>。</p>
|
||||
<p><img alt="链表删除结点" src="../linked_list.assets/linkedlist_remove_node.png" /></p>
|
||||
<p align="center"> Fig. 链表删除结点 </p>
|
||||
|
||||
@@ -2285,7 +2287,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="422">4.2.2. 链表缺点<a class="headerlink" href="#422" title="Permanent link">¶</a></h2>
|
||||
<p><strong>链表访问结点效率低</strong>。上节提到,数组可以在 <span class="arithmatex">\(O(1)\)</span> 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 <code>index</code> (即第 <code>index + 1</code> 个)的结点,那么需要 <code>index</code> 次访问操作。</p>
|
||||
<p><strong>链表访问结点效率较低</strong>。如上节所述,数组可以在 <span class="arithmatex">\(O(1)\)</span> 时间下访问任意元素。然而,链表无法直接访问任意结点,这是因为系统需要从头结点出发,逐个向后遍历直至找到目标结点。例如,若要访问链表索引为 <code>index</code>(即第 <code>index + 1</code> 个)的结点,则需要向后遍历 <code>index</code> 轮。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="5:10"><input checked="checked" id="__tabbed_5_1" name="__tabbed_5" type="radio" /><input id="__tabbed_5_2" name="__tabbed_5" type="radio" /><input id="__tabbed_5_3" name="__tabbed_5" type="radio" /><input id="__tabbed_5_4" name="__tabbed_5" type="radio" /><input id="__tabbed_5_5" name="__tabbed_5" type="radio" /><input id="__tabbed_5_6" name="__tabbed_5" type="radio" /><input id="__tabbed_5_7" name="__tabbed_5" type="radio" /><input id="__tabbed_5_8" name="__tabbed_5" type="radio" /><input id="__tabbed_5_9" name="__tabbed_5" type="radio" /><input id="__tabbed_5_10" name="__tabbed_5" type="radio" /><div class="tabbed-labels"><label for="__tabbed_5_1">Java</label><label for="__tabbed_5_2">C++</label><label for="__tabbed_5_3">Python</label><label for="__tabbed_5_4">Go</label><label for="__tabbed_5_5">JavaScript</label><label for="__tabbed_5_6">TypeScript</label><label for="__tabbed_5_7">C</label><label for="__tabbed_5_8">C#</label><label for="__tabbed_5_9">Swift</label><label for="__tabbed_5_10">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -2408,7 +2410,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p><strong>链表的内存占用多</strong>。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。</p>
|
||||
<p><strong>链表的内存占用较大</strong>。链表以结点为单位,每个结点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。</p>
|
||||
<h2 id="423">4.2.3. 链表常用操作<a class="headerlink" href="#423" title="Permanent link">¶</a></h2>
|
||||
<p><strong>遍历链表查找</strong>。遍历链表,查找链表内值为 <code>target</code> 的结点,输出结点在链表中的索引。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="6:10"><input checked="checked" id="__tabbed_6_1" name="__tabbed_6" type="radio" /><input id="__tabbed_6_2" name="__tabbed_6" type="radio" /><input id="__tabbed_6_3" name="__tabbed_6" type="radio" /><input id="__tabbed_6_4" name="__tabbed_6" type="radio" /><input id="__tabbed_6_5" name="__tabbed_6" type="radio" /><input id="__tabbed_6_6" name="__tabbed_6" type="radio" /><input id="__tabbed_6_7" name="__tabbed_6" type="radio" /><input id="__tabbed_6_8" name="__tabbed_6" type="radio" /><input id="__tabbed_6_9" name="__tabbed_6" type="radio" /><input id="__tabbed_6_10" name="__tabbed_6" type="radio" /><div class="tabbed-labels"><label for="__tabbed_6_1">Java</label><label for="__tabbed_6_2">C++</label><label for="__tabbed_6_3">Python</label><label for="__tabbed_6_4">Go</label><label for="__tabbed_6_5">JavaScript</label><label for="__tabbed_6_6">TypeScript</label><label for="__tabbed_6_7">C</label><label for="__tabbed_6_8">C#</label><label for="__tabbed_6_9">Swift</label><label for="__tabbed_6_10">Zig</label></div>
|
||||
@@ -2551,9 +2553,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="424">4.2.4. 常见链表类型<a class="headerlink" href="#424" title="Permanent link">¶</a></h2>
|
||||
<p><strong>单向链表</strong>。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 <code>null</code> 。</p>
|
||||
<p><strong>环形链表</strong>。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。</p>
|
||||
<p><strong>双向链表</strong>。单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。</p>
|
||||
<p><strong>单向链表</strong>。即上述介绍的普通链表。单向链表的结点包含值和指向下一结点的指针(引用)两项数据。我们将首个结点称为头结点,将最后一个结点成为尾结点,尾结点指向 <code>null</code> 。</p>
|
||||
<p><strong>环形链表</strong>。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,任意结点都可以视作头结点。</p>
|
||||
<p><strong>双向链表</strong>。与单向链表相比,双向链表记录了两个方向的指针(引用)。双向链表的结点定义同时包含指向后继结点(下一结点)和前驱结点(上一结点)的指针。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="7:10"><input checked="checked" id="__tabbed_7_1" name="__tabbed_7" type="radio" /><input id="__tabbed_7_2" name="__tabbed_7" type="radio" /><input id="__tabbed_7_3" name="__tabbed_7" type="radio" /><input id="__tabbed_7_4" name="__tabbed_7" type="radio" /><input id="__tabbed_7_5" name="__tabbed_7" type="radio" /><input id="__tabbed_7_6" name="__tabbed_7" type="radio" /><input id="__tabbed_7_7" name="__tabbed_7" type="radio" /><input id="__tabbed_7_8" name="__tabbed_7" type="radio" /><input id="__tabbed_7_9" name="__tabbed_7" type="radio" /><input id="__tabbed_7_10" name="__tabbed_7" type="radio" /><div class="tabbed-labels"><label for="__tabbed_7_1">Java</label><label for="__tabbed_7_2">C++</label><label for="__tabbed_7_3">Python</label><label for="__tabbed_7_4">Go</label><label for="__tabbed_7_5">JavaScript</label><label for="__tabbed_7_6">TypeScript</label><label for="__tabbed_7_7">C</label><label for="__tabbed_7_8">C#</label><label for="__tabbed_7_9">Swift</label><label for="__tabbed_7_10">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
|
||||
Reference in New Issue
Block a user