This commit is contained in:
krahets
2023-08-27 23:41:10 +08:00
parent 8c9cf3f087
commit 016f13d882
66 changed files with 262 additions and 270 deletions
+4 -4
View File
@@ -3529,7 +3529,7 @@ G & = \{ V, E \} \newline
<p><img alt="有权图与无权图" src="../graph.assets/weighted_graph.png" /></p>
<p align="center"> 图 9-4 &nbsp; 有权图与无权图 </p>
<p>常用术语包括:</p>
<p>数据结构包含以下常用术语</p>
<ul>
<li>「邻接 adjacency」:当两顶点之间存在边相连时,称这两顶点“邻接”。在图 9-4 中,顶点 1 的邻接顶点为顶点 2、3、5。</li>
<li>「路径 path」:从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径”。在图 9-4 中,边序列 1-5-2-4 是顶点 1 到顶点 4 的一条路径。</li>
@@ -3539,15 +3539,15 @@ G &amp; = \{ V, E \} \newline
<p>图的常用表示方式包括“邻接矩阵”和“邻接表”。以下使用无向图进行举例。</p>
<h3 id="1">1. &nbsp; 邻接矩阵<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<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>如图 9-5 所示,设邻接矩阵为 <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>如图 9-5 所示,设邻接矩阵为 <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"> 图 9-5 &nbsp; 图的邻接矩阵表示 </p>
<p>邻接矩阵具有以下特性</p>
<p>邻接矩阵具有以下特性</p>
<ul>
<li>顶点不能与自身相连,因此邻接矩阵主对角线元素没有意义。</li>
<li>对于无向图,两个方向的边等价,此时邻接矩阵关于主对角线对称。</li>
<li>将邻接矩阵的元素从 <span class="arithmatex">\(1\)</span> , <span class="arithmatex">\(0\)</span> 替换为权重,则可表示有权图。</li>
<li>将邻接矩阵的元素从 <span class="arithmatex">\(1\)</span> <span class="arithmatex">\(0\)</span> 替换为权重,则可表示有权图。</li>
</ul>
<p>使用邻接矩阵表示图时,我们可以直接访问矩阵元素以获取边,因此增删查操作的效率很高,时间复杂度均为 <span class="arithmatex">\(O(1)\)</span> 。然而,矩阵的空间复杂度为 <span class="arithmatex">\(O(n^2)\)</span> ,内存占用较多。</p>
<h3 id="2">2. &nbsp; 邻接表<a class="headerlink" href="#2" title="Permanent link">&para;</a></h3>
+4 -4
View File
@@ -4558,7 +4558,7 @@
</div>
</div>
<h2 id="922">9.2.2 &nbsp; 基于邻接表的实现<a class="headerlink" href="#922" title="Permanent link">&para;</a></h2>
<p>设无向图的顶点总数为 <span class="arithmatex">\(n\)</span> 、边总数为 <span class="arithmatex">\(m\)</span> ,则可根据图 9-8 所示的方法实现各种操作。</p>
<p>设无向图的顶点总数为 <span class="arithmatex">\(n\)</span>、边总数为 <span class="arithmatex">\(m\)</span> ,则可根据图 9-8 所示的方法实现各种操作。</p>
<ul>
<li><strong>添加边</strong>:在顶点对应链表的末尾添加边即可,使用 <span class="arithmatex">\(O(1)\)</span> 时间。因为是无向图,所以需要同时添加两个方向的边。</li>
<li><strong>删除边</strong>:在顶点对应链表中查找并删除指定边,使用 <span class="arithmatex">\(O(m)\)</span> 时间。在无向图中,需要同时删除两个方向的边。</li>
@@ -4587,12 +4587,12 @@
</div>
<p align="center"> 图 9-8 &nbsp; 邻接表的初始化、增删边、增删顶点 </p>
<p>以下是基于邻接表实现图的代码示例。细心的同学可能注意到,<strong>我们在邻接表中使用 <code>Vertex</code> 节点类来表示顶点</strong>,这样做的原因有:</p>
<ul>
<p>以下是基于邻接表实现图的代码示例。细心的同学可能注意到,<strong>我们在邻接表中使用 <code>Vertex</code> 节点类来表示顶点</strong>这样做是有原因的。</p>
<ol>
<li>如果我们选择通过顶点值来区分不同顶点,那么值重复的顶点将无法被区分。</li>
<li>如果类似邻接矩阵那样,使用顶点列表索引来区分不同顶点。那么,假设我们想要删除索引为 <span class="arithmatex">\(i\)</span> 的顶点,则需要遍历整个邻接表,将其中 <span class="arithmatex">\(&gt; i\)</span> 的索引全部减 <span class="arithmatex">\(1\)</span> ,这样操作效率较低。</li>
<li>因此我们考虑引入顶点类 <code>Vertex</code> ,使得每个顶点都是唯一的对象,此时删除顶点时就无须改动其余顶点了。</li>
</ul>
</ol>
<div class="tabbed-set tabbed-alternate" data-tabs="4:12"><input checked="checked" id="__tabbed_4_1" name="__tabbed_4" type="radio" /><input id="__tabbed_4_2" name="__tabbed_4" type="radio" /><input id="__tabbed_4_3" name="__tabbed_4" type="radio" /><input id="__tabbed_4_4" name="__tabbed_4" type="radio" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" name="__tabbed_4" type="radio" /><input id="__tabbed_4_10" name="__tabbed_4" type="radio" /><input id="__tabbed_4_11" name="__tabbed_4" type="radio" /><input id="__tabbed_4_12" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">Java</label><label for="__tabbed_4_2">C++</label><label for="__tabbed_4_3">Python</label><label for="__tabbed_4_4">Go</label><label for="__tabbed_4_5">JS</label><label for="__tabbed_4_6">TS</label><label for="__tabbed_4_7">C</label><label for="__tabbed_4_8">C#</label><label for="__tabbed_4_9">Swift</label><label for="__tabbed_4_10">Zig</label><label for="__tabbed_4_11">Dart</label><label for="__tabbed_4_12">Rust</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
+2 -2
View File
@@ -3925,7 +3925,7 @@
<div class="admonition question">
<p class="admonition-title">广度优先遍历的序列是否唯一?</p>
<p>不唯一。广度优先遍历只要求按“由近及远”的顺序遍历,<strong>而多个相同距离的顶点的遍历顺序是允许被任意打乱的</strong>。以图 9-10 为例,顶点 <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>
<p>不唯一。广度优先遍历只要求按“由近及远”的顺序遍历,<strong>而多个相同距离的顶点的遍历顺序是允许被任意打乱的</strong>。以图 9-10 为例,顶点 <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>
</div>
<h3 id="2">2. &nbsp; 复杂度分析<a class="headerlink" href="#2" title="Permanent link">&para;</a></h3>
<p><strong>时间复杂度:</strong> 所有顶点都会入队并出队一次,使用 <span class="arithmatex">\(O(|V|)\)</span> 时间;在遍历邻接顶点的过程中,由于是无向图,因此所有边都会被访问 <span class="arithmatex">\(2\)</span> 次,使用 <span class="arithmatex">\(O(2|E|)\)</span> 时间;总体使用 <span class="arithmatex">\(O(|V| + |E|)\)</span> 时间。</p>
@@ -4263,7 +4263,7 @@
</div>
</div>
</div>
<p>深度优先遍历的算法流程如图 9-12 所示,其中:</p>
<p>深度优先遍历的算法流程如图 9-12 所示</p>
<ul>
<li><strong>直虚线代表向下递推</strong>,表示开启了一个新的递归方法来访问新顶点。</li>
<li><strong>曲虚线代表向上回溯</strong>,表示此递归方法已经返回,回溯到了开启此递归方法的位置。</li>