mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-14 02:10:37 +08:00
deploy
This commit is contained in:
@@ -3489,7 +3489,7 @@ G & = \{ V, E \} \newline
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p><img alt="链表、树、图之间的关系" src="../graph.assets/linkedlist_tree_graph.png" /></p>
|
||||
<p align="center"> Fig. 链表、树、图之间的关系 </p>
|
||||
<p align="center"> 图:链表、树、图之间的关系 </p>
|
||||
|
||||
<p>那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作节点,把「边」看作连接各个节点的指针,则可将「图」看作是一种从「链表」拓展而来的数据结构。<strong>相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,从而更为复杂</strong>。</p>
|
||||
<h2 id="911">9.1.1. 图常见类型<a class="headerlink" href="#911" title="Permanent link">¶</a></h2>
|
||||
@@ -3499,7 +3499,7 @@ G & = \{ V, E \} \newline
|
||||
<li>在有向图中,边具有方向性,即 <span class="arithmatex">\(A \rightarrow B\)</span> 和 <span class="arithmatex">\(A \leftarrow B\)</span> 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系。</li>
|
||||
</ul>
|
||||
<p><img alt="有向图与无向图" src="../graph.assets/directed_graph.png" /></p>
|
||||
<p align="center"> Fig. 有向图与无向图 </p>
|
||||
<p align="center"> 图:有向图与无向图 </p>
|
||||
|
||||
<p>根据所有顶点是否连通,可分为「连通图 Connected Graph」和「非连通图 Disconnected Graph」。</p>
|
||||
<ul>
|
||||
@@ -3507,11 +3507,11 @@ G & = \{ V, E \} \newline
|
||||
<li>对于非连通图,从某个顶点出发,至少有一个顶点无法到达。</li>
|
||||
</ul>
|
||||
<p><img alt="连通图与非连通图" src="../graph.assets/connected_graph.png" /></p>
|
||||
<p align="center"> Fig. 连通图与非连通图 </p>
|
||||
<p align="center"> 图:连通图与非连通图 </p>
|
||||
|
||||
<p>我们还可以为边添加“权重”变量,从而得到「有权图 Weighted Graph」。例如,在王者荣耀等手游中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以用有权图来表示。</p>
|
||||
<p><img alt="有权图与无权图" src="../graph.assets/weighted_graph.png" /></p>
|
||||
<p align="center"> Fig. 有权图与无权图 </p>
|
||||
<p align="center"> 图:有权图与无权图 </p>
|
||||
|
||||
<h2 id="912">9.1.2. 图常用术语<a class="headerlink" href="#912" title="Permanent link">¶</a></h2>
|
||||
<ul>
|
||||
@@ -3525,7 +3525,7 @@ G & = \{ V, E \} \newline
|
||||
<p>设图的顶点数量为 <span class="arithmatex">\(n\)</span> ,「邻接矩阵 Adjacency Matrix」使用一个 <span class="arithmatex">\(n \times n\)</span> 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 <span class="arithmatex">\(1\)</span> 或 <span class="arithmatex">\(0\)</span> 表示两个顶点之间是否存在边。</p>
|
||||
<p>如下图所示,设邻接矩阵为 <span class="arithmatex">\(M\)</span> 、顶点列表为 <span class="arithmatex">\(V\)</span> ,那么矩阵元素 <span class="arithmatex">\(M[i][j] = 1\)</span> 表示顶点 <span class="arithmatex">\(V[i]\)</span> 到顶点 <span class="arithmatex">\(V[j]\)</span> 之间存在边,反之 <span class="arithmatex">\(M[i][j] = 0\)</span> 表示两顶点之间无边。</p>
|
||||
<p><img alt="图的邻接矩阵表示" src="../graph.assets/adjacency_matrix.png" /></p>
|
||||
<p align="center"> Fig. 图的邻接矩阵表示 </p>
|
||||
<p align="center"> 图:图的邻接矩阵表示 </p>
|
||||
|
||||
<p>邻接矩阵具有以下特性:</p>
|
||||
<ul>
|
||||
@@ -3537,7 +3537,7 @@ G & = \{ V, E \} \newline
|
||||
<h3 id="_2">邻接表<a class="headerlink" href="#_2" title="Permanent link">¶</a></h3>
|
||||
<p>「邻接表 Adjacency List」使用 <span class="arithmatex">\(n\)</span> 个链表来表示图,链表节点表示顶点。第 <span class="arithmatex">\(i\)</span> 条链表对应顶点 <span class="arithmatex">\(i\)</span> ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。</p>
|
||||
<p><img alt="图的邻接表表示" src="../graph.assets/adjacency_list.png" /></p>
|
||||
<p align="center"> Fig. 图的邻接表表示 </p>
|
||||
<p align="center"> 图:图的邻接表表示 </p>
|
||||
|
||||
<p>邻接表仅存储实际存在的边,而边的总数通常远小于 <span class="arithmatex">\(n^2\)</span> ,因此它更加节省空间。然而,在邻接表中需要通过遍历链表来查找边,因此其时间效率不如邻接矩阵。</p>
|
||||
<p>观察上图可发现,<strong>邻接表结构与哈希表中的「链地址法」非常相似,因此我们也可以采用类似方法来优化效率</strong>。例如,当链表较长时,可以将链表转化为 AVL 树或红黑树,从而将时间效率从 <span class="arithmatex">\(O(n)\)</span> 优化至 <span class="arithmatex">\(O(\log n)\)</span> ,还可以通过中序遍历获取有序序列;此外,还可以将链表转换为哈希表,将时间复杂度降低至 <span class="arithmatex">\(O(1)\)</span> 。</p>
|
||||
|
||||
@@ -3454,6 +3454,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p align="center"> 图:邻接矩阵的初始化、增删边、增删顶点 </p>
|
||||
|
||||
<p>以下是基于邻接矩阵表示图的实现代码。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="2:12"><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" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" 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">JS</label><label for="__tabbed_2_6">TS</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><label for="__tabbed_2_11">Dart</label><label for="__tabbed_2_12">Rust</label></div>
|
||||
<div class="tabbed-content">
|
||||
@@ -4553,6 +4555,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p align="center"> 图:邻接表的初始化、增删边、增删顶点 </p>
|
||||
|
||||
<p>以下是基于邻接表实现图的代码示例。细心的同学可能注意到,<strong>我们在邻接表中使用 <code>Vertex</code> 节点类来表示顶点</strong>,这样做的原因有:</p>
|
||||
<ul>
|
||||
<li>如果我们选择通过顶点值来区分不同顶点,那么值重复的顶点将无法被区分。</li>
|
||||
@@ -4931,7 +4935,7 @@
|
||||
<a id="__codelineno-16-62" name="__codelineno-16-62" href="#__codelineno-16-62"></a><span class="w"> </span><span class="c1">// 在邻接表中删除顶点 vet 对应的链表</span>
|
||||
<a id="__codelineno-16-63" name="__codelineno-16-63" href="#__codelineno-16-63"></a><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="ow">delete</span><span class="p">(</span><span class="nx">vet</span><span class="p">);</span>
|
||||
<a id="__codelineno-16-64" name="__codelineno-16-64" href="#__codelineno-16-64"></a><span class="w"> </span><span class="c1">// 遍历其他顶点的链表,删除所有包含 vet 的边</span>
|
||||
<a id="__codelineno-16-65" name="__codelineno-16-65" href="#__codelineno-16-65"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">set</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="nx">values</span><span class="p">())</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-16-65" name="__codelineno-16-65" href="#__codelineno-16-65"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">set</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="nx">values</span><span class="p">())</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-16-66" name="__codelineno-16-66" href="#__codelineno-16-66"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">set</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">vet</span><span class="p">);</span>
|
||||
<a id="__codelineno-16-67" name="__codelineno-16-67" href="#__codelineno-16-67"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">index</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="o">-</span><span class="mf">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-16-68" name="__codelineno-16-68" href="#__codelineno-16-68"></a><span class="w"> </span><span class="nx">set</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span><span class="nx">index</span><span class="p">,</span><span class="w"> </span><span class="mf">1</span><span class="p">);</span>
|
||||
@@ -5018,7 +5022,7 @@
|
||||
<a id="__codelineno-17-62" name="__codelineno-17-62" href="#__codelineno-17-62"></a><span class="w"> </span><span class="c1">// 在邻接表中删除顶点 vet 对应的链表</span>
|
||||
<a id="__codelineno-17-63" name="__codelineno-17-63" href="#__codelineno-17-63"></a><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="ow">delete</span><span class="p">(</span><span class="nx">vet</span><span class="p">);</span>
|
||||
<a id="__codelineno-17-64" name="__codelineno-17-64" href="#__codelineno-17-64"></a><span class="w"> </span><span class="c1">// 遍历其他顶点的链表,删除所有包含 vet 的边</span>
|
||||
<a id="__codelineno-17-65" name="__codelineno-17-65" href="#__codelineno-17-65"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">set</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="nx">values</span><span class="p">())</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-17-65" name="__codelineno-17-65" href="#__codelineno-17-65"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">set</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="nx">values</span><span class="p">())</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-17-66" name="__codelineno-17-66" href="#__codelineno-17-66"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">index</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">set</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">vet</span><span class="p">);</span>
|
||||
<a id="__codelineno-17-67" name="__codelineno-17-67" href="#__codelineno-17-67"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">index</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="o">-</span><span class="mf">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-17-68" name="__codelineno-17-68" href="#__codelineno-17-68"></a><span class="w"> </span><span class="nx">set</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span><span class="nx">index</span><span class="p">,</span><span class="w"> </span><span class="mf">1</span><span class="p">);</span>
|
||||
|
||||
@@ -3501,7 +3501,7 @@
|
||||
<h2 id="931">9.3.1. 广度优先遍历<a class="headerlink" href="#931" title="Permanent link">¶</a></h2>
|
||||
<p><strong>广度优先遍历是一种由近及远的遍历方式,从距离最近的顶点开始访问,并一层层向外扩张</strong>。具体来说,从某个顶点出发,先遍历该顶点的所有邻接顶点,然后遍历下一个顶点的所有邻接顶点,以此类推,直至所有顶点访问完毕。</p>
|
||||
<p><img alt="图的广度优先遍历" src="../graph_traversal.assets/graph_bfs.png" /></p>
|
||||
<p align="center"> Fig. 图的广度优先遍历 </p>
|
||||
<p align="center"> 图:图的广度优先遍历 </p>
|
||||
|
||||
<h3 id="_1">算法实现<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
||||
<p>BFS 通常借助「队列」来实现。队列具有“先入先出”的性质,这与 BFS 的“由近及远”的思想异曲同工。</p>
|
||||
@@ -3891,6 +3891,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p align="center"> 图:图的广度优先遍历步骤 </p>
|
||||
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">广度优先遍历的序列是否唯一?</p>
|
||||
<p>不唯一。广度优先遍历只要求按“由近及远”的顺序遍历,<strong>而多个相同距离的顶点的遍历顺序是允许被任意打乱的</strong>。以上图为例,顶点 <span class="arithmatex">\(1\)</span> , <span class="arithmatex">\(3\)</span> 的访问顺序可以交换、顶点 <span class="arithmatex">\(2\)</span> , <span class="arithmatex">\(4\)</span> , <span class="arithmatex">\(6\)</span> 的访问顺序也可以任意交换。</p>
|
||||
@@ -3901,7 +3903,7 @@
|
||||
<h2 id="932">9.3.2. 深度优先遍历<a class="headerlink" href="#932" title="Permanent link">¶</a></h2>
|
||||
<p><strong>深度优先遍历是一种优先走到底、无路可走再回头的遍历方式</strong>。具体地,从某个顶点出发,访问当前顶点的某个邻接顶点,直到走到尽头时返回,再继续走到尽头并返回,以此类推,直至所有顶点遍历完成。</p>
|
||||
<p><img alt="图的深度优先遍历" src="../graph_traversal.assets/graph_dfs.png" /></p>
|
||||
<p align="center"> Fig. 图的深度优先遍历 </p>
|
||||
<p align="center"> 图:图的深度优先遍历 </p>
|
||||
|
||||
<h3 id="_3">算法实现<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
|
||||
<p>这种“走到尽头 + 回溯”的算法形式通常基于递归来实现。与 BFS 类似,在 DFS 中我们也需要借助一个哈希表 <code>visited</code> 来记录已被访问的顶点,以避免重复访问顶点。</p>
|
||||
@@ -4274,6 +4276,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p align="center"> 图:图的深度优先遍历步骤 </p>
|
||||
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">深度优先遍历的序列是否唯一?</p>
|
||||
<p>与广度优先遍历类似,深度优先遍历序列的顺序也不是唯一的。给定某顶点,先往哪个方向探索都可以,即邻接顶点的顺序可以任意打乱,都是深度优先遍历。</p>
|
||||
|
||||
Reference in New Issue
Block a user