<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>hhmy 的博客</title>
        <link>https://hhmy27.github.io//</link>
        <description>hello world</description>
        <lastBuildDate>Thu, 31 Aug 2023 14:25:49 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <copyright>All rights reserved 2023, hhmy</copyright>
        <item>
            <title><![CDATA[我的 Vim 学习之路]]></title>
            <link>https://hhmy27.github.io//Vim</link>
            <guid>https://hhmy27.github.io//Vim</guid>
            <pubDate>Thu, 24 Aug 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[谨以此文缅怀 Vim 作者 Bram Moolenaar]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-530ff059bf5c4dea9b354eb5625f1e2a"><div class="notion-viewport"></div><div class="notion-table-of-contents notion-gray notion-block-05bbeb323346445bbd469cd0cd2ed5c8"><a href="#7ebb6fe62ddf44eb9c4199b975df7363" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">初识</span></a><a href="#fe669f0b37cf4c9586205a8f2fe4fb45" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">学习 Vim</span></a><a href="#90d7f222ef434b02b2af9dbdef80b76a" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">持续学习 Vim</span></a><a href="#11ea1c7c973a46a0afada49441a0370c" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">人生苦短</span></a></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-7ebb6fe62ddf44eb9c4199b975df7363" data-id="7ebb6fe62ddf44eb9c4199b975df7363"><span><div id="7ebb6fe62ddf44eb9c4199b975df7363" class="notion-header-anchor"></div><a class="notion-hash-link" href="#7ebb6fe62ddf44eb9c4199b975df7363" title="初识"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">初识</span></span></h2><div class="notion-text notion-block-ee24d99741824bd89639ac5eed21d343">我想大约是 17 年初的时候，在我看了一篇介绍的 Vim 和 Emacs 文章后，我对 Vim 这种编辑模式产生了浓厚的兴趣，文中介绍使用 Vim 你可以完全脱离鼠标工作，大幅提升 Coding 的效率，对当时的我产生了很大的震撼，这太酷了。</div><div class="notion-text notion-block-e4e46edf846b401bac765614e02e3f86">为了使用 Vim，我立即安装了一个 Ubuntu 16 的虚拟机，然后调出命令行，兴奋的敲下命令：</div><div class="notion-text notion-block-85ea38a9072342928a5c3a2e3d71ce46"><code class="notion-inline-code">$vim test</code></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-959efca861684a368d17c0720db4e684"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fc1b81674-a872-48f7-9057-3f4d025e9840%2FUntitled.png%3Fid%3D959efca8-6168-4a36-8d17-c0720db4e684%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D8QWERTsnD_fiBuLrwBzD0Vj58U7M_8flcxNBuS2ghDE?table=block&amp;id=959efca8-6168-4a36-8d17-c0720db4e684&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-4fc034271e5946e2bc41ae279ba10c55">接着，我发现键盘的⬆️⬇️⬅️➡️没有任何反应，我甚至无法打出 hello world。</div><div class="notion-text notion-block-a9dec0d00c9547e7a2c972ff21ce994a">对此，我的反应和大多数 Vim 初学者一样，感到沮丧。</div><div class="notion-text notion-block-cd9f56e7198e4eafbdf31199e7a3fffc">但是下一秒，我发现我沮丧得太早了，因为我根本无法退出这个界面，于是我不得不换台电脑，搜索那个经典的问题：</div><div class="notion-blank notion-block-2d9a27bcc3464a01aaa0ee7de92fbd4a"> </div><blockquote class="notion-quote notion-block-17021ef17ab241a6a28d6cf46ca50498">如何退出 Vim？</blockquote><div class="notion-text notion-block-93421cc55e564a3b8407cd2d95cd38a8">于是<code class="notion-inline-code">:q</code>成为了我第一个掌握的命令。</div><div class="notion-text notion-block-76beb5501c1543608d732528651e7af0">经过这次折腾，我对 Vim 没有太多的好感，但我始终保持了对 Vim 的敬畏，我认为这些从上个世纪开始持续称霸数十年的工具都是不可冒犯的，尤其是大多数人都一致好评的情况下。</div><div class="notion-text notion-block-ed917d41157b46fc822f02f93bedefdd">当时大学的课程进行到学习 C 了，写代码就需要用 IDE，我下载了 Dev-C++ 作为我第一个 IDE 进行编程，开始写课本上的练习题，当学期结束时，我也把 IDE 换成了 Visual Studio。</div><div class="notion-text notion-block-d25d6ec0ef7b4b4b92d5c6997cffb36c">我已经忘了要学习 Vim 这件事情了。</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-fe669f0b37cf4c9586205a8f2fe4fb45" data-id="fe669f0b37cf4c9586205a8f2fe4fb45"><span><div id="fe669f0b37cf4c9586205a8f2fe4fb45" class="notion-header-anchor"></div><a class="notion-hash-link" href="#fe669f0b37cf4c9586205a8f2fe4fb45" title="学习 Vim"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">学习 Vim</span></span></h2><div class="notion-text notion-block-3c446985e033443a8e117e2c246d4ddc">人做某件事情总是需要推动力的，而我开始学习 Vim 的转折点来自加入学校的 C++ 实验室。</div><div class="notion-text notion-block-4ff26987bd9046a59c4e1673118e6df4">所谓实验室其实就是计算机学院划出了一部分办公室，提供设备和场地给学生学习，虽然我大部分时间都在那里打游戏。</div><div class="notion-text notion-block-2264e1d1cdef4f2295c59ff7ba8b5ebd">18 年的夏天，在实验室的书架上，我发现了一本书，长得黑漆漆的，就是它：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-7543cc4c483644ed98461ada63563be4"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:240px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fc95ad3d1-430d-4b36-a208-0ccf0eb1120b%2FUntitled.png%3Fid%3D7543cc4c-4836-44ed-9846-1ada63563be4%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D-sCSrV7YrFmw9uKnErWwS1-G9QsTBAO6HnuwiAXeEmk?table=block&amp;id=7543cc4c-4836-44ed-9846-1ada63563be4&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-a9bd270c773d4e7fb5fadabc2fd76679">花了大约三周的时间读完这本书后，我开始觉得这些古老的指针、Unix 操作系统、C 悠久的历史太有魅力了，我陷入了某种对 C 的皈依狂热中，这种狂热在我读完《LinuxC编程一站式学习》后达到了顶峰。</div><blockquote class="notion-quote notion-block-8b3982405dda40b7842c54f27032ff5d">当时阅读的笔记：<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://blog.csdn.net/hhmy77/article/details/104038943?ops_request_misc=&amp;request_id=d2d3c76b6df049d8b3ebcb5915e2b009&amp;biz_id=&amp;utm_medium=distribute.pc_search_result.none-task-blog-2~blog~koosearch~default-1-104038943-null-null.268%5Ev1%5Econtrol&amp;utm_term=c&amp;spm=1018.2226.3001.4450">Linux下C编译的步骤_hhmy77的博客-CSDN博客</a></blockquote><div class="notion-text notion-block-e006f3d1c2e34ae9abe8a0e6fbf9f355">这种影响是巨大的，我立即放弃了 Visual Studio，转而开始刀耕火种式的编程，我强迫用命令行来写 C，自己生成可执行文件，在这一过程中， Vim 是必须要掌握的工具，出于教徒的虔诚，我开始认真的学习它。</div><div class="notion-text notion-block-a5401a575bac49f28512c849a1dcec3c">过了大半年的学习，我的 Vim 技巧已经能支持我像使用 IDE 一样流畅的编写代码了，也就是说我终于不用写到一半去搜索 Vim 的命令，或者复制粘贴到 IDE 中完成剩余的代码编写了，这让我看起来像一个正常人一样，真是令人振奋的进步。</div><div class="notion-text notion-block-37396eaadf4a44aab8c03f72556f80b0">在旧的博客上，我还记录了 Vim 的一些使用技巧，下面就是当时我掌握的技能</div><pre class="notion-code language-javascript"><code class="language-javascript">插入模式 i
退出模式esc
左下上右h j k l
跳转到改行的第一个afa
撤销u
<span class="token literal-property property">命令编辑模式下：保存并退出</span><span class="token operator">:</span>wq 不保存退出<span class="token operator">:</span>q 在后面加！则强制操作
保存并退出shift<span class="token operator">+</span>zz 不保存退出shift<span class="token operator">+</span>zq
普通模式下替换当前字符为ara ，替换字符串<span class="token constant">R</span>字符串将当前字符变为大写<span class="token operator">~</span>

普通模式下 当前行右移<span class="token operator">>></span>
跳转到行尾$ 行首<span class="token operator">^</span>

跳转到下一个单词e
复制：
v进入可视模式，然后选择想要复制的语句，按y复制，返回normal模式，按p粘贴

普通模式下输入 <span class="token operator">/</span> 然后键入需要查找的字符串 按回车后就会进行查找。
？ 与<span class="token operator">/</span> 功能相同，只不过 ？ 是向上而 <span class="token operator">/</span> 是向下查找。
进入查找之后，输入n 和 <span class="token constant">N</span> 可以继续查找。
n是查找下一个内容<span class="token punctuation">,</span><span class="token constant">N</span>查找上一个内容。

在当前行下开始插入o
在当前行之前开始插入<span class="token constant">O</span>

向上移动一行 ctrl<span class="token operator">+</span>y
向下移动一行ctrl<span class="token operator">+</span>e
跳转到单词词首、下一单词词尾、前一单词词首web
删除光标开始的单词 dw
删除光标所在的单词daw

删除当前字符x

<span class="token number">2.3</span><span class="token number">.3</span> 高级查找

普通模式下输入<span class="token operator">*</span>寻找游标所在处的单词
普通模式下输入#同上，但 # 是向前（上）找，<span class="token operator">*</span>则是向后（下）找
普通模式下输入g<span class="token operator">*</span>同<span class="token operator">*</span> ，但部分符合该单词即可
普通模式下输入g#同# ，但部分符合该单词即可</code></pre><div class="notion-text notion-block-fcaaa11629074b27ae76184fecf9b531">尽管并不多，我还是花了很多时间去学习 Vim，足以证明 Vim 的学习曲线有多么陡峭。</div><blockquote class="notion-quote notion-block-8d214977ee6f46a1b647f94ec65e0ed9">一些主流编辑器的学习曲线</blockquote><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-90d7f222ef434b02b2af9dbdef80b76a" data-id="90d7f222ef434b02b2af9dbdef80b76a"><span><div id="90d7f222ef434b02b2af9dbdef80b76a" class="notion-header-anchor"></div><a class="notion-hash-link" href="#90d7f222ef434b02b2af9dbdef80b76a" title="持续学习 Vim"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">持续学习 Vim</span></span></h2><div class="notion-text notion-block-6de73636b1094775a33d5020852b343a">又经过一段时间的学习，我接触了 Java 和 C#，我对现代化 IDE 的需求与日俱增，为了使用 C#，我重新开始使用 Visual Studio，为了学习 Java，下载了 IDEA。这些现代 IDE 使得我丝滑地忘记了那段狂热的时光，唯一存在过的证明是我会在第一时间下载 Vim 插件到这些 IDE 里，使用 Vim 的编辑模式来 Coding。</div><div class="notion-text notion-block-2ba4a3c7c24b4a43abd82801989dd05b">Vim 是如此博大精深，直到前几天，我才刚刚掌握了 <code class="notion-inline-code">:vsplit</code> 来分屏操作 : )，但学习 Vim 的过程是充满趣味的，这种苦行僧式的修行，一开始会疯狂的折磨你，然后会度过一个瓶颈期，接着，你会感觉到快感，随着你的技巧不断提升，快感甚至能进化为喜悦。</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-11ea1c7c973a46a0afada49441a0370c" data-id="11ea1c7c973a46a0afada49441a0370c"><span><div id="11ea1c7c973a46a0afada49441a0370c" class="notion-header-anchor"></div><a class="notion-hash-link" href="#11ea1c7c973a46a0afada49441a0370c" title="人生苦短"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">人生苦短</span></span></h2><div class="notion-text notion-block-8fcf0f7119064a31a9990a19599dcf11">2023年8月5日，我在 V2EX 上刷到了 Vim 作者去世的消息，点开链接查看，当即有一种人生苦短的感慨。回头看，我学习 Vim 的时间不过几年，而这几年的时光里，我们看到了太多新技术的诞生，看到了互联网的退潮，看到了各种人才陨落的消息，Life is short …</div><blockquote class="notion-quote notion-block-964f2fd673864816ab354c96ae062d4f"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://groups.google.com/g/vim_announce/c/tWahca9zkt4?pli=1">Message from the family of Bram Moolenaar (google.com)</a></blockquote><div class="notion-text notion-block-ee47b57db1e24a4181deda7b661484d1">老实说，我并不是一名虔诚的 Vim 使用者，我只是使用 Vim 的编辑模式来编程，而我也并不了解 Vim 的作者和他背后的历史。在这之后，我阅读了一些关于 Moolenaar 的文章，我越来越对 Moolenaar 感到敬佩。</div><blockquote class="notion-quote notion-block-08edbf57d5ae480c84766689edce9ce7"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.inside.com.tw/article/32437-moolenaar">R.I.P.！Vim 程式編輯器作者 Bram Moolenaar 享壽 62 歲 - INSIDE</a>
Moolenaar 設定 Vim 的使用條款寫到，如果願意支持的話使用者可以捐款給烏干達的兒童，而不是要求捐款給 Moolenaar 自己。
目前 Vim 每年約有 30000 歐元的捐款，這換算下來大約可以資助 50 名烏干達兒童完成從小學到大學的學費。</blockquote><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-a12a45c7622649b48a6c94eb342f5a0a"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fdc5c8fd1-2622-418e-a6a5-7100a459c038%2FUntitled.png%3Fid%3Da12a45c7-6226-49b4-8a6c-94eb342f5a0a%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DtURKu1u1xT78TF92tRJBE2567Wd7txYBhls-lQD217s?table=block&amp;id=a12a45c7-6226-49b4-8a6c-94eb342f5a0a&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-0d002115a7d34edd8f13ce9f7b8befec">如此伟大的作品，如此伟大的人，文章的最后，让我们一起缅怀这名程序员吧。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-cac5a94de1014a80b9deb8272bb55471"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F58db7b1c-b316-4c49-90f0-ef2494a22e2b%2FUntitled.png%3Fid%3Dcac5a94d-e101-4a80-b9de-b8272bb55471%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D53f1ycu-DW-9bvypyEAGC0pmYEx_Fx8bT7AK9TZDIsg?table=block&amp;id=cac5a94d-e101-4a80-b9de-b8272bb55471&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><hr class="notion-hr notion-block-27fa4442502348c1ab13a588edfe4940"/><div class="notion-blank notion-block-8472d79ae73c409d9be6ce853ce8a137"> </div><div class="notion-blank notion-block-8b8adf032116458b85889e0652873785"> </div><div class="notion-blank notion-block-2f6880a3c9a6495ea7ce0ef925ea0a09"> </div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[并查集汇总]]></title>
            <link>https://hhmy27.github.io//disjoint-set</link>
            <guid>https://hhmy27.github.io//disjoint-set</guid>
            <pubDate>Mon, 06 Feb 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[朴素并查集、带权并查集、种类并查集]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-553879bbdb6e448d8adc68864d7be6cf"><div class="notion-viewport"></div><div class="notion-table-of-contents notion-gray notion-block-665362aba08c4941aaad9ba92cd57ea5"><a href="#743bd369755b4811a44a68fb3b0a8f1e" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">用途</span></a><a href="#7c7b72d0f6324af99ebe2269462dfa7e" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">朴素并查集</span></a><a href="#5376cc1bc6f647778d2ede5ca0e3e500" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">带权并查集</span></a><a href="#14d5c62483e84328bb2bec7c5ec913b8" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">带权的合并操作</span></a><a href="#4cc23306a71442f68095b46fd43c4de6" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">find函数</span></a><a href="#3213415af97a4e7a961768e328fe6c9b" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">种类并查集</span></a><a href="#3ddf3fea1f334e209cecc5acd4cfb1e2" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">复杂度</span></a><a href="#b9538b9a6118488cb7ccb7ca69fe7743" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">练习题</span></a><a href="#81026ead852e4099994b00e4788d79a5" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">银河英雄传说</span></a><a href="#1bf31868400841d7b91cb408559a2298" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">食物链</span></a><a href="#8120be68a9714082927c98b972b45ecb" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">关押罪犯</span></a><a href="#b196fc372dfe417c9acf5ba7241c08d7" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">总结</span></a><a href="#e03ba5df13e6471e83ed3ee25793e00f" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">Ref</span></a></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-743bd369755b4811a44a68fb3b0a8f1e" data-id="743bd369755b4811a44a68fb3b0a8f1e"><span><div id="743bd369755b4811a44a68fb3b0a8f1e" class="notion-header-anchor"></div><a class="notion-hash-link" href="#743bd369755b4811a44a68fb3b0a8f1e" title="用途"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">用途</span></span></h2><div class="notion-text notion-block-e4f3bbce7fda49abafe7ab880de46adc">并查集一般用于管理元素所属集合，是一种数据结构，一般有两个操作：</div><ol start="1" class="notion-list notion-list-numbered notion-block-96995af44edd4e668bbc3d0a2d25bf5c"><li>合并两个集合</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-55f5fc809f324043a80ced24f7b52d93"><li>判断两个元素是否属于同一集合</li></ol><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-7c7b72d0f6324af99ebe2269462dfa7e" data-id="7c7b72d0f6324af99ebe2269462dfa7e"><span><div id="7c7b72d0f6324af99ebe2269462dfa7e" class="notion-header-anchor"></div><a class="notion-hash-link" href="#7c7b72d0f6324af99ebe2269462dfa7e" title="朴素并查集"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">朴素并查集</span></span></h2><div class="notion-text notion-block-6fbd64de451a4faa8dfd2196f1986785">一般这种并查集是我们最常用的结构，为了区分，我把它成为朴素并查集。</div><div class="notion-text notion-block-bf6c9466e8ca415885965af5de8d18d0"><b>原版</b></div><div class="notion-text notion-block-1ad906644a08482caa1b1bfd01a23876">原版的并查集就是实现了开头介绍的合并、判断两个操作。</div><pre class="notion-code language-c++"><code class="language-c++">int p<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token constant">N</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// 找到一个节点的祖先节点</span>
int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int a</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>p<span class="token punctuation">[</span>a<span class="token punctuation">]</span> <span class="token operator">!=</span> a<span class="token punctuation">)</span>
        p<span class="token punctuation">[</span>a<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>a<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>a<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 合并两个节点所在的集合</span>
<span class="token keyword">void</span> <span class="token function">merge</span><span class="token punctuation">(</span><span class="token parameter">int a<span class="token punctuation">,</span> int b</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    int x <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">,</span> y <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
    p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> y<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-b0c91971fc16429dbe76889b47aab5e8"><b>维护size的并查集</b></div><div class="notion-text notion-block-8759aefbd9f04686b1adab66164bc9d3">可以使用一个size数组来记录集合存在的元素，合并的时候让元素少的集合往元素多的集合合并。</div><div class="notion-text notion-block-7013df33b65b4efcbc59ba1634114206">这种合并方式也被称为启发式合并。</div><div class="notion-text notion-block-b6873227302346a0a74cc378d9f17a79">相比于原版的合并，效率会高一些。</div><pre class="notion-code language-c++"><code class="language-c++"><span class="token comment">// p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义，表示祖宗节点所在集合中的点的数量</span>
int p<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> size<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> n<span class="token punctuation">;</span>

<span class="token comment">// 返回x的祖宗节点</span>
int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">!=</span> x<span class="token punctuation">)</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 初始化，假定节点编号是1~n</span>
<span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i<span class="token punctuation">;</span>
        size<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// 启发式合并</span>
<span class="token keyword">void</span> <span class="token function">merge</span><span class="token punctuation">(</span><span class="token parameter">int a<span class="token punctuation">,</span> int b</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    int x <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">,</span> y <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">==</span> y<span class="token punctuation">)</span>
        <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>size<span class="token punctuation">[</span>y<span class="token punctuation">]</span> <span class="token operator">&lt;</span> size<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span>
        <span class="token function">swap</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> y<span class="token punctuation">;</span>
    size<span class="token punctuation">[</span>y<span class="token punctuation">]</span> <span class="token operator">+=</span> size<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-blank notion-block-eb290993b4744c958133bf984f6e97ef"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-5376cc1bc6f647778d2ede5ca0e3e500" data-id="5376cc1bc6f647778d2ede5ca0e3e500"><span><div id="5376cc1bc6f647778d2ede5ca0e3e500" class="notion-header-anchor"></div><a class="notion-hash-link" href="#5376cc1bc6f647778d2ede5ca0e3e500" title="带权并查集"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">带权并查集</span></span></h2><div class="notion-text notion-block-5b211aec572a4a02bb03d26063613253"><b>维护到祖先节点距离的并查集</b></div><pre class="notion-code language-c++"><code class="language-c++"><span class="token keyword">const</span> int <span class="token constant">N</span> <span class="token operator">=</span> <span class="token number">1e5</span><span class="token punctuation">;</span>

int p<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> d<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token comment">// p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离</span>

<span class="token comment">// 返回x的祖宗节点</span>
int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">!=</span> x<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        int u <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">+=</span> d<span class="token punctuation">[</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> u<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 版本2</span>
int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
		<span class="token keyword">if</span> <span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">!=</span> x<span class="token punctuation">)</span>
		<span class="token punctuation">{</span>
				int u <span class="token operator">=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
				p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">+=</span> d<span class="token punctuation">[</span>u<span class="token punctuation">]</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
		<span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 初始化，假定节点编号是1~n</span>
<span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i<span class="token punctuation">;</span>
        d<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// 合并a和b所在的两个集合：</span>
<span class="token keyword">void</span> <span class="token function">merge</span><span class="token punctuation">(</span><span class="token parameter">int a<span class="token punctuation">,</span> int b</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
		int px <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">,</span> py <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
    p<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> py<span class="token punctuation">;</span>
    d<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> distance<span class="token punctuation">;</span> <span class="token comment">// 根据具体问题，初始化find(a)的偏移量</span>
<span class="token punctuation">}</span></code></pre><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-14d5c62483e84328bb2bec7c5ec913b8" data-id="14d5c62483e84328bb2bec7c5ec913b8"><span><div id="14d5c62483e84328bb2bec7c5ec913b8" class="notion-header-anchor"></div><a class="notion-hash-link" href="#14d5c62483e84328bb2bec7c5ec913b8" title="带权的合并操作"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">带权的合并操作</span></span></h3><div class="notion-text notion-block-6b3c512b8f164687a2a65f635be098e6">我们看一个具体的例子，假设有x和y两个节点，他们的距离是w，而px和py分别是两个节点的祖先节点，它们到祖先的距离分别是d[x]和d[y]。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-2be479718f344d61be6be52186222b23"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:632px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fe2ea857c-e35e-4e95-80e7-134da4810a4f%2FUntitled.png%3Fid%3D2be47971-8f34-4d61-be6b-e52186222b23%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DGZwHdz5Z80Dr81i8Q5LLVdChzLx4930NKEw_qvBQKKE?table=block&amp;id=2be47971-8f34-4d61-be6b-e52186222b23&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-dbe6a740c308412bb31668f1a83ab1b4">现在想要合并px到py，问题是我们怎么设置px和py的距离？</div><div class="notion-text notion-block-d0fdae4058bf4091919b2cf47ec48eec">经过观察后发现，合并后x→px→py和x→y→py的距离应该相同，如果不相同就矛盾，因此可以得到<code class="notion-inline-code">d[px] =  w + d[y] - d[x]</code>，这是一般情况下权值的计算方式。</div><div class="notion-text notion-block-fafb083d4b5d4c24999f1e8b36401ef5">在实际题目中，可以根据题目的描述来修改合并方式。</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-4cc23306a71442f68095b46fd43c4de6" data-id="4cc23306a71442f68095b46fd43c4de6"><span><div id="4cc23306a71442f68095b46fd43c4de6" class="notion-header-anchor"></div><a class="notion-hash-link" href="#4cc23306a71442f68095b46fd43c4de6" title="find函数"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">find函数</span></span></h3><div class="notion-text notion-block-813a189dfdef4377ae269fee0dc5c5aa">需要注意的是这里的find函数，一开始<code class="notion-inline-code">d[x]</code>实际上表示的是当前节点离<code class="notion-inline-code">p[x]</code>的距离，而当进行路径压缩后，<code class="notion-inline-code">p[x]</code> 就是这个集合的祖先节点，那么<code class="notion-inline-code">d[x]</code> 的含义就变为了当前节点到祖先节点的距离。所以我们必须先进行路径压缩，才能更新<code class="notion-inline-code">d[x]</code></div><pre class="notion-code language-c++"><code class="language-c++"><span class="token comment">// 返回x的祖宗节点</span>
int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">!=</span> x<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
				<span class="token comment">// 寻找祖先节点</span>
        int u <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">+=</span> d<span class="token punctuation">[</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 更新到祖先节点的距离</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> u<span class="token punctuation">;</span> <span class="token comment">// 修改为祖先节点</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-12e692f3341a4ff8a5b05772cf40fc2d"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F28e39de8-cdfa-4268-9101-52b9b8da85fb%2FUntitled.png%3Fid%3D12e692f3-341a-4ff8-a5b0-5772cf40fc2d%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DrhoIngKaf0H7TijHzoMEOkHNuNieYr_2d0ZXHjolphQ?table=block&amp;id=12e692f3-341a-4ff8-a5b0-5772cf40fc2d&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-00ce0b8a8c07434a923e94bbb1b18d17">如果先更新<code class="notion-inline-code">d[x]</code>，再进行压缩，那么就会得到错误的数据：</div><pre class="notion-code language-c++"><code class="language-c++">int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">!=</span> x<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">+=</span> d<span class="token punctuation">[</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 没有经过路径压缩的 d[p[x]]，只是父节点到其父节点的距离</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-5c8df75c5e47490bbee636d3a0b87bb2">这样写就不能正确更新了。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-e66f4254048a445e872153bfc17dc7f5"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F27db6022-e584-4dbe-8cf8-06cc84109e28%2FUntitled.png%3Fid%3De66f4254-048a-445e-8721-53bfc17dc7f5%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DAAKbqd59IxBe_TADug5znNn_wMPWmE57wuMmNwh2PBY?table=block&amp;id=e66f4254-048a-445e-8721-53bfc17dc7f5&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-3213415af97a4e7a961768e328fe6c9b" data-id="3213415af97a4e7a961768e328fe6c9b"><span><div id="3213415af97a4e7a961768e328fe6c9b" class="notion-header-anchor"></div><a class="notion-hash-link" href="#3213415af97a4e7a961768e328fe6c9b" title="种类并查集"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">种类并查集</span></span></h2><div class="notion-text notion-block-70e96fad90c242f0a9da9cb2c38036ca">上面我们介绍的并查集，维护的信息都是具有连通性、传递性的，比如说朋友的朋友是朋友。但有时候题目要求我们维护敌人的敌人是朋友这种关系，种类并查集就是为了解决这个问题而诞生的。</div><div class="notion-text notion-block-91f4b90167004762811ea63bb26ec35b">假设需要总元素个数是n，题目需要将维护朋友和敌人两种关系，那么我们就开2n的空间，这里假设n=4。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-0144989bb6ee45588cf4f0e5be7bfe91"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:492px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F3bfbcc56-e3fb-4ced-b2b1-1d2b28c06d68%2FUntitled.png%3Fid%3D0144989b-b6ee-4558-8cf4-f0e5be7bfe91%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3Ds8xch-f0Ug-b3D1HHJHXSNynXrxiW1dpl8XiwuzXT_k?table=block&amp;id=0144989b-b6ee-4558-8cf4-f0e5be7bfe91&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-09f05433628f464a964ef366c76a4103">我们用1~4维护<b>朋友</b>关系（比如说关在同一个监狱的狱友），用5~8维护<b>敌人</b>关系（比如说关在不同监狱的仇人）。现在假如我们得到信息：1和2是敌人，应该怎么办？</div><div class="notion-text notion-block-ae650b1660754857b284631c85505ee5">我们<code class="notion-inline-code">merge(1, 2+n), merge(1+n, 2);</code>。这里的意思就是：1是2的敌人，2是1的敌人</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-f27b8f0c97d14edea9e66b84d67bd33f"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:492px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F66470aa1-ca10-4971-9c27-583323d07a39%2FUntitled.png%3Fid%3Df27b8f0c-97d1-4ede-a9e6-6b84d67bd33f%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DeY5z0qn5V4huBgDHmtP60Eo77cMzDTohjJt3I6TLZ4A?table=block&amp;id=f27b8f0c-97d1-4ede-a9e6-6b84d67bd33f&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-b3cec4c5000e484a8c4fa8422a6991de">假设2和4又是敌人，我们同样<code class="notion-inline-code">merge(2, 4+n), merge(2+n, 4)</code></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-4f97c576a7c94239b29187a205470098"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:492px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F590a4762-9e66-4cf0-887d-57bce3af9861%2FUntitled.png%3Fid%3D4f97c576-a7c9-4239-b291-87a205470098%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DR0yEmLkSuCEVGrcRfXpaJfoRltFd3NpYO23qRlzjWJI?table=block&amp;id=4f97c576-a7c9-4239-b291-87a205470098&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-6a31c330a1374ff7986f08c9469e167c">由于敌人的敌人是朋友，我们可以得出1和4是朋友。当我们查询1和4是否属于同一集合的时候，得到的结果是true。</div><div class="notion-text notion-block-5741c6e4f7d44565806eef6864edc947">通过上面的例子我们可以总结一般性规律：</div><ul class="notion-list notion-list-disc notion-block-2cabc407c5b74edbafc1e51458463f56"><li>题目有<code class="notion-inline-code">n</code>个元素，有<code class="notion-inline-code">m</code>种类别，则我们需要开<code class="notion-inline-code">n * m</code>的空间</li></ul><ul class="notion-list notion-list-disc notion-block-85cc7b92cadf4ef699db9d425effaa55"><li><code class="notion-inline-code">1*n, 2*n, 3*n, …, m*n</code> 分别代表不同的类别</li></ul><ul class="notion-list notion-list-disc notion-block-841a3111c6374d0d839f37baaeed5dc1"><li>通过链接不同类别的节点，可以描述某种关系。</li></ul><div class="notion-blank notion-block-4ec0d426bb214a16a9a0d6a5b4286a16"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-3ddf3fea1f334e209cecc5acd4cfb1e2" data-id="3ddf3fea1f334e209cecc5acd4cfb1e2"><span><div id="3ddf3fea1f334e209cecc5acd4cfb1e2" class="notion-header-anchor"></div><a class="notion-hash-link" href="#3ddf3fea1f334e209cecc5acd4cfb1e2" title="复杂度"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">复杂度</span></span></h2><div class="notion-text notion-block-0a0845d3c89440d7bc248e6240a7232e">复杂度的详细讨论请看：<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://oi-wiki.org/ds/dsu/#%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6">并查集 - OI Wiki (oi-wiki.org)</a></div><div class="notion-text notion-block-912bc2e8e40840339098cd3784c37b44">默认情况下，我们可以认为并查集的每个操作时间复杂度都是很小的常数，近似<span role="button" tabindex="0" class="notion-equation notion-equation-inline"><span></span></span></div><div class="notion-text notion-block-618d8d55eff64adaaa7103570bda9634">空间复杂度是<span role="button" tabindex="0" class="notion-equation notion-equation-inline"><span></span></span></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-b9538b9a6118488cb7ccb7ca69fe7743" data-id="b9538b9a6118488cb7ccb7ca69fe7743"><span><div id="b9538b9a6118488cb7ccb7ca69fe7743" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b9538b9a6118488cb7ccb7ca69fe7743" title="练习题"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">练习题</span></span></h2><div class="notion-text notion-block-7b6da535136f47a69aec7b960d9a4f9e">下面我们看一些经典的并查集题目，来了解并查集的应用：</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-81026ead852e4099994b00e4788d79a5" data-id="81026ead852e4099994b00e4788d79a5"><span><div id="81026ead852e4099994b00e4788d79a5" class="notion-header-anchor"></div><a class="notion-hash-link" href="#81026ead852e4099994b00e4788d79a5" title="银河英雄传说"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">银河英雄传说</span></span></h3><div class="notion-text notion-block-65f4474a8ee64fcfa628200a5986eda0"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.acwing.com/problem/content/description/240/">238. 银河英雄传说 - AcWing题库</a></div><div class="notion-text notion-block-091f7d2b41b24cf7976ca373a343a0e0">题目大意：</div><div class="notion-text notion-block-33f23196f0bb42afbab0232af7c612bb">N个飞船分布在N列，给定两种操作：</div><div class="notion-text notion-block-4cf0d2c1f77e4c8bbb13371ca56ffba5"><code class="notion-inline-code">M i j</code> 将第i列的飞船保持原有顺序，接在第j列的<b>尾部</b></div><div class="notion-text notion-block-3bb6fcc3857d4d9aa618b3daa115cbec"><code class="notion-inline-code">C i j</code> 询问第i号战舰与第j号战舰是否处于同一列，如果同一列，输出它们所隔的战舰。</div><div class="notion-text notion-block-4d1666116d7a4871846684b059c9605d">可以看到题目有很强的集合特征，用并查集来做最合适了</div><div class="notion-text notion-block-a7664f0bbf5d49ecaae463ad114debe0">对于<code class="notion-inline-code">M</code>操作，我们执行合并，对于<code class="notion-inline-code">C</code>操作，我们查询<code class="notion-inline-code">i</code>和<code class="notion-inline-code">j</code>是否是同一个集合，如果不是输出-1，否则输出<code class="notion-inline-code">abs(d[i] - d[j]) - 1</code>，其中<code class="notion-inline-code">d[i]</code>表示<code class="notion-inline-code">i</code>节点到祖先节点的距离</div><div class="notion-text notion-block-d068478b61b14b5c97cb07071b160993">初始化时，将<code class="notion-inline-code">d[i]</code>初始化为1，在我们执行合并的时候，假设要执行<code class="notion-inline-code">M 3 2</code>，当前状态如图：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-4c23debe108943df92cce5bf688b0795"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F7351ebc1-cfc8-4151-ba15-364261d1b233%2FUntitled.png%3Fid%3D4c23debe-1089-43df-92cc-e5bf688b0795%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DdSiUIze-PuAmMFjRdRNpP-SDoJT-OoIeZGqIIuquGLk?table=block&amp;id=4c23debe-1089-43df-92cc-e5bf688b0795&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-f2906ef88cb44ba2bcdfe5cae358027a">那么<code class="notion-inline-code">3→2</code>这条边，值应该是多少呢？</div><div class="notion-text notion-block-0fbc1110f37b43259d658c5075603ed1">考虑合并后，<code class="notion-inline-code">3</code>和<code class="notion-inline-code">6</code>的祖先节点均变为<code class="notion-inline-code">4</code>，而M操作要求我们把节点接在目标集合的<b>尾部</b>，那么<code class="notion-inline-code">3→2</code>的权值应该更新为4，同样地，6→3的权值应该加上4。</div><div class="notion-text notion-block-12f8f23453d343e399b1bf182eaad004">我们把结论变得更具有概括性：执行<code class="notion-inline-code">M i j</code>操作时，属于<code class="notion-inline-code">i</code>集合的节点的权值，应该加上<code class="notion-inline-code">j</code>集合的元素个数<code class="notion-inline-code">s[j]</code>。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-691f11ea11f74309ab25939306d7871e"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:432px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fe3e91a81-2871-4d11-a1b8-65d6a3fcb4b5%2FUntitled.png%3Fid%3D691f11ea-11f7-4309-ab25-939306d7871e%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DaXQF9j-nYynDY94p8RIQA6ks8sp7bmOhkMi76TPSeAE?table=block&amp;id=691f11ea-11f7-4309-ab25-939306d7871e&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-8eeed060fab44007b0808848ae81d043">现在问题是怎么给<code class="notion-inline-code">i</code>集合的所有权值加上<code class="notion-inline-code">s[j]</code> 呢？我们需要找出所有属于<code class="notion-inline-code">i</code>集合节点的元素然后添加<code class="notion-inline-code">s[j]</code>吗？实际上并查集的路径压缩能很方便的实现这个操作。</div><div class="notion-text notion-block-883b515ddf294456905c24277da806d6">还是上面的例子，一开始<code class="notion-inline-code">d[3] = 0</code> 当我们执行<code class="notion-inline-code">M 3 2</code> 的时候，会设置<code class="notion-inline-code">d[3] = s[4]</code> 。只需要执行这步操作后，再将来find的时候，由于路径压缩的原因：</div><pre class="notion-code language-c++"><code class="language-c++">int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
		<span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">!=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span>
		<span class="token punctuation">{</span>
				int u <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">+=</span> d<span class="token punctuation">[</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 路径压缩，更新d[x]</span>
				p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> u<span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
		<span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-95d30863e8974717ba293a2e3d732ae2">当我们<code class="notion-inline-code">find(6)</code> 的时候，其中<code class="notion-inline-code">d[x] += d[p[x]]</code> 这步操作，会把先前更新的<code class="notion-inline-code">d[3]</code>累加到<code class="notion-inline-code">d[6]</code> 上。从而保证了从6出发，到祖先节点的<code class="notion-inline-code">d</code>都能被正确更新。</div><div class="notion-text notion-block-e21980d357f4481481d04a3ff3f65555">总而言之我们只需要修改<code class="notion-inline-code">i</code>集合祖先节点的<code class="notion-inline-code">d</code>，后续就能通过<code class="notion-inline-code">find</code>操作来更新集合中所有节点的<code class="notion-inline-code">d</code>。</div><div class="notion-text notion-block-832b9e90f4864a02ac549538494a892a">下面我们就可以根据分析内容和带权并查集的模板写出代码了：</div><details class="notion-toggle notion-block-64d8a066268c43d4a0857624ffd9b45a"><summary>答案</summary><div><pre class="notion-code language-c++"><code class="language-c++">#include <span class="token operator">&lt;</span>iostream<span class="token operator">></span>
using namespace std<span class="token punctuation">;</span>
<span class="token keyword">const</span> int <span class="token constant">N</span> <span class="token operator">=</span> <span class="token number">1e4</span> <span class="token operator">*</span> <span class="token number">3</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">;</span>
int p<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> d<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> s<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// s表示size，仅有祖先节点才有效</span>
int t<span class="token punctuation">,</span> a<span class="token punctuation">,</span> b<span class="token punctuation">;</span>
char m<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token constant">N</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i<span class="token punctuation">;</span>
        s<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">!=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        int u <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">+=</span> d<span class="token punctuation">[</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> u<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

int <span class="token function">merge</span><span class="token punctuation">(</span><span class="token parameter">int x<span class="token punctuation">,</span> int y</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    int px <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">,</span> py <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    p<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> py<span class="token punctuation">;</span>
    d<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> s<span class="token punctuation">[</span>py<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 应该设置为y所属集合的节点数</span>
    s<span class="token punctuation">[</span>py<span class="token punctuation">]</span> <span class="token operator">+=</span> s<span class="token punctuation">[</span>px<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

int <span class="token function">query</span><span class="token punctuation">(</span><span class="token parameter">int x<span class="token punctuation">,</span> int y</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    int px <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">,</span> py <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>px <span class="token operator">!=</span> py<span class="token punctuation">)</span>
        <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token function">max</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token function">abs</span><span class="token punctuation">(</span>d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">-</span> d<span class="token punctuation">[</span>y<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 有可能x = y</span>
<span class="token punctuation">}</span>
int <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> t<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%s%d%d"</span><span class="token punctuation">,</span> m<span class="token punctuation">,</span> <span class="token operator">&amp;</span>a<span class="token punctuation">,</span> <span class="token operator">&amp;</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>m<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token string">'M'</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">find</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token function">find</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token keyword">continue</span><span class="token punctuation">;</span>
            <span class="token function">merge</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">else</span>
        <span class="token punctuation">{</span>
            <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%d\n"</span><span class="token punctuation">,</span> <span class="token function">query</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> b<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div></details><div class="notion-blank notion-block-6c0679bef8ba452fab7510d4344ff0a2"> </div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-1bf31868400841d7b91cb408559a2298" data-id="1bf31868400841d7b91cb408559a2298"><span><div id="1bf31868400841d7b91cb408559a2298" class="notion-header-anchor"></div><a class="notion-hash-link" href="#1bf31868400841d7b91cb408559a2298" title="食物链"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">食物链</span></span></h3><div class="notion-text notion-block-d2ee2cb64a9d407788b6089df270870b"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.acwing.com/problem/content/242/">240. 食物链 - AcWing题库</a></div><div class="notion-text notion-block-81687515bef0485b96ab9e33922958e5"><b>带权并查集</b></div><div class="notion-text notion-block-fdb84352872c4e3285b1cbccaa3512ff">这道题可以用带权并查集做，我们假设此时的权值<code class="notion-inline-code">d[i]</code>的含义是当前节点<code class="notion-inline-code">i</code>和祖先节点的关系。</div><div class="notion-text notion-block-05298e8820224ac3b47d4ae8c22aa29d">根据题目的定义，无非是三种关系：同类、吃、被吃，我们用0，1，2分别来表示这三种关系：</div><ul class="notion-list notion-list-disc notion-block-545e7c776d82425c927df88e0c397ef6"><li>d[i] = 0 → 和根节点同类</li></ul><ul class="notion-list notion-list-disc notion-block-1617e45acb654b1598c1d6c82b927e8f"><li>d[i] = 1 → 吃根节点</li></ul><ul class="notion-list notion-list-disc notion-block-90432622ab424af4aaa2fd48770b20c0"><li>d[i] = 2 → 被根节点吃</li></ul><div class="notion-text notion-block-b0fc6ad4de5a4a2e9dedff36467f3659">举一个例子，下面图中，x是祖先节点，<code class="notion-inline-code">d[y] = 1</code> 表示y吃根节点，<code class="notion-inline-code">d[z] = 2</code> 表示z被根节点吃，<code class="notion-inline-code">d[k] = 3, d[k] % 3 = 0</code> 表示k和x是同类。我们可以根据d的值推断出任意两个节点的关系，比如说z和y就是z吃y的关系，而k和y是同类。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-27f6dfc967304cfe8f19822c64a780a4"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:240px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fc2c24577-e41a-44b5-bc28-54d61b6d6d43%2FUntitled.png%3Fid%3D27f6dfc9-6730-4cfe-8f19-822c64a780a4%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D25Svm32qXtIMIyJkhfJVxtvq28EpoaW273yxGGyO12o?table=block&amp;id=27f6dfc9-6730-4cfe-8f19-822c64a780a4&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-328f0f9998cd42b5ac4af5cd332fa66f">根据上面的定义，我们现在考虑任意两个节点<code class="notion-inline-code">x</code>和<code class="notion-inline-code">y</code>, <code class="notion-inline-code">x ≠ y</code></div><ul class="notion-list notion-list-disc notion-block-0b803b7419bd4178b6d3e5cb9902a079"><li>如果x和y在同一集合</li><ul class="notion-list notion-list-disc notion-block-0b803b7419bd4178b6d3e5cb9902a079"><li>如果x和y是同类，那么<code class="notion-inline-code">(d[x] - d[y]) % 3 = 0</code> 成立。</li><ul class="notion-list notion-list-disc notion-block-f8b4155965e647d69292f328fc92890a"><li>这里表示x和y都和根节点是同类，因此x和y是同类</li></ul><li>如果x吃y，那么<code class="notion-inline-code">(d[x] - d[y] - 1) % 3 = 0</code> 成立</li><ul class="notion-list notion-list-disc notion-block-74adbc7e6e7f4c97adc4ec2fbc6a1be1"><li>这里表示x吃根节点，而y和根节点是同类，因此x吃y</li></ul><li>如果x被y吃，那么<code class="notion-inline-code">(d[x] - d[y] - 2) % 3 = 0</code> 成立</li><div class="notion-text notion-block-8a004dc9740645a5910769a8cfd51275">这里需要取模，是因为d[x]的值很有可能超过3，所以我们用%3来约束d[i]的值域，便于判断。</div></ul></ul><ul class="notion-list notion-list-disc notion-block-1a8b46df25894f9781f3fd72b292d8fd"><li>如果x和y不在同一个集合，则需要合并，方便以后判断</li><ul class="notion-list notion-list-disc notion-block-1a8b46df25894f9781f3fd72b292d8fd"><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-dcac6f7b41254cdc8ffbcf00da99094b"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:480px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fdff9c13f-5f05-4148-a7da-957ea5b5473d%2FUntitled.png%3Fid%3Ddcac6f7b-4125-4cdc-8ffb-cf00da99094b%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DRWgdETCn1dRKRKLbePiLYfWO7ktCEaptntG9dupZYAY?table=block&amp;id=dcac6f7b-4125-4cdc-8ffb-cf00da99094b&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-8cf5408029864de79d58f92d648ea1f1">比如说上面的情况，给定x和y，以及它们的祖先节点px和py，还有距离d[x]和d[y]，想要合并px到py，那么d[px]应该等于多少呢？</div><li>如果x和y是同类，那么<code class="notion-inline-code">(d[x] + d[px] - d[y]) % 3 = 0</code> 成立，可以推断出<code class="notion-inline-code">d[px] = (d[y] - d[x]) %3</code></li><li>如果x吃y，那么<code class="notion-inline-code">(d[x] + d[px] - d[y] - 1) % 3 = 0</code> 成立，可以推断出<code class="notion-inline-code">d[px] = (d[y] - d[x] + 1) %3</code></li></ul></ul><div class="notion-text notion-block-974f7ab926ed46599131d4b7fd4847d7">经过上面讨论，我们已经可以判断任意两个节点之间的关系了，接下来就可以写出答案了。</div><div class="notion-text notion-block-b380f7f7fee74976915cca416e50da3e">题目给出的两类语句：</div><div class="notion-text notion-block-e2815dda276342fc9915f1f814f0752e"><code class="notion-inline-code">1 x y</code>表示x和y是同类</div><div class="notion-text notion-block-62305182f3514311b01934681c3524f0"><code class="notion-inline-code">2 x y</code>表示x吃y</div><div class="notion-text notion-block-1d567d6798704627a86d56507b9f6fc8">在我们拿到一个语句的时候，我们先判断这个语句是否是假话，如果是假话，我们什么都不做，否则的话当前话是真话，我们根据真话来执行相应操作，比如说如果x和y属于不同集合，且当前话是真话，那么就要合并x和y的两个集合，方便后续判断</div><details class="notion-toggle notion-block-5814d0ee12414376a664f600c2657c3d"><summary>答案</summary><div><pre class="notion-code language-c++"><code class="language-c++">#include <span class="token operator">&lt;</span>iostream<span class="token operator">></span>
using namespace std<span class="token punctuation">;</span>
<span class="token keyword">const</span> int <span class="token constant">N</span> <span class="token operator">=</span> <span class="token number">50010</span><span class="token punctuation">,</span> <span class="token constant">M</span> <span class="token operator">=</span> <span class="token number">1e5</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">;</span>
int p<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> d<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
int res<span class="token punctuation">;</span>
int n<span class="token punctuation">,</span> k<span class="token punctuation">;</span>
<span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token constant">N</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">!=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        int u <span class="token operator">=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">+=</span> d<span class="token punctuation">[</span>u<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

int <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>n<span class="token punctuation">,</span> <span class="token operator">&amp;</span>k<span class="token punctuation">)</span><span class="token punctuation">;</span>
    int c<span class="token punctuation">,</span> x<span class="token punctuation">,</span> y<span class="token punctuation">;</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span>k<span class="token operator">--</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>c<span class="token punctuation">,</span> <span class="token operator">&amp;</span>x<span class="token punctuation">,</span> <span class="token operator">&amp;</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">></span> n <span class="token operator">||</span> y <span class="token operator">></span> n<span class="token punctuation">)</span>
            res<span class="token operator">++</span><span class="token punctuation">;</span>
        <span class="token keyword">else</span>
        <span class="token punctuation">{</span>
            int px <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">,</span> py <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                <span class="token comment">// 如果x和y处于同一集合，且x和y的关系不是同类，那么当前是假话</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span>px <span class="token operator">==</span> py <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">-</span> d<span class="token punctuation">[</span>y<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">3</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>
                    res<span class="token operator">++</span><span class="token punctuation">;</span>
                <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>px <span class="token operator">!=</span> py<span class="token punctuation">)</span> <span class="token comment">// 如果不是假话，那么我们根据需要来合并</span>
                <span class="token punctuation">{</span>
                    <span class="token comment">// 如果x和y不属于同一个集合，那么合并两个集合</span>
                    p<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> py<span class="token punctuation">;</span>
                    d<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">(</span>d<span class="token punctuation">[</span>y<span class="token punctuation">]</span> <span class="token operator">-</span> d<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">3</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
            <span class="token keyword">else</span>
            <span class="token punctuation">{</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span>px <span class="token operator">==</span> py <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">-</span> d<span class="token punctuation">[</span>y<span class="token punctuation">]</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">3</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>
                    res<span class="token operator">++</span><span class="token punctuation">;</span>
                <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>px <span class="token operator">!=</span> py<span class="token punctuation">)</span>
                <span class="token punctuation">{</span>
                    <span class="token comment">// 如果x和y不属于同一个集合，那么合并两个集合</span>
                    p<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> py<span class="token punctuation">;</span>
                    d<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">(</span>d<span class="token punctuation">[</span>y<span class="token punctuation">]</span> <span class="token operator">-</span> d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">3</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%d\n"</span><span class="token punctuation">,</span> res<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-blank notion-block-5842aa76afd747a3912f3c9969611135"> </div></div></details><div class="notion-text notion-block-8c08fbdcf6274d85bd0d7fd97c89d043"><b>种类并查集</b></div><div class="notion-text notion-block-be5e096ec2ee40c992515e778fd73f69">当前有三类元素，我们开3n的空间。用i + n来表示i的捕食对象，i+2n来表示i的天敌。</div><div class="notion-text notion-block-0e30ba586a404e4ab02f006f4c4a7621">这三类集合如果存在联系，那么就是某种关系存在，举个例子</div><div class="notion-text notion-block-60e2a5695d0546058c90b8faa44b9a64">如果(x, y + 2n) 存在一条边，那么就表示 y 是 x 的天敌</div><div class="notion-text notion-block-b8fd920c381440cc9398ebec9a991766">如果(x, y + n) 存在一条边，那么就表示 x 吃 y</div><details class="notion-toggle notion-block-6c61980bf47543e4b4b96b05fc7a43af"><summary>答案</summary><div><pre class="notion-code language-c++"><code class="language-c++">#include <span class="token operator">&lt;</span>iostream<span class="token operator">></span>
using namespace std<span class="token punctuation">;</span>
<span class="token keyword">const</span> int <span class="token constant">N</span> <span class="token operator">=</span> <span class="token number">15</span> <span class="token operator">*</span> <span class="token number">1e4</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">;</span>
int n<span class="token punctuation">,</span> k<span class="token punctuation">,</span> res<span class="token punctuation">;</span>
int p<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n <span class="token operator">*</span> <span class="token number">3</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">!=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

bool <span class="token function">query</span><span class="token punctuation">(</span><span class="token parameter">int x<span class="token punctuation">,</span> int y</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">void</span> <span class="token function">merge</span><span class="token punctuation">(</span><span class="token parameter">int x<span class="token punctuation">,</span> int y</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    int px <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">,</span> py <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    p<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> py<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

int <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>n<span class="token punctuation">,</span> <span class="token operator">&amp;</span>k<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    int c<span class="token punctuation">,</span> x<span class="token punctuation">,</span> y<span class="token punctuation">;</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span>k<span class="token operator">--</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>c<span class="token punctuation">,</span> <span class="token operator">&amp;</span>x<span class="token punctuation">,</span> <span class="token operator">&amp;</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">></span> n <span class="token operator">||</span> y <span class="token operator">></span> n<span class="token punctuation">)</span>
            res<span class="token operator">++</span><span class="token punctuation">;</span>
        <span class="token keyword">else</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                <span class="token comment">// 如果x吃y或者x被y吃，假话</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">query</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y <span class="token operator">+</span> n<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">query</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y <span class="token operator">+</span> <span class="token number">2</span> <span class="token operator">*</span> n<span class="token punctuation">)</span><span class="token punctuation">)</span>
                    res<span class="token operator">++</span><span class="token punctuation">;</span>
                <span class="token keyword">else</span>
                <span class="token punctuation">{</span>
                    <span class="token comment">// 否则x和y同类，则x的猎物是y的猎物，x的天敌是y的天敌</span>
                    <span class="token function">merge</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token function">merge</span><span class="token punctuation">(</span>x <span class="token operator">+</span> n<span class="token punctuation">,</span> y <span class="token operator">+</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token function">merge</span><span class="token punctuation">(</span>x <span class="token operator">+</span> <span class="token number">2</span> <span class="token operator">*</span> n<span class="token punctuation">,</span> y <span class="token operator">+</span> <span class="token number">2</span> <span class="token operator">*</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
            <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">2</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                <span class="token comment">// 如果x和y是同类，或者y是x的天敌，假话</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">query</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">query</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y <span class="token operator">+</span> <span class="token number">2</span> <span class="token operator">*</span> n<span class="token punctuation">)</span><span class="token punctuation">)</span>
                    res<span class="token operator">++</span><span class="token punctuation">;</span>
                <span class="token keyword">else</span>
                <span class="token punctuation">{</span>
                    <span class="token comment">// 否则x吃y是真的</span>
                    <span class="token function">merge</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y <span class="token operator">+</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span>         <span class="token comment">// x吃y</span>
                    <span class="token function">merge</span><span class="token punctuation">(</span>x <span class="token operator">+</span> n<span class="token punctuation">,</span> y <span class="token operator">+</span> <span class="token number">2</span> <span class="token operator">*</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// x的猎物吃y的猎物（传递性）</span>
                    <span class="token function">merge</span><span class="token punctuation">(</span>x <span class="token operator">+</span> <span class="token number">2</span> <span class="token operator">*</span> n<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>     <span class="token comment">// y吃x的天敌</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%d\n"</span><span class="token punctuation">,</span> res<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-0951220dfb3d4d439f38766d9c02bec9">这里关键的地方就是merge的选择了，如果是同类的话很好理解，如果x吃y的话，我们要顺着这个逻辑把所有边都merge，不然就没法通过了。</div><div class="notion-blank notion-block-dc1e95d8464649e1934254b43ab51841"> </div></div></details><div class="notion-blank notion-block-d82da6793039491ab1b515372a116f36"> </div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-8120be68a9714082927c98b972b45ecb" data-id="8120be68a9714082927c98b972b45ecb"><span><div id="8120be68a9714082927c98b972b45ecb" class="notion-header-anchor"></div><a class="notion-hash-link" href="#8120be68a9714082927c98b972b45ecb" title="关押罪犯"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">关押罪犯</span></span></h3><div class="notion-text notion-block-dabd88d85be14d198fd3dfa4569f246a"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.acwing.com/problem/content/description/259/">257. 关押罪犯 - AcWing题库</a></div><div class="notion-text notion-block-efea4489810b41c0a3075d181ac2d92a"><b>种类并查集</b></div><div class="notion-text notion-block-ec1d6e05f7da4e63ac09c6bac4f2a0c9">我们可以按照边权降序排序，优先将大的边分布在集合之间，就是优先把大冲突的两个节点关在不同监狱，直到无法这么做（无法安排在不同监狱），此时的冲突就是最小的冲突。</div><div class="notion-text notion-block-13fe00e8897d419cb5efe362468f3603">显然我们可以分为两类元素，那么需要2 * n的空间。n ~ 2*n 表示敌人。</div><details class="notion-toggle notion-block-36db7c5498494c1ba50c12789e4b112f"><summary>答案</summary><div><div class="notion-blank notion-block-eb015261ea934d12bf6947f4af32c5b4"> </div><pre class="notion-code language-c++"><code class="language-c++">#include <span class="token operator">&lt;</span>iostream<span class="token operator">></span>
#include <span class="token operator">&lt;</span>algorithm<span class="token operator">></span>
using namespace std<span class="token punctuation">;</span>
<span class="token keyword">const</span> int <span class="token constant">N</span> <span class="token operator">=</span> <span class="token number">4</span> <span class="token operator">*</span> <span class="token number">1e4</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token constant">M</span> <span class="token operator">=</span> <span class="token number">1e5</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">;</span>
int p<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
struct Edge
<span class="token punctuation">{</span>
    int x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">;</span>
    bool operator<span class="token operator">&lt;</span><span class="token punctuation">(</span><span class="token keyword">const</span> Edge <span class="token operator">&amp;</span><span class="token constant">W</span><span class="token punctuation">)</span> <span class="token keyword">const</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> w <span class="token operator">></span> <span class="token constant">W</span><span class="token punctuation">.</span>w<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

<span class="token punctuation">}</span> edges<span class="token punctuation">[</span><span class="token constant">M</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">!=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

bool <span class="token function">query</span><span class="token punctuation">(</span><span class="token parameter">int x<span class="token punctuation">,</span> int y</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">void</span> <span class="token function">merge</span><span class="token punctuation">(</span><span class="token parameter">int x<span class="token punctuation">,</span> int y</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    int px <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">,</span> py <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    p<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> py<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

int <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token constant">N</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i<span class="token punctuation">;</span>
    int n<span class="token punctuation">,</span> m<span class="token punctuation">;</span>
    int x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">;</span>
    <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>n<span class="token punctuation">,</span> <span class="token operator">&amp;</span>m<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>x<span class="token punctuation">,</span> <span class="token operator">&amp;</span>y<span class="token punctuation">,</span> <span class="token operator">&amp;</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span>
        edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span>x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token function">sort</span><span class="token punctuation">(</span>edges<span class="token punctuation">,</span> edges <span class="token operator">+</span> m<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        x <span class="token operator">=</span> edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>x<span class="token punctuation">,</span> y <span class="token operator">=</span> edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>y<span class="token punctuation">,</span> w <span class="token operator">=</span> edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>w<span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">query</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 试图把处于同一集合的元素分开，矛盾</span>
        <span class="token punctuation">{</span>
            <span class="token comment">// 此时的冲突就是无法避免的，输出</span>
            <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%d\n"</span><span class="token punctuation">,</span> w<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token comment">// 标记敌人关系</span>
        <span class="token function">merge</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y <span class="token operator">+</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">merge</span><span class="token punctuation">(</span>x <span class="token operator">+</span> n<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"0\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div></details><div class="notion-text notion-block-437a058419f24d3cb6e4a14f02291a46"><b>带权并查集</b></div><div class="notion-text notion-block-c75d4478a246490880e65fcd641aaeb7">这道题目也可以用带权并查集+二分来做。</div><div class="notion-text notion-block-d73a0a131ba04b1e9beb3e6999849cb1">假设答案是mid，那么所有大于mid的边中，边的两端x和y是不连通的，就是说它们属于不同的类别。那么我们可以使用二分来枚举mid，使用一个<code class="notion-inline-code">check()</code>函数来判断当前枚举值是否可行。</div><div class="notion-text notion-block-bc26e95d09334ddaa2a79ab1c320f88a"><code class="notion-inline-code">check</code> 函数的细节就是，对于所有大于mid的边，如果我们发现两个端点是同一类中，那么就返回false，重复直到没有边，返回true。</div><div class="notion-text notion-block-d0975a03c4244e1a81a5b6d171a42165">如何描述两个点是否属于同一类，可以用带权并查集来实现，0 和 1 分别表示两个类别。</div><div class="notion-text notion-block-256aed8f36214ec5be05bc67e5529c5d">接下来我们可以写出代码：</div><details class="notion-toggle notion-block-83cc3b6e02ea45d3825184b30b319848"><summary>答案</summary><div><div class="notion-text notion-block-865f1a1e96244b3abbdeb59ba6bff796">这里的一个小技巧是用xor来模拟无进位加法，值域限制在0和1，代替%2的操作</div><pre class="notion-code language-c++"><code class="language-c++">#include <span class="token operator">&lt;</span>cstring<span class="token operator">></span>
#include <span class="token operator">&lt;</span>iostream<span class="token operator">></span>
using namespace std<span class="token punctuation">;</span>
<span class="token keyword">const</span> int <span class="token constant">N</span> <span class="token operator">=</span> <span class="token number">2</span> <span class="token operator">*</span> <span class="token number">1e4</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token constant">M</span> <span class="token operator">=</span> <span class="token number">1e5</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">;</span>
int n<span class="token punctuation">,</span> m<span class="token punctuation">,</span> x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">;</span>
int p<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> d<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
struct Edge
<span class="token punctuation">{</span>
    int x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">;</span>
<span class="token punctuation">}</span> edges<span class="token punctuation">[</span><span class="token constant">M</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">memset</span><span class="token punctuation">(</span>d<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> sizeof d<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">!=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        int u <span class="token operator">=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">^=</span> d<span class="token punctuation">[</span>u<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

bool <span class="token function">check</span><span class="token punctuation">(</span><span class="token parameter">int mid</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        x <span class="token operator">=</span> edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>x<span class="token punctuation">,</span> y <span class="token operator">=</span> edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>y<span class="token punctuation">,</span> w <span class="token operator">=</span> edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>w<span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>w <span class="token operator">&lt;=</span> mid<span class="token punctuation">)</span>
            <span class="token keyword">continue</span><span class="token punctuation">;</span>
        int px <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">,</span> py <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>px <span class="token operator">==</span> py<span class="token punctuation">)</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">^</span> d<span class="token punctuation">[</span>y<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// xor 结果为0，表示它们属于同一类</span>
                <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>px <span class="token operator">!=</span> py<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            p<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> py<span class="token punctuation">;</span>
            d<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> d<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">^</span> d<span class="token punctuation">[</span>y<span class="token punctuation">]</span> <span class="token operator">^</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 因为是不同类，所以要 ^ 1</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
int <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>n<span class="token punctuation">,</span> <span class="token operator">&amp;</span>m<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>x<span class="token punctuation">,</span> <span class="token operator">&amp;</span>y<span class="token punctuation">,</span> <span class="token operator">&amp;</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span>
        edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span>x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    int l <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> r <span class="token operator">=</span> <span class="token number">1e9</span><span class="token punctuation">;</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span>l <span class="token operator">&lt;</span> r<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        int mid <span class="token operator">=</span> l <span class="token operator">+</span> r <span class="token operator">>></span> <span class="token number">1</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">check</span><span class="token punctuation">(</span>mid<span class="token punctuation">)</span><span class="token punctuation">)</span>
            r <span class="token operator">=</span> mid<span class="token punctuation">;</span>
        <span class="token keyword">else</span>
            l <span class="token operator">=</span> mid <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%d"</span><span class="token punctuation">,</span> r<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div></details><div class="notion-text notion-block-bc3bba95e53743c39479f3c46665828b">当然这里也可以把带权并查集换成种类并查集，同样用二分解决，不过直接用种类并查集的性质比较优雅。</div><details class="notion-toggle notion-block-dadd6171c23f4c47b2c2b2ed76a7280c"><summary>答案</summary><div><pre class="notion-code language-c++"><code class="language-c++">#include <span class="token operator">&lt;</span>cstring<span class="token operator">></span>
#include <span class="token operator">&lt;</span>iostream<span class="token operator">></span>
using namespace std<span class="token punctuation">;</span>
<span class="token keyword">const</span> int <span class="token constant">N</span> <span class="token operator">=</span> <span class="token number">2</span> <span class="token operator">*</span> <span class="token number">1e4</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token constant">M</span> <span class="token operator">=</span> <span class="token number">1e5</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">;</span>
int n<span class="token punctuation">,</span> m<span class="token punctuation">,</span> x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">;</span>
int p<span class="token punctuation">[</span><span class="token constant">N</span> <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
struct Edge
<span class="token punctuation">{</span>
    int x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">;</span>
<span class="token punctuation">}</span> edges<span class="token punctuation">[</span><span class="token constant">M</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token parameter">int n</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">2</span> <span class="token operator">*</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

int <span class="token function">find</span><span class="token punctuation">(</span><span class="token parameter">int x</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">!=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        int u <span class="token operator">=</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
        p<span class="token punctuation">[</span>x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> p<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

bool <span class="token function">query</span><span class="token punctuation">(</span><span class="token parameter">int x<span class="token punctuation">,</span> int y</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

<span class="token keyword">void</span> <span class="token function">merge</span><span class="token punctuation">(</span><span class="token parameter">int x<span class="token punctuation">,</span> int y</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    int px <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">,</span> py <span class="token operator">=</span> <span class="token function">find</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    p<span class="token punctuation">[</span>px<span class="token punctuation">]</span> <span class="token operator">=</span> py<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

bool <span class="token function">check</span><span class="token punctuation">(</span><span class="token parameter">int mid</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">init</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        x <span class="token operator">=</span> edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>x<span class="token punctuation">,</span> y <span class="token operator">=</span> edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>y<span class="token punctuation">,</span> w <span class="token operator">=</span> edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>w<span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>w <span class="token operator">&lt;=</span> mid<span class="token punctuation">)</span>
            <span class="token keyword">continue</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">query</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
        <span class="token function">merge</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y <span class="token operator">+</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">merge</span><span class="token punctuation">(</span>x <span class="token operator">+</span> n<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
int <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>n<span class="token punctuation">,</span> <span class="token operator">&amp;</span>m<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%d%d%d"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>x<span class="token punctuation">,</span> <span class="token operator">&amp;</span>y<span class="token punctuation">,</span> <span class="token operator">&amp;</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span>
        edges<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span>x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    int l <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> r <span class="token operator">=</span> <span class="token number">1e9</span><span class="token punctuation">;</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span>l <span class="token operator">&lt;</span> r<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        int mid <span class="token operator">=</span> l <span class="token operator">+</span> r <span class="token operator">>></span> <span class="token number">1</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">check</span><span class="token punctuation">(</span>mid<span class="token punctuation">)</span><span class="token punctuation">)</span>
            r <span class="token operator">=</span> mid<span class="token punctuation">;</span>
        <span class="token keyword">else</span>
            l <span class="token operator">=</span> mid <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%d"</span><span class="token punctuation">,</span> r<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div></details><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-b196fc372dfe417c9acf5ba7241c08d7" data-id="b196fc372dfe417c9acf5ba7241c08d7"><span><div id="b196fc372dfe417c9acf5ba7241c08d7" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b196fc372dfe417c9acf5ba7241c08d7" title="总结"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">总结</span></span></h2><div class="notion-text notion-block-a64c1481c5e74994adeaf737317faffe">本文介绍了朴素并查集，带权并查集，种类并查集，这里总结一下他们的区别</div><ul class="notion-list notion-list-disc notion-block-a44b2439351142208ff2a1560ec93609"><li>朴素并查集：最常用的一类并查集，用来管理元素的从属关系。</li></ul><ul class="notion-list notion-list-disc notion-block-3594b93dc80a49e3a15189fe6af4f641"><li>带权并查集：在朴素并查集上添加了权重<code class="notion-inline-code">d</code> ，<code class="notion-inline-code">d</code> 可以根据题目的不同来表示不同的含义，使用时需要确定<code class="notion-inline-code">merge</code>和<code class="notion-inline-code">find</code>操作如何更新d</li></ul><ul class="notion-list notion-list-disc notion-block-b5db9287f8cc435aadba181c8a47e566"><li>种类并查集：一般维护若干个n的区间，每个区间表示不同含义，通过merge不同区间的节点，标识某种关系的存在</li></ul><div class="notion-text notion-block-f070095c8ec34b28a2a57d5edbf258b6">一般种类并查集都可以用带权并查集来做，我们定义好d的含义即可，而且只有总节点数的比较小情况下才能用种类并查集，不然开若干个区间可能会溢出。但是种类并查集的思想和含义也比较简洁，有的时候可以利用它的性质来巧妙解题。</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-e03ba5df13e6471e83ed3ee25793e00f" data-id="e03ba5df13e6471e83ed3ee25793e00f"><span><div id="e03ba5df13e6471e83ed3ee25793e00f" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e03ba5df13e6471e83ed3ee25793e00f" title="Ref"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Ref</span></span></h2><div class="notion-text notion-block-662c7741f1d64443b4c116ce5c49f963"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://oi-wiki.org/ds/dsu/">并查集 - OI Wiki (oi-wiki.org)</a></div><div class="notion-text notion-block-c7eac45655aa463db01a056355e78f2f"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.cnblogs.com/zhxmdefj/p/11117791.html">并查集到带权并查集 - zhxmdefj - 博客园 (cnblogs.com)</a></div><div class="notion-text notion-block-3e8cd89133b94337838f63ba277b1b4c"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://zhuanlan.zhihu.com/p/97813717">算法学习笔记(7)：种类并查集 - 知乎 (zhihu.com)</a></div><div class="notion-blank notion-block-91696f2d8f0a4702af8d5cffd04f1e7b"> </div><div class="notion-text notion-block-ed39e1d711f84744a1619243d98033a6">更多例题：</div><div class="notion-text notion-block-0f6af6f45967408a8edb615fa6b814e0"><b><b>hdu1232 城镇交通</b></b></div><div class="notion-text notion-block-f50a8de5daba440abb49ae7ee6ee1128"><b><b>poj1611 感染的学生</b></b></div><div class="notion-text notion-block-9775b549502743c8816c9bf9e4e00188"><b><b>hdu3038</b></b></div><div class="notion-text notion-block-5cb0b6af4f024fe88e7ee631a2dd1eab"><b><b>POJ2492</b></b></div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[求最短路径的基本算法]]></title>
            <link>https://hhmy27.github.io//short-path-problem</link>
            <guid>https://hhmy27.github.io//short-path-problem</guid>
            <pubDate>Sun, 29 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[五种求最短路径的基本算法]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-cf1ae05adc0549ed934badf97088cd5c"><div class="notion-viewport"></div><div class="notion-table-of-contents notion-gray notion-block-c1ea8b8f165640f79e124606bdac87d7"><a href="#195db797754a4d65b26292c2c40b9251" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">简介</span></a><a href="#ca72d4f7c2e44cc68999129aaacb48f7" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">问题分类</span></a><a href="#3771f43d9ca947f394092b934a3c5317" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">图的存储</span></a><a href="#ae9a99dd03274094a30c7f967a6ec4f8" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">邻接矩阵</span></a><a href="#4af6bf982b17443d9b7d858f333ce0ea" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">邻接表</span></a><a href="#9ed4b1e931af48c6912d9bcf41d43fd7" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">单源最短路问题算法</span></a><a href="#454c67cec7214ecf9f61b540bcb88576" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">dijkstra 算法</span></a><a href="#77c270d6028e4f12b79136e62fd59c7d" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">朴素版 dijkstra</span></a><a href="#fe9d2211d4d347acada8419f561785e9" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">堆优化版 dijkstra</span></a><a href="#8de2296e47554ba48f9ca3a7001a7ee8" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">BF 算法</span></a><a href="#e1d3262805a54666a3c883d178e4e158" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">SPFA 算法</span></a><a href="#dfdc1164ec5240018bcee8af894a1d4f" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">负权回路</span></a><a href="#6f05c08985fa4a51b9f8e1b23ab856fe" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">对比 SPFA 和堆优化 dijkstra</span></a><a href="#fb182adbd8d74a2c9abcb9aa44d4e9c8" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">Floyd 算法</span></a><a href="#b9825e52097b495f825051846560a004" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">总结</span></a><a href="#b0eebf59397243c2960a1be9598a6341" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">参考</span></a></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-195db797754a4d65b26292c2c40b9251" data-id="195db797754a4d65b26292c2c40b9251"><span><div id="195db797754a4d65b26292c2c40b9251" class="notion-header-anchor"></div><a class="notion-hash-link" href="#195db797754a4d65b26292c2c40b9251" title="简介"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">简介</span></span></h2><div class="notion-text notion-block-83a4984765d544bfa5f8114e10ec88f0">最短路径问题要求我们给出点与点之间的最短路径，根据问题的分类一共有 5 种解决该类问题的方法。</div><div class="notion-text notion-block-5a0821b2fe8a43a98a4cf70a8d685664">本文将依次介绍每种算法的思想、时间复杂度、适用场景。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-570427a144dd40c28e3dc16b095e4cd0"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fe045bf49-c643-450c-84cb-8d7e80f87827%2FUntitled.png%3Fid%3D570427a1-44dd-40c2-8e3d-c16b095e4cd0%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DlOFZFP3CPaSE33XvAq8WRXmCkTJk9qmREsVkv0Z_pas?table=block&amp;id=570427a1-44dd-40c2-8e3d-c16b095e4cd0&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-ca72d4f7c2e44cc68999129aaacb48f7" data-id="ca72d4f7c2e44cc68999129aaacb48f7"><span><div id="ca72d4f7c2e44cc68999129aaacb48f7" class="notion-header-anchor"></div><a class="notion-hash-link" href="#ca72d4f7c2e44cc68999129aaacb48f7" title="问题分类"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">问题分类</span></span></h2><div class="notion-text notion-block-7e6199289e4c474ead4005e6c3c4362e">最短路问题分为两类：</div><ol start="1" class="notion-list notion-list-numbered notion-block-939c90f4b6ab4520903353d76e46c9d8"><li>单源最短路：求两个点之间的最短路径，一般是 1 到 n 号点</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-40e3e64704db48e9a14fed6fbf29931c"><li>多源汇最短路：求任意两个点之间的最短路径</li></ol><div class="notion-blank notion-block-49925caeef1f4f9ca1cda83133ba9f32"> </div><div class="notion-text notion-block-bba72da2e4b24a7eac88973fc377e261">而单源最短路问题，根据边的不同，又有一些需要注意的地方：</div><ol start="1" class="notion-list notion-list-numbered notion-block-f5eb2c8f48554dc0871019667b3c7f4e"><li>所有边权都是正数：这种情况是最常见的，用 dijkstra 就能求解</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-549c05a473a54e3a822c6122c52fb1cf"><li>边权存在负数：此时 dijkstra 无法求解，只能使用 BF 或者 SPFA 算法求解</li></ol><div class="notion-blank notion-block-40afe4b44bad4ff2b21ee939b8f6288f"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-3771f43d9ca947f394092b934a3c5317" data-id="3771f43d9ca947f394092b934a3c5317"><span><div id="3771f43d9ca947f394092b934a3c5317" class="notion-header-anchor"></div><a class="notion-hash-link" href="#3771f43d9ca947f394092b934a3c5317" title="图的存储"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">图的存储</span></span></h2><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-ae9a99dd03274094a30c7f967a6ec4f8" data-id="ae9a99dd03274094a30c7f967a6ec4f8"><span><div id="ae9a99dd03274094a30c7f967a6ec4f8" class="notion-header-anchor"></div><a class="notion-hash-link" href="#ae9a99dd03274094a30c7f967a6ec4f8" title="邻接矩阵"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">邻接矩阵</span></span></h3><div class="notion-text notion-block-a055e8692264412a8b8802c69974fbc3">最简单的存储方式</div><div class="notion-text notion-block-41bb197800cb461fbb048494ead8a01a">N是点数，一般不会太大，用于存储稀疏图</div><pre class="notion-code language-c++"><code class="language-c++">int g<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-4af6bf982b17443d9b7d858f333ce0ea" data-id="4af6bf982b17443d9b7d858f333ce0ea"><span><div id="4af6bf982b17443d9b7d858f333ce0ea" class="notion-header-anchor"></div><a class="notion-hash-link" href="#4af6bf982b17443d9b7d858f333ce0ea" title="邻接表"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">邻接表</span></span></h3><div class="notion-text notion-block-e1490e3d81a9424a876d185ff40e40f6">又称为链式前向星</div><pre class="notion-code language-c++"><code class="language-c++"><span class="token comment">// 对于每个点k，开一个单链表，存储k所有可以走到的点。</span>
<span class="token comment">// h[k]存储这个单链表的头节点</span>
<span class="token comment">// e 存储 value， ne 存储 next 指针， w 存储边权</span>
int h<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> e<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> ne<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> w<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> idx<span class="token punctuation">;</span>

<span class="token comment">// 添加一条边 a -> b, 边权为 c</span>
<span class="token keyword">void</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token parameter">int a<span class="token punctuation">,</span> int b<span class="token punctuation">,</span> int c</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
		e<span class="token punctuation">[</span>idx<span class="token punctuation">]</span> <span class="token operator">=</span> b<span class="token punctuation">,</span> ne<span class="token punctuation">[</span>idx<span class="token punctuation">]</span> <span class="token operator">=</span> h<span class="token punctuation">[</span>a<span class="token punctuation">]</span><span class="token punctuation">,</span> w<span class="token punctuation">[</span>idx<span class="token punctuation">]</span> <span class="token operator">=</span> c<span class="token punctuation">,</span> h<span class="token punctuation">[</span>a<span class="token punctuation">]</span> <span class="token operator">=</span> idx <span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 遍历点t的所有出边</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> h<span class="token punctuation">[</span>t<span class="token punctuation">]</span><span class="token punctuation">;</span> i <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">=</span> ne<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
		distance <span class="token operator">=</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 边权</span>
		<span class="token comment">// 具体操作</span>
<span class="token punctuation">}</span>

<span class="token comment">// 初始化</span>
idx <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token function">memset</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> sizeof h<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-9ed4b1e931af48c6912d9bcf41d43fd7" data-id="9ed4b1e931af48c6912d9bcf41d43fd7"><span><div id="9ed4b1e931af48c6912d9bcf41d43fd7" class="notion-header-anchor"></div><a class="notion-hash-link" href="#9ed4b1e931af48c6912d9bcf41d43fd7" title="单源最短路问题算法"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">单源最短路问题算法</span></span></h2><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-454c67cec7214ecf9f61b540bcb88576" data-id="454c67cec7214ecf9f61b540bcb88576"><span><div id="454c67cec7214ecf9f61b540bcb88576" class="notion-header-anchor"></div><a class="notion-hash-link" href="#454c67cec7214ecf9f61b540bcb88576" title="dijkstra 算法"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">dijkstra 算法</span></span></h2><div class="notion-text notion-block-b8468e1aff914346a49fa98fcc373ea0">该算法的思想是：</div><div class="notion-text notion-block-0e8da0ac62384a069990556e2aab5e7f">维护一个<b>点集</b>，每一轮找到一个离当前点集最近的一个点<code class="notion-inline-code">t</code>，然后将<code class="notion-inline-code">t</code>加入到点集中，同时用这个最近点<b>去尝试更新</b>其它未遍历点的距离，遍历n - 1轮即可得到答案。</div><div class="notion-text notion-block-3e284938c88d47178c54259b5759773e">伪代码：</div><pre class="notion-code language-c++"><code class="language-c++"><span class="token function">dijkstra</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
		<span class="token comment">// dist[i] 表示从 1 号点出发到点 i 的最短路径</span>
		<span class="token keyword">for</span> 遍历n<span class="token operator">-</span><span class="token number">1</span>轮：
			<span class="token comment">// 寻找最近点 O(n)</span>
			<span class="token keyword">for</span> 遍历所有节点 j<span class="token operator">:</span>
				如果当前点j没有确认最短路，并且距离最小：
					t <span class="token operator">=</span> j，记录最近点是j
			
			<span class="token comment">// O(n)</span>
			<span class="token keyword">for</span> 遍历所有点 j<span class="token operator">:</span>
				<span class="token comment">// 尝试用 t 去更新其它点的距离，g是邻接矩阵</span>
				<span class="token comment">// 如果 1 -> j 的距离大于 1 -> t -> j，更新最短路径为 1 -> t -> j 的距离</span>
				dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">min</span><span class="token punctuation">(</span>dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">,</span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">+</span> g<span class="token punctuation">[</span>t<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			
			标记 t 点已经确认最短路径
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-3f0cccd6904e463797d55708b68d1a84">经过上面的计算，<code class="notion-inline-code">dist[n]</code> 就是最终答案了。</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-77c270d6028e4f12b79136e62fd59c7d" data-id="77c270d6028e4f12b79136e62fd59c7d"><span><div id="77c270d6028e4f12b79136e62fd59c7d" class="notion-header-anchor"></div><a class="notion-hash-link" href="#77c270d6028e4f12b79136e62fd59c7d" title="朴素版 dijkstra"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">朴素版 dijkstra</span></span></h3><div class="notion-text notion-block-9af9674caad144f3a335144e2291fc13">朴素版的 dijkstra 基本上是伪代码的实现，代码如下：</div><pre class="notion-code language-c++"><code class="language-c++">int g<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 存储每条边</span>
int dist<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 存储1号点到每个点的最短距离</span>
bool st<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token comment">// 存储每个点的最短路是否已经确定</span>

<span class="token comment">// 求1号点到n号点的最短路，如果不存在则返回-1 </span>
int <span class="token function">dijkstra</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
		<span class="token comment">// 0x3f3f3f3f 表示无穷大</span>
    <span class="token function">memset</span><span class="token punctuation">(</span>dist<span class="token punctuation">,</span> <span class="token number">0x3f</span><span class="token punctuation">,</span> sizeof dist<span class="token punctuation">)</span><span class="token punctuation">;</span>
    dist<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 在还未确定最短路的点中，寻找距离最小的点</span>
        int t <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span>int j <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>st<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>t <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">></span> dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                t <span class="token operator">=</span> j<span class="token punctuation">;</span>
        
        <span class="token comment">// 尝试用t更新其它点</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span>int j <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span>
            dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">min</span><span class="token punctuation">(</span>dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">,</span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">+</span> g<span class="token punctuation">[</span>t<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        st<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>n<span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token number">0x3f3f3f3f</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> dist<span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-ffb0148c8c0744ca96f5154fe31cd4c8">时间复杂度：<span role="button" tabindex="0" class="notion-equation notion-equation-inline"><span></span></span>，n是点数，m是边数</div><blockquote class="notion-quote notion-block-88380cd524f446408620dfe52a37cc20">有关时间复杂度的计算可以参考这篇文章：<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://blog.csdn.net/michealoven/article/details/114040136"> Dijkstra算法时间复杂度分析</a></blockquote><div class="notion-text notion-block-7c2f8ef2433e4c92923d308d0dbafb9a">适用场景：稠密图，即点少但边多的场景，因此可以用邻接矩阵来存储图</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-fe9d2211d4d347acada8419f561785e9" data-id="fe9d2211d4d347acada8419f561785e9"><span><div id="fe9d2211d4d347acada8419f561785e9" class="notion-header-anchor"></div><a class="notion-hash-link" href="#fe9d2211d4d347acada8419f561785e9" title="堆优化版 dijkstra"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">堆优化版 dijkstra</span></span></h3><div class="notion-text notion-block-8e645975609844979c965a2a554c19b4">在朴素版 dijkstra 中，下面的步骤：</div><pre class="notion-code language-c++"><code class="language-c++">  <span class="token comment">// 在还未确定最短路的点中，寻找距离最小的点</span>
  int t <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span>int j <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>st<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>t <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">></span> dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
          t <span class="token operator">=</span> j<span class="token punctuation">;</span></code></pre><div class="notion-text notion-block-5368d39254e84c669464acb5dde07459"><b>寻找距离最小的点，</b>这个操作可以用堆来优化。具体做法是小根堆来存储当前距离最小的点，其它步骤相同</div><pre class="notion-code language-c++"><code class="language-c++">typedef pair<span class="token operator">&lt;</span>int<span class="token punctuation">,</span> int<span class="token operator">></span> <span class="token constant">PII</span><span class="token punctuation">;</span>
int n<span class="token punctuation">;</span>                            <span class="token comment">// 点的数量</span>
int h<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> w<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> e<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> ne<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> idx<span class="token punctuation">;</span> <span class="token comment">// 邻接表存储所有边, w[i] 是距离</span>
int dist<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>                      <span class="token comment">// 存储所有点到1号点的距离</span>
bool st<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>                       <span class="token comment">// 存储每个点的最短距离是否已确定</span>

int <span class="token function">dijkstra</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">memset</span><span class="token punctuation">(</span>dist<span class="token punctuation">,</span> <span class="token number">0x3f</span><span class="token punctuation">,</span> sizeof dist<span class="token punctuation">)</span><span class="token punctuation">;</span>
    dist<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    priority_queue<span class="token operator">&lt;</span><span class="token constant">PII</span><span class="token punctuation">,</span> vector<span class="token operator">&lt;</span><span class="token constant">PII</span><span class="token operator">></span><span class="token punctuation">,</span> greater<span class="token operator">&lt;</span><span class="token constant">PII</span><span class="token operator">>></span> heap<span class="token punctuation">;</span>
    heap<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// first存储距离，second存储节点编号</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span>heap<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
				<span class="token comment">// 弹出距离最近的点，优化它的出边</span>
        auto t <span class="token operator">=</span> heap<span class="token punctuation">.</span><span class="token function">top</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        heap<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        int ver <span class="token operator">=</span> t<span class="token punctuation">.</span>second<span class="token punctuation">,</span> distance <span class="token operator">=</span> t<span class="token punctuation">.</span>first<span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>st<span class="token punctuation">[</span>ver<span class="token punctuation">]</span><span class="token punctuation">)</span>
            <span class="token keyword">continue</span><span class="token punctuation">;</span>

        st<span class="token punctuation">[</span>ver<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
				<span class="token comment">// 遍历t的所有出边</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> h<span class="token punctuation">[</span>ver<span class="token punctuation">]</span><span class="token punctuation">;</span> i <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">=</span> ne<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            int j <span class="token operator">=</span> e<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">></span> distance <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> distance <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
                heap<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">,</span> j<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 如果能优化，则放入点</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>n<span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token number">0x3f3f3f3f</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> dist<span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-873667bd7f0444a5a12adb98e9eb05e9">需要注意的是，堆里面允许冗余点存在，比如{4, 3}，{2, 3}，{7, 3}这几个组合是允许同时存在的。</div><div class="notion-text notion-block-ad31d36198cc4ffeb84f82a19c9c2089"><code class="notion-inline-code">st</code> 的含义是点是否被处理过，因此只有在从堆中弹出的时候才标记 <code class="notion-inline-code">st[t] = true</code> </div><div class="notion-text notion-block-d92dba5b71644d6988f561a10025e55e">时间复杂度：<span role="button" tabindex="0" class="notion-equation notion-equation-inline"><span></span></span></div><div class="notion-text notion-block-305ca289b4ac44238cb341a5cc7ed7f8">适用场景：稀疏图，点多边少</div><hr class="notion-hr notion-block-41938f35dc0646d990c77f0a12521f06"/><div class="notion-text notion-block-fe3602f2aa034822aad5e5016d22a911">一旦图中存在负权边，dijkstra 算法就失效了，只能使用 BF 算法或者 SPFA 算法求解</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-8de2296e47554ba48f9ca3a7001a7ee8" data-id="8de2296e47554ba48f9ca3a7001a7ee8"><span><div id="8de2296e47554ba48f9ca3a7001a7ee8" class="notion-header-anchor"></div><a class="notion-hash-link" href="#8de2296e47554ba48f9ca3a7001a7ee8" title="BF 算法"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">BF 算法</span></span></h2><div class="notion-text notion-block-45900b25aa684ba2ad9916b43fe34d0c">思想：</div><div class="notion-text notion-block-5ff1add47bc64c0b9dd3ed2805ba5cef">迭代 n 次，每次遍历所有边，尝试去更新dist数组。这样得到的dist数组就是最终结果了</div><div class="notion-text notion-block-7249a3eed7c44806aa840c2a1d395d4a">伪代码：</div><pre class="notion-code language-c++"><code class="language-c++"><span class="token keyword">for</span> <span class="token literal-property property">迭代n次</span><span class="token operator">:</span>
	<span class="token keyword">for</span> 所有边 a <span class="token operator">-</span><span class="token operator">></span> b<span class="token punctuation">,</span> 边权 w<span class="token operator">:</span>
		dist<span class="token punctuation">[</span>b<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">min</span><span class="token punctuation">(</span>dist<span class="token punctuation">[</span>b<span class="token punctuation">]</span><span class="token punctuation">,</span> dist<span class="token punctuation">[</span>a<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 松弛操作</span></code></pre><div class="notion-text notion-block-fc56b7e6100a41bca8fe5ee64c59ef26">实现：</div><pre class="notion-code language-c++"><code class="language-c++">int n<span class="token punctuation">,</span> m<span class="token punctuation">;</span>
int dist<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token comment">// 边，a表示出点，b表示入点，w表示边的权重</span>
struct Edge
<span class="token punctuation">{</span>
    int a<span class="token punctuation">,</span> b<span class="token punctuation">,</span> w<span class="token punctuation">;</span>
<span class="token punctuation">}</span> edges<span class="token punctuation">[</span><span class="token constant">M</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

int <span class="token function">bellman_ford</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">memset</span><span class="token punctuation">(</span>dist<span class="token punctuation">,</span> <span class="token number">0x3f</span><span class="token punctuation">,</span> sizeof dist<span class="token punctuation">)</span><span class="token punctuation">;</span>
    dist<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token comment">// 如果第n次迭代仍然会松弛三角不等式，就说明存在一条长度是n+1的最短路径，</span>
    <span class="token comment">// 由抽屉原理，路径中至少存在两个相同的点，说明图中存在负权回路。</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span>int j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            int a <span class="token operator">=</span> edges<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">.</span>a<span class="token punctuation">,</span> b <span class="token operator">=</span> edges<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">.</span>b<span class="token punctuation">,</span> w <span class="token operator">=</span> edges<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">.</span>w<span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>b<span class="token punctuation">]</span> <span class="token operator">></span> dist<span class="token punctuation">[</span>a<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">)</span>
                dist<span class="token punctuation">[</span>b<span class="token punctuation">]</span> <span class="token operator">=</span> dist<span class="token punctuation">[</span>a<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>n<span class="token punctuation">]</span> <span class="token operator">></span> <span class="token number">0x3f3f3f3f</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> dist<span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-59a84b8ec45d4775a3e2ff29f05e842f">时间复杂度： <span role="button" tabindex="0" class="notion-equation notion-equation-inline"><span></span></span></div><div class="notion-text notion-block-4e335f48d4b049e399d1743b4c8165c0">适用场景：图中存在负权边</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-e1d3262805a54666a3c883d178e4e158" data-id="e1d3262805a54666a3c883d178e4e158"><span><div id="e1d3262805a54666a3c883d178e4e158" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e1d3262805a54666a3c883d178e4e158" title="SPFA 算法"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">SPFA 算法</span></span></h2><div class="notion-text notion-block-561c0d291bdc414c9c7e81994356c387">从 BF 算法中我们可以观察这个条件：</div><pre class="notion-code language-c++"><code class="language-c++"><span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>b<span class="token punctuation">]</span> <span class="token operator">></span> dist<span class="token punctuation">[</span>a<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">)</span>
    dist<span class="token punctuation">[</span>b<span class="token punctuation">]</span> <span class="token operator">=</span> dist<span class="token punctuation">[</span>a<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">;</span></code></pre><div class="notion-text notion-block-b0ec13cd0b304c9ea29bbb4e186cce82">何时才能更新 dist[b] 呢？当然是只有 dist[a] 发生变动的时候才能更新了。</div><div class="notion-text notion-block-5ccbdfff5f1f4bb0a3ea7a13886abfa2">SPFA 的思想也很简单，当一个点被更新的时候，才去更新它的所有出边。</div><div class="notion-text notion-block-78c664a536b847e2a56577104cf28f04">这样就避免每次都遍历所有边尝试去更新最短距离了。</div><div class="notion-text notion-block-e40b098ac2414ec3ab0e8553fa96004d">代码：</div><pre class="notion-code language-c++"><code class="language-c++">int n<span class="token punctuation">;</span>                            <span class="token comment">// 总点数</span>
int h<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> w<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> e<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> ne<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> idx<span class="token punctuation">;</span> <span class="token comment">// 邻接表存储所有边</span>
int dist<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>                      <span class="token comment">// 存储每个点到1号点的最短距离</span>
bool st<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>                       <span class="token comment">// 存储每个点是否在队列中</span>

int <span class="token function">spfa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">memset</span><span class="token punctuation">(</span>dist<span class="token punctuation">,</span> <span class="token number">0x3f</span><span class="token punctuation">,</span> sizeof dist<span class="token punctuation">)</span><span class="token punctuation">;</span>
    dist<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    queue<span class="token operator">&lt;</span>int<span class="token operator">></span> q<span class="token punctuation">;</span>
    q<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    st<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span>q<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        auto t <span class="token operator">=</span> q<span class="token punctuation">.</span><span class="token function">front</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        q<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        st<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> h<span class="token punctuation">[</span>t<span class="token punctuation">]</span><span class="token punctuation">;</span> i <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">=</span> ne<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            int j <span class="token operator">=</span> e<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">></span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>st<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span>
                <span class="token punctuation">{</span>
                    q<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span>
                    st<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>n<span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token number">0x3f3f3f3f</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> dist<span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>
</code></pre><div class="notion-text notion-block-481069a89264486fa0b0c1eb86b1ae97">需要注意的地方是：<code class="notion-inline-code">st</code> 记录的是点是否存在队列中，所以在入队的时候要标记<code class="notion-inline-code">st[j] = true</code> ，而出队的时候要修改<code class="notion-inline-code">st[j] = false</code></div><div class="notion-text notion-block-ec140ca2c31343ac802d35ca67157e80">时间复杂度：一般 <span role="button" tabindex="0" class="notion-equation notion-equation-inline"><span></span></span>，最差 <span role="button" tabindex="0" class="notion-equation notion-equation-inline"><span></span></span></div><div class="notion-text notion-block-857fb32786bb4f88ac51409122aae584">适用场景：存在负权边</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-dfdc1164ec5240018bcee8af894a1d4f" data-id="dfdc1164ec5240018bcee8af894a1d4f"><span><div id="dfdc1164ec5240018bcee8af894a1d4f" class="notion-header-anchor"></div><a class="notion-hash-link" href="#dfdc1164ec5240018bcee8af894a1d4f" title="负权回路"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">负权回路</span></span></h2><div class="notion-text notion-block-f3168a7ec7894cce9c4caded2c9fd01d">首先介绍一个概念，叫做负权回路，如下图所示</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-35456df632194f62a1dc14bf4c45eea8"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F844bb17e-a37b-4d92-b968-91b56af5972a%2FUntitled.png%3Fid%3D35456df6-3219-4f62-a1dc-14bf4c45eea8%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D01WO9_WzSoezH82i_9rtTGoXOAIw2Z6haNvTB55VP3M?table=block&amp;id=35456df6-3219-4f62-a1dc-14bf4c45eea8&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-6207ea19b45242d396b35c60607c4c45">图中2，3，4组成了一个负权回路。</div><div class="notion-text notion-block-b10bad308a584162bf084ff2c9f08b61">当负权回路处于源点和目标点的路径上时，必然无法求出最短路径。因为每经过一次负权回路，边权就-1。</div><div class="notion-text notion-block-10169151a2884e989c4ecd3985e1bc57">对比BF算法和SPFA算法我们可以发现，上面这种情况发生的时候，BF算法迭代n次就停止，不会发生死循环，而SPFA会发生死循环。</div><div class="notion-text notion-block-9a12e3c17d714ea2968dfe5bac863641">对于这种情况，SPFA可以做一些改进，检测出是否有上面情况的存在。</div><pre class="notion-code language-c++"><code class="language-c++">int n<span class="token punctuation">;</span>                            <span class="token comment">// 总点数</span>
int h<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> w<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> e<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> ne<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> idx<span class="token punctuation">;</span> <span class="token comment">// 邻接表存储所有边</span>
int dist<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">,</span> cnt<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>              <span class="token comment">// dist[x]存储1号点到x的最短距离，cnt[x]存储1到x的最短路中经过的点数</span>
bool st<span class="token punctuation">[</span><span class="token constant">N</span><span class="token punctuation">]</span><span class="token punctuation">;</span>                       <span class="token comment">// 存储每个点是否在队列中</span>

<span class="token comment">// 如果存在负环，则返回true，否则返回false。</span>
bool <span class="token function">spfa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// 不需要初始化dist数组</span>
    <span class="token comment">// 原理：如果某条最短路径上有n个点（除了自己），那么加上自己之后一共有n+1个点</span>
    <span class="token comment">// 由抽屉原理一定有两个点相同，所以存在环。</span>

    queue<span class="token operator">&lt;</span>int<span class="token operator">></span> q<span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        q<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>
        st<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">while</span> <span class="token punctuation">(</span>q<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        auto t <span class="token operator">=</span> q<span class="token punctuation">.</span><span class="token function">front</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        q<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        st<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>

        <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> h<span class="token punctuation">[</span>t<span class="token punctuation">]</span><span class="token punctuation">;</span> i <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">=</span> ne<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            int j <span class="token operator">=</span> e<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">></span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
                cnt<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> cnt<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span>cnt<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">>=</span> n<span class="token punctuation">)</span>
                    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment">// 如果从1号点到x的最短路中包含至少n个点（不包括自己），则说明存在环</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>st<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span>
                <span class="token punctuation">{</span>
                    q<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span>
                    st<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-9951eca852e042579a3e28002e567712">具体做法是，使用<code class="notion-inline-code">cnt</code>数组来记录经过的点数，如果到某个点经过点数≥n，那么根据抽屉原理，此时有n+1个点，但是最多图中有n个点，所以必然存在2个点相同，因此图中存在负权回路。</div><hr class="notion-hr notion-block-b427db4be4fd46c7bd0c50d503ef6530"/><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-6f05c08985fa4a51b9f8e1b23ab856fe" data-id="6f05c08985fa4a51b9f8e1b23ab856fe"><span><div id="6f05c08985fa4a51b9f8e1b23ab856fe" class="notion-header-anchor"></div><a class="notion-hash-link" href="#6f05c08985fa4a51b9f8e1b23ab856fe" title="对比 SPFA 和堆优化 dijkstra"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">对比 SPFA 和堆优化 dijkstra</span></span></h2><div class="notion-row notion-block-ce956d67d1094d6ea96e36e2e2b5016a"><div class="notion-column notion-block-76f352c5a9684fe794606a7de6555eab" style="width:calc((100% - (1 * min(32px, 4vw))) * 0.5)"><div class="notion-text notion-block-a71030891e634d9baf647fa4baad4ff3">堆优化 dijkstra</div><div class="notion-text notion-block-3641bca73f194972afb66950a7584cae">思想：使用小根堆来存储最小距离的点，遍历它的所有出边，尝试优化 dist</div><div class="notion-text notion-block-97f8dba5f02b4c62978f3dc5a8f57852">时间复杂度：O(nlogm)</div><div class="notion-text notion-block-9221a0b40583437c95fff71df620d90c">核心代码：</div><pre class="notion-code language-c++"><code class="language-c++"><span class="token keyword">while</span> <span class="token punctuation">(</span>heap<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
		<span class="token comment">// 弹出距离最近的点，优化它的出边</span>
    auto t <span class="token operator">=</span> heap<span class="token punctuation">.</span><span class="token function">top</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    heap<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    int ver <span class="token operator">=</span> t<span class="token punctuation">.</span>second<span class="token punctuation">,</span> distance <span class="token operator">=</span> t<span class="token punctuation">.</span>first<span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>st<span class="token punctuation">[</span>ver<span class="token punctuation">]</span><span class="token punctuation">)</span>
        <span class="token keyword">continue</span><span class="token punctuation">;</span>

    st<span class="token punctuation">[</span>ver<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
		<span class="token comment">// 遍历t的所有出边</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> h<span class="token punctuation">[</span>ver<span class="token punctuation">]</span><span class="token punctuation">;</span> i <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">=</span> ne<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        int j <span class="token operator">=</span> e<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">></span> distance <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> distance <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
            heap<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">,</span> j<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 如果能优化，则放入点</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-53ce08cb947f48a6a05649f2bb575aec">st的作用：标记是否被处理过</div></div><div class="notion-spacer"></div><div class="notion-column notion-block-383288cbbacb4927a50565d9d6c3fbf2" style="width:calc((100% - (1 * min(32px, 4vw))) * 0.5)"><div class="notion-text notion-block-ceccc611fb584652893982069e74fd95">SPFA</div><div class="notion-text notion-block-2e1d100b45ee4316889b8935ae471529">思想：使用队列来存储被更新的点，优化它的所有出边</div><div class="notion-text notion-block-b89ca272e05c4e7b85189b1211371084">时间复杂度：O(n)，最差O(nm)</div><div class="notion-text notion-block-17ff269bbdde4c479d645513064ac070">核心代码：</div><pre class="notion-code language-c++"><code class="language-c++"><span class="token keyword">while</span> <span class="token punctuation">(</span>q<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
		<span class="token comment">// 弹出被更新的点</span>
    auto t <span class="token operator">=</span> q<span class="token punctuation">.</span><span class="token function">front</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    q<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    st<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
		<span class="token comment">// 遍历所有出边，尝试优化</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> h<span class="token punctuation">[</span>t<span class="token punctuation">]</span><span class="token punctuation">;</span> i <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">=</span> ne<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        int j <span class="token operator">=</span> e<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">></span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            dist<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> dist<span class="token punctuation">[</span>t<span class="token punctuation">]</span> <span class="token operator">+</span> w<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>st<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                q<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span>
                st<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-2f251fcab61f4585805da309ef54f7d2">st的作用：标记是否存在队列中</div></div><div class="notion-spacer"></div></div><div class="notion-text notion-block-574ef502c94f46c3b291aa9863a0b5f3">可以发现上面两个版本的思想非常相似，区别仅仅在于：</div><div class="notion-text notion-block-5a43c95b159c4f63bd0c4d02b551f349">堆优化dijkstra使用小根堆来保证弹出的点是目前距离最小的点，而SPFA只需要获得被更新过的点即可。</div><div class="notion-text notion-block-cdb2a1ce32a741d88c2326410fe36e93">一般我们都可以用 SPFA 来解决单源最短路径问题，只有被卡的时候，才考虑堆优化版 dijkstra。</div><hr class="notion-hr notion-block-0384a964ce1b497e9ad94d76bc51fcb9"/><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-fb182adbd8d74a2c9abcb9aa44d4e9c8" data-id="fb182adbd8d74a2c9abcb9aa44d4e9c8"><span><div id="fb182adbd8d74a2c9abcb9aa44d4e9c8" class="notion-header-anchor"></div><a class="notion-hash-link" href="#fb182adbd8d74a2c9abcb9aa44d4e9c8" title="Floyd 算法"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Floyd 算法</span></span></h2><div class="notion-text notion-block-5ac01c7cb055455cab9d494b1e9fba0f">如果是要求任意两个点之间的最短路径的话，就考虑用 floyd 算法了，这个算法很暴力：</div><pre class="notion-code language-c++"><code class="language-c++">初始化：
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i <span class="token operator">++</span> <span class="token punctuation">)</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span>int j <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> j <span class="token operator">++</span> <span class="token punctuation">)</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">==</span> j<span class="token punctuation">)</span> d<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
            <span class="token keyword">else</span> d<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token constant">INF</span><span class="token punctuation">;</span>

<span class="token comment">// 算法结束后，d[a][b]表示a到b的最短距离</span>
<span class="token keyword">void</span> <span class="token function">floyd</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int k <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> k <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> k <span class="token operator">++</span> <span class="token punctuation">)</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i <span class="token operator">++</span> <span class="token punctuation">)</span>
            <span class="token keyword">for</span> <span class="token punctuation">(</span>int j <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> j <span class="token operator">++</span> <span class="token punctuation">)</span>
                d<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">min</span><span class="token punctuation">(</span>d<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">,</span> d<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>k<span class="token punctuation">]</span> <span class="token operator">+</span> d<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-b5f91a4336aa4baeaa8d218a546cd474">时间复杂度：O(n^3)</div><div class="notion-text notion-block-234dbb79118843eca2290621e0bc6865">需要注意的是，先枚举中间点k，再枚举左右端点i和j</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-b9825e52097b495f825051846560a004" data-id="b9825e52097b495f825051846560a004"><span><div id="b9825e52097b495f825051846560a004" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b9825e52097b495f825051846560a004" title="总结"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">总结</span></span></h2><div class="notion-text notion-block-14b0a4095f724048b2fc15b2aed3ffba">单源最短路径：优先考虑SPFA，被卡再考虑堆优化版dijkstra</div><div class="notion-text notion-block-2322dc5c2fe24726a19f2d204bf860e9">多源最短路径：Floyd</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-b0eebf59397243c2960a1be9598a6341" data-id="b0eebf59397243c2960a1be9598a6341"><span><div id="b0eebf59397243c2960a1be9598a6341" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b0eebf59397243c2960a1be9598a6341" title="参考"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">参考</span></span></h2><div class="notion-text notion-block-07a2443e56e447419696de6a9af81778"><b>
</b><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.acwing.com/activity/content/punch_the_clock/11/">AcWing 算法基础课</a></div><div class="notion-blank notion-block-96eff80ca79f468686ea8767657e1196"> </div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Go 并发编程 - Mutex 注意事项]]></title>
            <link>https://hhmy27.github.io//use-mutex</link>
            <guid>https://hhmy27.github.io//use-mutex</guid>
            <pubDate>Wed, 28 Dec 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[同步原语使用注意事项]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-918008b0231b4552b752e1f3dc35cbfc"><div class="notion-viewport"></div><div class="notion-table-of-contents notion-gray notion-block-c039c9e86673407d81992721d3516ab4"><a href="#585955a5c5ff4369b30f137066398ff0" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">简介</span></a><a href="#c12b3f313e28483aaa85849c41f676ca" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">Lock 和 Unlock 没有匹配</span></a><a href="#3be64419d413466788760523afc0c99b" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">不可复制</span></a><a href="#e7866a55d7994fc9bdc3c8757eb9970b" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">不可重入</span></a><a href="#e1f145fcaf8c4dbba8431be6df58a1d0" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">死锁</span></a><a href="#48881f4313484a52b88b2428f2288e24" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">扩展</span></a><a href="#9117d7eb585845baae37dcf036af6147" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">Go 的死锁检查机制</span></a><a href="#d6c59017233641b6abf42c20455055b5" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">Ref</span></a></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-585955a5c5ff4369b30f137066398ff0" data-id="585955a5c5ff4369b30f137066398ff0"><span><div id="585955a5c5ff4369b30f137066398ff0" class="notion-header-anchor"></div><a class="notion-hash-link" href="#585955a5c5ff4369b30f137066398ff0" title="简介"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">简介</span></span></h2><div class="notion-text notion-block-6fa2786768444096a9c6e424281e0b1b">使用 Mutex 需要注意四个可能踩坑的地方：</div><ol start="1" class="notion-list notion-list-numbered notion-block-7ebc3bc68efb45bf9ad6a09693cc39b1"><li>Lock 和 Unlock 没有匹配</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-edd7e4bbdf0d4cebbd17695af3127cee"><li>不可复制</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-4c65e82f1bdd40749cc2d2edc76f508c"><li>不可重入</li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-5eeeac8554dd4659bf00b5f797f108ff"><li>死锁</li></ol><div class="notion-blank notion-block-112bab1ad6b3446e983d06cefb618735"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-c12b3f313e28483aaa85849c41f676ca" data-id="c12b3f313e28483aaa85849c41f676ca"><span><div id="c12b3f313e28483aaa85849c41f676ca" class="notion-header-anchor"></div><a class="notion-hash-link" href="#c12b3f313e28483aaa85849c41f676ca" title="Lock 和 Unlock 没有匹配"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Lock 和 Unlock 没有匹配</span></span></h2><div class="notion-text notion-block-969e43e12a354d4aaec252ba5c6fc9d2">没有匹配指的是 Lock 和 Unlock 不一致</div><div class="notion-text notion-block-3d495baf062247b1a9de13a45894eefb">比如说 Lock 之后没有及时的调用 Unlock，导致锁一直被持有，最终死锁 panic。</div><div class="notion-text notion-block-9fcfe44188494323928ad89242b4bb30">或者没有 Lock 之前就调用了 Unlock，这样会触发 fatal error，recover 也无能为力。</div><div class="notion-text notion-block-beb69999565040f59a02fa5905c1dd1d">听起来是很低级的失误，但是在实际开发过程中，可能出现的场景却有很多</div><ol start="1" class="notion-list notion-list-numbered notion-block-1c0793ce41894e6ba3e4685b7b42e3e3"><li>多层 if-else 的复杂嵌套，导致没有在对应的逻辑分支中释放锁</li><ol class="notion-list notion-list-numbered notion-block-1c0793ce41894e6ba3e4685b7b42e3e3"><blockquote class="notion-quote notion-block-bb1618ca7ecf4ad29644908447cb0151">尽量避免超过 3 层以上的嵌套缩进，如果超过了 3 层，或许应该考虑重构这段逻辑
<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.bilibili.com/video/BV1q14y1K7p3/?spm_id_from=333.337.search-card.all.click&amp;vd_source=6fdf39f6cb0f5cc47230ec74d156d0a4">为什么你不应该嵌套代码，Linux之父也在这样做_哔哩哔哩_bilibili</a></blockquote><div class="notion-text notion-block-171daff389764f80b784cdd5118e8321">举一个例子</div><pre class="notion-code language-go"><code class="language-go"> <span class="token keyword">if</span> check <span class="token punctuation">{</span>
		mu<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
		<span class="token keyword">if</span> check <span class="token punctuation">{</span>
			<span class="token comment">// do something ...</span>
			mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
		<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
			<span class="token comment">// do something ...</span>
			<span class="token comment">// doesn't release lock</span>
			<span class="token keyword">return</span> 
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-a54bee4afccd49df8930c969ffdfdc91">上面这段代码在 else 的时候忘记释放锁，直接 return，这种情况下会直接导致死锁。</div></ol></ol><ol start="2" class="notion-list notion-list-numbered notion-block-7946549192f94a929a003155dd4c7c1b"><li>重构代码的时候错误的删除了 Unlock</li><ol class="notion-list notion-list-numbered notion-block-7946549192f94a929a003155dd4c7c1b"><div class="notion-text notion-block-725bad6fadd54258ac82429c8e529839">由于不熟悉上下文，在我们进行复杂的逻辑改动的时候，很有可能会不小心删除掉 Unlock 相关的代码，比如说重构上面复杂的 if-else，我们可能尝试删减或添加一个分支的时候，把原有的 Unlock 给去掉了，导致死锁</div><div class="notion-text notion-block-c77e76afe597460d9b953f6c4ee12f05">再举一个例子</div><pre class="notion-code language-go"><code class="language-go">mu<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment">// 新增的 if 逻辑，直接返回并没有释放</span>
<span class="token keyword">if</span> check <span class="token punctuation">{</span>
	<span class="token keyword">return</span>
<span class="token punctuation">}</span>
mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre></ol></ol><ol start="3" class="notion-list notion-list-numbered notion-block-fa0f69c0a7044595a589ffd8bb50b4ee"><li>没有触发 Unlock 的调用逻辑</li><ol class="notion-list notion-list-numbered notion-block-fa0f69c0a7044595a589ffd8bb50b4ee"><div></div><div class="notion-text notion-block-b699c42e7a86453abd0061cc97364c6d">原始代码逻辑是在 845 行进行加锁，在接下来的子函数中进行处理，并负责解锁</div><div class="notion-text notion-block-047040421bdb497d84e83ed4a5bf25d5">但是这里有一个弊端，就是子函数如果出现 panic，会直接触发 recover 的逻辑，但是旧的 recover 并没有做锁的释放，那么就会出现死锁了。所以这个 MR 也是修复了这个问题，如果出现了 panic，在 recover 的时候同时释放锁，避免死锁出现。</div><blockquote class="notion-quote notion-block-008420c4b2c645b4b0638aac41b91255">虽然理想状态下是平级做 Lock/Unlock 的，但是工程规模一旦扩大，就很难达到这种理想的重构，所以只能用这种折中的办法修复问题，虽然不是很优雅，但是能解决问题。</blockquote><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-dea6283ca92142b99a5b3201fc5a0b40"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F9d67aea7-037f-4406-99eb-4611df9c24a3%2FUntitled.png%3Fid%3Ddea6283c-a921-42b9-9a5b-3201fc5a0b40%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DbLobBdncI2Z8H_dssX_1dIGukMU4abCG2SwjSXT7rqg?table=block&amp;id=dea6283c-a921-42b9-9a5b-3201fc5a0b40&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure></ol></ol><ol start="4" class="notion-list notion-list-numbered notion-block-5c452d84c9ed4011a488c7f8dc07aea2"><li>加锁之前解锁</li><ol class="notion-list notion-list-numbered notion-block-5c452d84c9ed4011a488c7f8dc07aea2"><pre class="notion-code language-go"><code class="language-go"><span class="token literal-property property">mu</span> <span class="token operator">:</span><span class="token operator">=</span> sync<span class="token punctuation">.</span>Mutex<span class="token punctuation">{</span><span class="token punctuation">}</span>
mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre><div class="notion-text notion-block-9f9de483a2c54075b2099fb8470c72c4">比如说上面这种情况，会报错</div><pre class="notion-code language-go"><code class="language-go">fatal error<span class="token operator">:</span> sync<span class="token operator">:</span> unlock <span class="token keyword">of</span> unlocked mutex</code></pre><div class="notion-text notion-block-bce85baf72b443a79f166a84ae0b4ff4">这个 fatal error 是没办法 recover 的，它不是 panic，出现这种情况只能修改代码了。</div></ol></ol><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-3be64419d413466788760523afc0c99b" data-id="3be64419d413466788760523afc0c99b"><span><div id="3be64419d413466788760523afc0c99b" class="notion-header-anchor"></div><a class="notion-hash-link" href="#3be64419d413466788760523afc0c99b" title="不可复制"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">不可复制</span></span></h2><div class="notion-text notion-block-768e21c93f464e3b945d9e4edef2553d">Mutex 的特性是不可复制的，因为 Mutex 中用 state 记录了当前锁的信息，如 waiter 数量，当前锁的状态等信息。而 Mutex 的状态是瞬息万变的，是多个 goruntine 一起决定的，当我们复制了一个 Mutex，可能会得到处于加锁状态的 Mutex，这个 Mutex 永远也没办法使用了。</div><div class="notion-text notion-block-de03be155bef4a2fb790844e6182338b">看一个例子</div><pre class="notion-code language-go"><code class="language-go">type Counter struct <span class="token punctuation">{</span>
	sync<span class="token punctuation">.</span>Mutex
	Count int
<span class="token punctuation">}</span>

func <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">var</span> c Counter
	c<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	defer c<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	c<span class="token punctuation">.</span>Count<span class="token operator">++</span>
	<span class="token function">foo</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// 无意间复制了 Mutex</span>
func <span class="token function">foo</span><span class="token punctuation">(</span><span class="token parameter">c Counter</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	c<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	defer c<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"in foo"</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-26eac19bc09e4a06b2e9187093741047">在函数传递的无意间复制了 Counter 中的 Mutex，这里运行会直接报错<code class="notion-inline-code">fatal error: all goroutines are asleep - deadlock!</code></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-e7866a55d7994fc9bdc3c8757eb9970b" data-id="e7866a55d7994fc9bdc3c8757eb9970b"><span><div id="e7866a55d7994fc9bdc3c8757eb9970b" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e7866a55d7994fc9bdc3c8757eb9970b" title="不可重入"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">不可重入</span></span></h2><div class="notion-text notion-block-8ac753931bfd4a59b12f8f171c9ef5f6">锁的可重入特性指的是锁的持有者可以反复获取锁。java 中的 ReentrantLock 就是可重入锁。通过记录锁的持有者和重入次数来进行加锁和解锁。</div><div class="notion-text notion-block-f373c8bfce344460b30e2feb9dff8881">Go 的 Mutex 不支持可重入，所以连续调用 Lock 也会导致 fatal error。</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-e1f145fcaf8c4dbba8431be6df58a1d0" data-id="e1f145fcaf8c4dbba8431be6df58a1d0"><span><div id="e1f145fcaf8c4dbba8431be6df58a1d0" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e1f145fcaf8c4dbba8431be6df58a1d0" title="死锁"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">死锁</span></span></h2><div class="notion-text notion-block-db2bfd1311ce428681deabb567f996bd">这几乎是最常见的问题了。</div><div class="notion-text notion-block-6e6c0f662bea40fb9b04e35fed2b3ba3">我们先回忆一下死锁产生的必要条件</div><ol start="1" class="notion-list notion-list-numbered notion-block-c07e5d2b91804001a2b444b5ea8d52bc"><li>互斥：竞争的资源具有排他性，不能同时被多个线程占有。</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-c8fe6510e1bf4f9caafc401a18b7c8bb"><li>请求并保持：每个线程保持了一定的资源，同时还申请新的资源。</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-0006ae968f1747cf849f8939e398799e"><li>不可剥夺：一旦资源分配给线程，就无法从它手中剥夺资源。</li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-5e6e9fce00f24b6c94f01ba4d555683b"><li>循环等待：多个线程处于循环等待状态，都在等待对方释放资源。</li></ol><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-48881f4313484a52b88b2428f2288e24" data-id="48881f4313484a52b88b2428f2288e24"><span><div id="48881f4313484a52b88b2428f2288e24" class="notion-header-anchor"></div><a class="notion-hash-link" href="#48881f4313484a52b88b2428f2288e24" title="扩展"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">扩展</span></span></h2><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-9117d7eb585845baae37dcf036af6147" data-id="9117d7eb585845baae37dcf036af6147"><span><div id="9117d7eb585845baae37dcf036af6147" class="notion-header-anchor"></div><a class="notion-hash-link" href="#9117d7eb585845baae37dcf036af6147" title="Go 的死锁检查机制"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Go 的死锁检查机制</span></span></h3><div class="notion-text notion-block-fd56543705644a51a63595b194257bd6">Go 能够在运行时检查出死锁问题，能够打印出堆栈信息</div><div class="notion-text notion-block-3d0925ab55044b00bf41fb38d9281d0d">如果我们想要在运行之前检测有没有死锁问题，可以使用 <b>vet </b>这个工具</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-a17aff7a2aec43b0a804f91ca7482449"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fb975d0a4-698e-4dda-b9ce-9b68948a8ec6%2FUntitled.png%3Fid%3Da17aff7a-2aec-43b0-a804-f91ca7482449%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D43tMuIYCKLstkOsgbsZl9ggTmuzk_Bq3TgZmxHndmd0?table=block&amp;id=a17aff7a-2aec-43b0-a804-f91ca7482449&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-0c22ad80f701449c87a6d1b0b1114b7f">当使用 vet 之后，这个工具可以提示我们潜在的死锁问题，非常智能</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-d6c59017233641b6abf42c20455055b5" data-id="d6c59017233641b6abf42c20455055b5"><span><div id="d6c59017233641b6abf42c20455055b5" class="notion-header-anchor"></div><a class="notion-hash-link" href="#d6c59017233641b6abf42c20455055b5" title="Ref"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Ref</span></span></h2><div class="notion-text notion-block-beaf21da2d1b4319ad5971fbbb5c7f42"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://time.geekbang.org/column/intro/100061801">Go 并发编程实战课_Go_并发编程_分布式_同步原语_鸟窝-极客时间 (geekbang.org)</a></div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[CS144 - lab 1]]></title>
            <link>https://hhmy27.github.io//cs144-lab1</link>
            <guid>https://hhmy27.github.io//cs144-lab1</guid>
            <pubDate>Tue, 29 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[滑动窗口的实现]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-24f98f49407b4f0cb90b7cb436c0063d"><div class="notion-viewport"></div><div class="notion-table-of-contents notion-gray notion-block-38685b58af934811933e64fc1cf959b5"><a href="#2464eaef513d4f4a9fc313e9ab3b8fc3" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">简介</span></a><a href="#36a7c264312a414ba64ccf59e5ed59c2" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">2 Getting started</span></a><a href="#5d78756550fb4194a54775abfad103cf" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">3 Putting substrings in sequence</span></a><a href="#2928d819d35f444baca10c678105fae0" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">3.1 What’s the “capacity”?</span></a><a href="#f6fb2e68f4234d3fa55f57415fe81488" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">分析和设计</span></a><a href="#32cb590933924546932c5a057b42a831" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">实验结果</span></a><a href="#86123e58279242d6a47ea320bda54488" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">一些容易忽略的点</span></a><a href="#70381c645a4346af856c36a7f362fb74" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">总结</span></a></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-2464eaef513d4f4a9fc313e9ab3b8fc3" data-id="2464eaef513d4f4a9fc313e9ab3b8fc3"><span><div id="2464eaef513d4f4a9fc313e9ab3b8fc3" class="notion-header-anchor"></div><a class="notion-hash-link" href="#2464eaef513d4f4a9fc313e9ab3b8fc3" title="简介"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">简介</span></span></h2><div class="notion-text notion-block-e767a0021ad5439680eb2b7d4096e743">本次lab 对应到 week 3。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-f0847288ba494a13bc593819d1ee98d3"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fac558c1f-e369-4f07-bfb1-37f60ed4474f%2FUntitled.png%3Fid%3Df0847288-ba49-4a13-bc59-3819d1ee98d3%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DNGdM-_yJwAnU9Df96zEkySNHuPHuQUdQbLUXodEXy3E?table=block&amp;id=f0847288-ba49-4a13-bc59-3819d1ee98d3&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-a1c6f2a3f209493dabaf44c9acc12b5b">week 3 的 slide 讲的都是包交换（packet switching）技术，这里记录一下基本概念：</div><ul class="notion-list notion-list-disc notion-block-72499b0e9ae64992bcc9e0f30d77a106"><li>包交换技术</li></ul><div class="notion-text notion-block-cf3a68444ee9445dac65db9936d269c5">数据从http ⇒ tcp ⇒ ip层层封装然后<b>交给路由器转发到目的地</b></div><ul class="notion-list notion-list-disc notion-block-cd99503c3c1c4c4abd6ead49eac77eb7"><li>Delay</li><ul class="notion-list notion-list-disc notion-block-cd99503c3c1c4c4abd6ead49eac77eb7"><div class="notion-text notion-block-7431d7fa247e46e68a8b3acaccfd535d">使用路由交换技术不可避免的延迟：</div><li>链接上固定的传播延迟。 <b>Propagation delay along the links (fixed)</b></li><li>数据打包的固定延迟。<b>Packetization delay to place packets onto links (fixed</b>)</li><li>路由器数据包缓冲区中的排队延迟. <b>Queueing delay in the packet buffers of the routers (variable)</b></li></ul></ul><ul class="notion-list notion-list-disc notion-block-ad161ae4fa6c4f6ba11db5171d3c2c23"><li>缓冲区</li></ul><div class="notion-text notion-block-3a86a832fbf049e8abc36fec4258b796">网络环境是有波动的，如下图：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-c14b123787b84cab9e226cb72b6d1e0c"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:720px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F7fa5a5a6-678f-44dd-9479-9e0ebfbd4a24%2FUntitled.png%3Fid%3Dc14b1237-87b8-4cab-9e22-6cb72b6d1e0c%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DGbQ1OoazqymsW6cDZ77NG2JvF4yIUu5_B8CG0YPV8aM?table=block&amp;id=c14b1237-87b8-4cab-9e22-6cb72b6d1e0c&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-cdc7e31cefe0476db8907251323920e2">发送的bytes和时间的曲线可能会产生波动，为此缓冲区可以缓解这些波动带来的影响。缓冲区可以将部分数据缓存起来，避免丢失。</div><div class="notion-text notion-block-4893d7dee6e446d1825f6040fb9a0749">这次lab的任务是完成StreamReassembler部分</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-f016818982d2408ea1ac717011a4b0f9"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F8d435ace-5442-4752-b834-e47d9a0b604e%2FUntitled.png%3Fid%3Df0168189-82d2-408e-a1ac-717011a4b0f9%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3Dz72jRqRgX5UZWUxb_ZIAh-UljAyoHF-weQxxjRM7_z4?table=block&amp;id=f0168189-82d2-408e-a1ac-717011a4b0f9&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-f105eca764db49b28ee2b6d9c1281942">同时说明了我们每个 lab 需要做的任务</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-496144abddf84510a2518bef166b2e81"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F422080c5-585d-48cf-8b70-127895829c42%2FUntitled.png%3Fid%3D496144ab-ddf8-4510-a251-8bef166b2e81%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DPwQ82FV9J3eQTQxBLIp4ABKsirF1uFsCGyAMIUYfA8g?table=block&amp;id=496144ab-ddf8-4510-a251-8bef166b2e81&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-36a7c264312a414ba64ccf59e5ed59c2" data-id="36a7c264312a414ba64ccf59e5ed59c2"><span><div id="36a7c264312a414ba64ccf59e5ed59c2" class="notion-header-anchor"></div><a class="notion-hash-link" href="#36a7c264312a414ba64ccf59e5ed59c2" title="2 Getting started"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><b>2 Getting started</b></span></span></h2><div class="notion-text notion-block-ec300c7525814760a58f2172a7fd257d">这边要求我们合并远程的 lab1-startercode 代码再继续工作。</div><div class="notion-text notion-block-c3788d3c7b1d4c88861984be02255557">我一开始没搞清楚这个 lab 的 git 分支逻辑，然后尝试了几次才搞清楚。</div><div class="notion-text notion-block-1ba7334eb5614647a287841b038dcd16">这个 lab 的每一个分支都对应一个lab</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-1b779991610747f19280203ac0a68ca4"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:288px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Feb189974-c31e-49f5-9ce6-5af20c957f94%2FUntitled.png%3Fid%3D1b779991-6107-47f1-9280-203ac0a68ca4%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DWmvikzjqyWVJTjexhRaK1k-H6GVHwOTESdBLQUrr01I?table=block&amp;id=1b779991-6107-47f1-9280-203ac0a68ca4&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-75e2ab1545ff43c689c79cc1205eff35">我们每完成一个分支，都要保存下来，然后在完成的工作基础上合并新的<code class="notion-inline-code">labx-startercode</code> 进行开发。</div><div class="notion-text notion-block-5f8e2cd6fb034359a41a99e1415b207a">比如说我自己的仓库有 master，我开了一个 lab0 的分支，然后完成了 lab0 的测试。</div><div class="notion-text notion-block-c1ed9310ae2f463299ccfe6ff526bd39">接着我从 lab0 checkout 出来一个 lab1 的分支，然后 <code class="notion-inline-code">merge lab1-startercode</code> 到这个新的 lab1 分支上，完成 lab1 的任务，以此类推</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-6f35cd418ebd4bcfbd95136a591fb379"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F675be5d9-92da-4959-b754-2a510f6dc36f%2FUntitled.png%3Fid%3D6f35cd41-8ebd-4bcf-bd95-136a591fb379%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3Dee_e4V-xrllwyqySE3HD7T0fVVvn7ivE2cSfWwoPmgw?table=block&amp;id=6f35cd41-8ebd-4bcf-bd95-136a591fb379&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-7a2494f5242b4a04b1476f9a73f5cbc8">示意图大概就是这样</div><div class="notion-text notion-block-c9c53682c2454e2d82b255193bdad16c">当然这是我自己的 git 风格，你可以在原始的 master 上进行迭代开发，而我习惯每进行一个 lab 都创建一个新的分支</div><div class="notion-text notion-block-a6976469b5674e30be8b39b4f9ce9bae">ok，搞定之后我们看需求</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-5d78756550fb4194a54775abfad103cf" data-id="5d78756550fb4194a54775abfad103cf"><span><div id="5d78756550fb4194a54775abfad103cf" class="notion-header-anchor"></div><a class="notion-hash-link" href="#5d78756550fb4194a54775abfad103cf" title="3 Putting substrings in sequence"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">3 Putting substrings in sequence</span></span></h2><div class="notion-text notion-block-1d12b2ba49e347139b1e5e0d53de2611">在 lab1 和 lab2 我们将要实现 TCP receiver，它能够接受数据报文并且将它们转换为可靠的字节流供应用程序从 socket 中读取数据。</div><div class="notion-text notion-block-0943ecfa83394e4ab9adf0726b81802b">TCP sender 将字节流分割为多个 segments，每个子串最大长度不超过 1460 bytes。网络是不稳定的，可能会产生乱序、丢失、重复提交等这些工作。receiver 必须要充足这些 segments 以实现可靠的字节流（reliable byte stream）</div><div class="notion-text notion-block-f325ec46b4ea44e0a6424c1957df00c1">本次 lab 的工作就是实现 <b>StreamReassmbler</b></div><ul class="notion-list notion-list-disc notion-block-9a7b111896924bc7970e905d0a2774d9"><li>接收 substrings，包括了组成 string 的 bytes，还有 string 在 stream 中 的唯一 index。这些数据可能是乱序的，需要我们组装起来</li></ul><ul class="notion-list notion-list-disc notion-block-b7c89a2033be41e8b213bc39f9a76341"><li>使用 ByteStream 作为输出，有序的把 bytes 写入到 ByteStream 中</li></ul><div class="notion-text notion-block-590bfd84e6db4b50afca15538f722fed">然后文档也给出了需要实现的接口，同时强调了 <b>StreamReassmbler </b>实现的是 TCP 的可靠性：从任意字节数据中恢复出原有的数据流，用此来对抗网络波动产生的丢失、复制等结果。</div><div class="notion-text notion-block-13dc1185f5ef4340aa0041cdf084695d">接口说明在<code class="notion-inline-code">stream_reassembler.hh</code> </div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-2928d819d35f444baca10c678105fae0" data-id="2928d819d35f444baca10c678105fae0"><span><div id="2928d819d35f444baca10c678105fae0" class="notion-header-anchor"></div><a class="notion-hash-link" href="#2928d819d35f444baca10c678105fae0" title="3.1 What’s the “capacity”?"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">3.1 <b>What’s the “capacity”?</b></span></span></h3><div class="notion-text notion-block-7a8e2bad977b4d1eac6a33865c8cba9f">主要是定义了 capacity 的含义：</div><ul class="notion-list notion-list-disc notion-block-2209abe6d5da4a15a78ddf720472cd9d"><li>一方面是限制了 ByteStream 的大小</li></ul><ul class="notion-list notion-list-disc notion-block-3522a643c7904241b6e4761b54ea9ecc"><li>另一方面限制了从 substrings 中读取的最大 bytes 数</li></ul><div class="notion-text notion-block-3c725c1b070143aeb9a0a73440056fd4">比如下面这种图</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-8f96a543e8e74de28c7b58027d1ca647"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F5643bc4a-1688-4992-94d3-b92d373525f2%2FUntitled.png%3Fid%3D8f96a543-e8e7-4de2-8c7b-58027d1ca647%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D6whKhH3p9bFd3b1pC8yybhzwpau93ymwAzVNymPHNp0?table=block&amp;id=8f96a543-e8e7-4de2-8c7b-58027d1ca647&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-42a65f5438d641d0a91a1c11965e101d">蓝色部分指的是已经处理完的数据，绿色部分指的是在 ByteStream 中的数据，红色部分指的是还没有放入 ByteStream 的部分，后面的部分就是还没开始处理的数据</div><hr class="notion-hr notion-block-4db1f94e9f7046b6a6f986cc93d4652d"/><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-f6fb2e68f4234d3fa55f57415fe81488" data-id="f6fb2e68f4234d3fa55f57415fe81488"><span><div id="f6fb2e68f4234d3fa55f57415fe81488" class="notion-header-anchor"></div><a class="notion-hash-link" href="#f6fb2e68f4234d3fa55f57415fe81488" title="分析和设计"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">分析和设计</span></span></h2><div class="notion-text notion-block-1415c3abb6a04722b42a6245c3bbccba">我们最终要设计的结构如下所示：</div><ul class="notion-list notion-list-disc notion-block-3ef352184e7849d9adb89a5899b8ea64"><li>有一个 buffer 帮助我们缓存不能立即放入 ByteStream 的数据</li></ul><ul class="notion-list notion-list-disc notion-block-eb5d8dd963e6425c9a9ce0e7610fc9e0"><li>一个 ByteStream 供读写</li></ul><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-2dc4970548544397812a94342a0dfa48"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F4ec99f2d-fb4c-40dc-805b-e197e07ea7e3%2FUntitled.png%3Fid%3D2dc49705-4854-4397-812a-94342a0dfa48%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3Drpo79eTvE9Ql-k3QySkWXuqP8TSoFnc9a_lHg5h-Gps?table=block&amp;id=2dc49705-4854-4397-812a-94342a0dfa48&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-ba8e027386524cbe89cc24cbca088566">特别的我们需要注意这种情况：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-ae40e67c049d4bf68c0051f969d937a6"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fa97bdd9a-18fe-4972-b13a-7d11344caa68%2FUntitled.png%3Fid%3Dae40e67c-049d-4bf6-8c00-51f969d937a6%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DncUB0kiMiHhbSlfaltPYnrKUPEmPBTbGaM251yZ2_B0?table=block&amp;id=ae40e67c-049d-4bf6-8c00-51f969d937a6&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-cfb3a9269f6846ca9d094251223d7efe">假设我们读入 5 和 6，那么后面的三个 bytes 也应该一并放入 ByteStream 中</div><div class="notion-text notion-block-bb706686401245be82ea31cca5e9d7bc">然后我们需要合并 buffer 中的 bytes，例如：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-4d0f392f1e5f429f850efed1a438f94e"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:654px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F6e46f64a-9189-45f0-9d23-a3e076a7853d%2FUntitled.png%3Fid%3D4d0f392f-1e5f-429f-850e-fed1a438f94e%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DyTxRB4KJc9MMRkq7oaEZQEsraDBreGOf98hlfDX_Ey4?table=block&amp;id=4d0f392f-1e5f-429f-850e-fed1a438f94e&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-f46abb36a1cb4f389dd8f58120329425">如果某次输入中带有10、11，例如 index = 7，data = “aaaa”，那么 7 ~ 12 的数据应该立即合并 </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-32cb590933924546932c5a057b42a831" data-id="32cb590933924546932c5a057b42a831"><span><div id="32cb590933924546932c5a057b42a831" class="notion-header-anchor"></div><a class="notion-hash-link" href="#32cb590933924546932c5a057b42a831" title="实验结果"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">实验结果</span></span></h2><div class="notion-text notion-block-3c70c6a916134d1fb81251c0a174c7f0"><b>第一次尝试</b></div><div class="notion-text notion-block-090101710872419881e2775351b898e4">第一个版本，用 set 来标记出现过的 index，用 map 来作为缓冲区，每次<code class="notion-inline-code">push_substring</code>的时候都先往 map 里面放，然后再尝试从 map 里面取 byte 放到 ByteStream 里面</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-3110489d12c441b6856c17429559b2c4"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fab6ed6db-ff2c-456a-b4a5-6d5b4e88a893%2FUntitled.png%3Fid%3D3110489d-12c4-41b6-856c-17429559b2c4%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3Dj-JLDwyIlgDLwijS4uzfuTSKaKj1WqaqggZ62rnWn4E?table=block&amp;id=3110489d-12c4-41b6-856c-17429559b2c4&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-d8a9dd2590e742f7bc7183c195e427bb">虽然通过了本地测试，但是性能比较糟糕。</div><div class="notion-text notion-block-2bb26587bcc848bf8023a2d213a6243f">首先是按照 byte 插入 buffer 性能太低了，然后用 set 标记使用过的 index 是没有必要的，因为最终表示的字节流是一致的，我当时写的时候没有注意到这一点</div><div class="notion-text notion-block-d4be93c78a3a44daafbaa4e47f78f4ea"><b>第二次尝试</b></div><div class="notion-text notion-block-e6a09b3630024e17ac4fcdffb8eddda3">经过第一次尝试，现在问题清晰很多了，问题本质就是一个算法问题：</div><div class="notion-text notion-block-de2d26d89fe94f1f93b1942a11751e5a">对于一大段的 bytes，有多个切片（线段），切片之间可能重叠，每次输入会提供一个切片和起点，对于能够立即写入的切片，应该马上写入到 ByteStream 中，不能写入的切片应该暂存，但是需要丢弃容量之外的 bytes。</div><div class="notion-text notion-block-2af8702f3fe14a218d9fbeddfeafcb78">显然我们从线段的角度考虑更优，这里的线段可能会重叠、乱序、重复，但是它们表示的 bytes 都是<b>一致</b>的，这个性质很重要。</div><div class="notion-text notion-block-c1c6736c4ff645ecb4d19f7e77794429">对于这个角度，我最先想到用堆来处理，每次都把一个 node: &lt;index, slice&gt; 放置到堆中，然后尝试将堆顶写入到 ByteSteam 中。但是用堆经过若干次插入后可能会变得很冗余，也就是线段重叠的部分会很多，这是不符合设计的（lab1 的 FAQs 也有说明）。</div><div class="notion-text notion-block-395f1d45c7bf41d483616b984ac1d420">于是可以在堆上进一步尝试，我们根据 node 的 index 作为第一索引，存放到一个有序的数据结构中，我们对这个数据结构的要求是：node 之间没有重叠的部分。</div><div class="notion-text notion-block-15e66d2bc0fc42c08e8bee34dc7e7195">那么在 <code class="notion-inline-code">push_substring</code> 的时候，对于当前的 node，我们可以检查它前后的 node 是否能合并，如果能合并则消除重叠部分，直到不能合并为止。这样能够保证数据结构中的 node 都是无重叠的。显然用二叉排序树能够很好的满足我们的要求，还可以用二分来加快查找过程。在 cpp 里面，set 的底层是红黑树，使用起来很方便。</div><div class="notion-text notion-block-08bf70fac87a4053aa5c78c036b8518f">这个核心数据结构想明白之后，接下来的实现就简单了。</div><div class="notion-text notion-block-ba3ba3bc670947178cdd4d08720b2688"><code class="notion-inline-code">push_substring</code>的流程</div><ol start="1" class="notion-list notion-list-numbered notion-block-17d9352e30804dcab8acf581bd0413b4"><li><b>检查是否越界</b></li><ol class="notion-list notion-list-numbered notion-block-17d9352e30804dcab8acf581bd0413b4"><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-a46a23f26d96450d869aa1909e954a42"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:432px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F639b4d70-a38c-4ecd-ba99-a4054f794689%2FUntitled.png%3Fid%3Da46a23f2-6d96-450d-869a-a1909e954a42%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D0_bc34IIizVyQNYQ7JMiAmptAmuicsoSqcBcekXWxlU?table=block&amp;id=a46a23f2-6d96-450d-869a-a1909e954a42&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-ccdd77bfd67d4175b9c1f92238e64755">假设 recv_index_ 是期望接收的第一个 index，而 last_store_index_ 是最后不能接收的字符（index = 0，cap = 8 时，last_store_index_ = 8）</div><div class="notion-text notion-block-b9bda57951514598861aa0a15891f877">那么从图中来看，我们需要丢弃 1 和 5 两种情况</div></ol></ol><ol start="2" class="notion-list notion-list-numbered notion-block-34245907fb6e472abab98e52e9be6f28"><li><b>裁剪线段</b></li><ol class="notion-list notion-list-numbered notion-block-34245907fb6e472abab98e52e9be6f28"><div class="notion-text notion-block-c868384ce1954f04a2c91e0398b6d919">根据上图，2 和 4 都需要裁剪出处于区间的部分，3不用裁剪</div><div class="notion-text notion-block-8e6b2ccd48c547fdaf26a57efdab0d92">特别的，4 需要我们进行 eof 的判断，如果 4 是 eof 的，那么此时这个 eof 指令是无效的，因为最后的字符被裁剪掉了</div><div class="notion-text notion-block-1c991e9a897b4ac3b7d68b88c17d2b3b">另外我们还需要考虑一种完全覆盖区间的情况，即 2 + 3 + 4，这中情况即需要裁剪头部，也需要裁剪尾部，因此我们不能用<code class="notion-inline-code"> if … else … </code> 进行处理</div></ol></ol><ol start="3" class="notion-list notion-list-numbered notion-block-9af50f63abdf414e842f9a578da509a7"><li><b>在树中合并</b></li><ol class="notion-list notion-list-numbered notion-block-9af50f63abdf414e842f9a578da509a7"><div class="notion-text notion-block-c19cc13c493f4c2192ab61f2aec6d278">由于最终指向的 bytes 是<b>一致</b>的，我们处理起来的难度大大降低了</div><div class="notion-text notion-block-1da6676e8b544f02aeb99ecaaa2b678a">现在我们得到了经过裁剪后的 node，我们想要把它插入到树中，保持树中 node 无重叠的性质，那么就需要我们对树的 node 进行合并。</div><div class="notion-text notion-block-a4045a44b82e4d4ab8c253521f6ee01a">先是<span class="notion-inline-underscore">向后合并</span></div><div class="notion-text notion-block-2b1394627de14971918bcd761f1849d1">我们根据lower_bound，找到结构中对应的下一个 node，判断两个 node 的位置关系</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-5d95961eecb148e6b1496d58fec8a176"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F6b0dd19f-81ef-4fa3-b574-405d29cfada9%2FUntitled.png%3Fid%3D5d95961e-ecb1-48e6-b149-6d58fec8a176%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3Dy-ii-LswzJiIyNtzYwe09jAnogbHqLiIeiH9ZlcNRhk?table=block&amp;id=5d95961e-ecb1-48e6-b149-6d58fec8a176&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-9a2d9f9eb6c94b859f6af21c9ca9c496">由上图我们可以总结出三种类型的位置关系，分别是重叠、不重叠、覆盖</div><li>case 1，如果不重叠，退出向后合并</li><li>case 3，如果覆盖，把被覆盖的线段移除，因为当前线段能够表示被覆盖的线段了，继续向后合并</li><li>case 2，如果重叠，那么合并两条线段，移除下一条线段，继续向后合并</li><div class="notion-text notion-block-5aca1b0969224717871d75396aaf9474">向后合并完毕后，考虑<span class="notion-inline-underscore">向前合并</span></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-82efe1eadd5f4e9bacee20cfce1fd0ea"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Ff4ac74d5-f942-4cde-bf40-b3d3123e68ca%2FUntitled.png%3Fid%3D82efe1ea-dd5f-4e9b-acee-20cfce1fd0ea%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DbuRxc9YhNa8dpHy_sTEodnNnUeSVUMHA7viA8jP_arM?table=block&amp;id=82efe1ea-dd5f-4e9b-acee-20cfce1fd0ea&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-04ef57f9f96143ee8d67a70d1333782c">情况也是一样的</div><div class="notion-text notion-block-ad901e06be9b4cb4879a63c3c2711b91">那么经过最终的前后合并，树中的 node 都是无重叠的了</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-ab916ff859ba45669e4216fae2b0bfe5"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fa706dbbe-5a9f-48e6-b167-e491d890c16a%2FUntitled.png%3Fid%3Dab916ff8-59ba-4566-9e42-16fae2b0bfe5%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DPTGM9oeQYhY_jBfsXd2807nrXPIYozUNvrn77RO1_6Y?table=block&amp;id=ab916ff8-59ba-4566-9e42-16fae2b0bfe5&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure></ol></ol><ol start="4" class="notion-list notion-list-numbered notion-block-7bf202a2d5d341b7bf35415092cc03bb"><li>尝试写入 ByteStream</li><ol class="notion-list notion-list-numbered notion-block-7bf202a2d5d341b7bf35415092cc03bb"><div class="notion-text notion-block-1ff70bc0beb047c2b16601beb3a80ebc">当我们树中第一个 node 的 index，恰好等于 recv_index_ 的时候，就能进行写入了</div></ol></ol><ol start="5" class="notion-list notion-list-numbered notion-block-2dd80ecd1fc947558a2b2368a22fa6a7"><li>eof 的处理</li><ol class="notion-list notion-list-numbered notion-block-2dd80ecd1fc947558a2b2368a22fa6a7"><div class="notion-text notion-block-6ccb6e2ccfa742c59cfb5d4581ce36b9">我们先考虑一下 eof 什么时候是有效的，当且仅当 eof = true 且 最后一个字符被读入的时候就是有效的。那么回到最原始的这张图</div></ol></ol><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-ae351a62e5af42e094d12168085e2b85"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:432px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F639b4d70-a38c-4ecd-ba99-a4054f794689%2FUntitled.png%3Fid%3Dae351a62-e5af-42e0-94d1-2168085e2b85%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3Dae1cxwzrvrOi-_B77HEuzxabpR8loxBukd24uPOMZHA?table=block&amp;id=ae351a62-e5af-42e0-94d1-2168085e2b85&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-f2bb1526c4004087afcccc51ffb9543b">对于 2 和 3，它们的 eof 都是有效的，对于 4 和 5，它们的 eof 都是无效的，因为最后一个字符被丢弃了，而对于 1 呢，测试用例并没有体现，我也把它视为无效的情况了。</div><div class="notion-text notion-block-ddbb3ede3de840dc957043724cdc45bd">最终通过了测试，满足了 lab 1 的 0.5 s 性能需求</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-d0f80dde68394e5185b0e042c9779093"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fb1826731-6b64-4784-86e5-7a93f69aeb16%2FUntitled.png%3Fid%3Dd0f80dde-6839-4e51-85b0-e042c9779093%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D8sQ8-WJE1AiAYjmPW65D31lAVBzAsnRrmGdBgT-3nFc?table=block&amp;id=d0f80dde-6839-4e51-85b0-e042c9779093&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-86123e58279242d6a47ea320bda54488" data-id="86123e58279242d6a47ea320bda54488"><span><div id="86123e58279242d6a47ea320bda54488" class="notion-header-anchor"></div><a class="notion-hash-link" href="#86123e58279242d6a47ea320bda54488" title="一些容易忽略的点"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">一些容易忽略的点</span></span></h2><div class="notion-text notion-block-5d8cb5b264c84ed49bb5a67aca3fdab3">一定要认真看 3.2 FAQs</div><ul class="notion-list notion-list-disc notion-block-b7a738f948c142fd85c978778dd88754"><li>切片表示的最终的 bytes 是一致的</li></ul><div class="notion-text notion-block-0b077300d1314936b5b380952a0d13f8">在 3.2 FAQs 中提到了</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-ebd9629687eb4023a7f5bf7eb9c38b74"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fbe83fba7-6f5f-4c8e-aca9-40a2026b4797%2FUntitled.png%3Fid%3Debd96296-87eb-4023-a7f5-bf7eb9c38b74%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DPLc_t3kayX-BIisih7s5aUxapvMU-FLK8FDq0VF5t54?table=block&amp;id=ebd96296-87eb-4023-a7f5-bf7eb9c38b74&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><ul class="notion-list notion-list-disc notion-block-77316f0f769344e59296447d966230ea"><li>buffer 中的最好不要存储重叠的子串</li></ul><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-08d94e185652479bae9b082d0f4dc154"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F9facd4b5-4ace-48d0-9a97-b6b6b5c3c9f6%2FUntitled.png%3Fid%3D08d94e18-5652-479b-ae9b-082d0f4dc154%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DROppxtB8Zzx5XNRPqADRui_7Tm-dgvfLrM1FHwgNavM?table=block&amp;id=08d94e18-5652-479b-ae9b-082d0f4dc154&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><ul class="notion-list notion-list-disc notion-block-f3ceb975da7b4ba3b0dfd979314a39f6"><li>注意计算 first unassembled 和 first unacceptable</li></ul><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-a2822dc2866c4ce18b5557a03e02afb6"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F3d02df14-3489-43e6-99d4-ac75f4f08f14%2FUntitled.png%3Fid%3Da2822dc2-866c-4ce1-8b55-57a03e02afb6%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DRvZpexkvjzOJunAjoHOQ5uUNKJlT-sNLPc2Uzb0j_oA?table=block&amp;id=a2822dc2-866c-4ce1-8b55-57a03e02afb6&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><ul class="notion-list notion-list-disc notion-block-fbe357bb5a3043d4bdaf1afc82f6bb91"><li>有些不确定的地方，看测试用例确定应该怎么处理</li></ul><div class="notion-text notion-block-eb3826b21d204061abe77fcbead46837">如果还不能确定，就自己约定一个一致的风格好了</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-70381c645a4346af856c36a7f362fb74" data-id="70381c645a4346af856c36a7f362fb74"><span><div id="70381c645a4346af856c36a7f362fb74" class="notion-header-anchor"></div><a class="notion-hash-link" href="#70381c645a4346af856c36a7f362fb74" title="总结"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">总结</span></span></h2><div class="notion-text notion-block-ce3bbdfe2150423690b5f4dd7967b7ee">其实这个 lab1 总结出来后就是一个算法问题了，比较考验学生的算法能力。</div><div class="notion-text notion-block-b1481780a1f0456abc032415c3d2a193">整个<code class="notion-inline-code">StreamReassembler</code>实现的功能更能很像上计网课的时候讲到的滑动窗口，没想到是这种数据结构实现的。</div><div class="notion-text notion-block-d15b71d5e3224722a8da35f356c7b319"><b>debug</b></div><div class="notion-text notion-block-6e9c812d262a4ae1a22c14aad3aa2730">coding 途中我遇到了不稳定复现的 bug，而且由于没有好用的调试工具（或者说自己暂时没学会如何更好地调试 cpp），导致 debug 非常困难
不过好在最终成功 debug 了，下面是我的一些心得：</div><ul class="notion-list notion-list-disc notion-block-da424da1585c414f8e29380322f7896e"><li>计算机不会骗人，不要怀着“我的代码没问题啊”这种心态去 debug，不然自己很容易崩溃</li></ul><ul class="notion-list notion-list-disc notion-block-ee6a996b3e8e4ebda98faebee9eed0ab"><li>高效的 debug 工具非常重要，能帮你节省很多时间</li><ul class="notion-list notion-list-disc notion-block-ee6a996b3e8e4ebda98faebee9eed0ab"><li>尽量让自己的工作自动化，能够节省很多时间</li></ul></ul><ul class="notion-list notion-list-disc notion-block-0b2f6e5fdb6044c58a12db6cacebc4d4"><li>想办法弄到导致 bug 的数据，如果没办法弄到，观察日志分析走到哪一步导致不符合预期的结果，然后根据调用链跟进</li></ul><div class="notion-blank notion-block-7363e8255a774012b432db2b084971ed"> </div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Go 并发编程 - channel 小结]]></title>
            <link>https://hhmy27.github.io//go-channel</link>
            <guid>https://hhmy27.github.io//go-channel</guid>
            <pubDate>Tue, 29 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[channel 使用注意事项]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-5625b2fb6c824130b3360ec9a19d28ef"><div class="notion-viewport"></div><div class="notion-table-of-contents notion-gray notion-block-dd19b74c09914037add791435eeeb4e3"><a href="#cb6912ad9f904db489bd7e660c6d6558" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">简介</span></a><a href="#35b8e4af272c49669ae1944bc4de8d66" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">初始化</span></a><a href="#4a044f168f98418fbf4b554d9f7ab691" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">channel 的使用</span></a><a href="#e689bcabb635420f97b72ece1c8e620c" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">遍历</span></a><a href="#0492324d62704c528055fc03f3e5e583" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">select</span></a><a href="#d23480cc11e94cdc85fe812a45f69bb3" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">通道的限制</span></a><a href="#39e8470d5ffc43eb80fe8855cb185f5c" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">通道的关闭和异常</span></a><a href="#622603a1a46c4a239ae1760fb6bd08d0" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">源码分析</span></a><a href="#440692534dd5466d976e286b6f96ecb6" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">Ref</span></a></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-cb6912ad9f904db489bd7e660c6d6558" data-id="cb6912ad9f904db489bd7e660c6d6558"><span><div id="cb6912ad9f904db489bd7e660c6d6558" class="notion-header-anchor"></div><a class="notion-hash-link" href="#cb6912ad9f904db489bd7e660c6d6558" title="简介"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">简介</span></span></h2><blockquote class="notion-quote notion-block-ce4eedd60c1f4a258125b344b2c2f0eb">使用通信(channel)来共享内存，而不是通过共享内存来通信</blockquote><div class="notion-text notion-block-e5619cb1c99e4a62b667ebd4c97d8351">channel 可以用于实现一个生产者消费者模型，用于生产者和消费者之间共享数据</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-92cab5571b7f440084483908167acc2e"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F2a1fa1f5-d64f-45f5-9d9f-b2c513e17cfa%2FUntitled.png%3Fid%3D92cab557-1b7f-4400-8448-3908167acc2e%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DCtzvdPQVhS87FWoR_cVJLN6DcBvcJv_WFkddol3k2xU?table=block&amp;id=92cab557-1b7f-4400-8448-3908167acc2e&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-4e2ee7852ec442008f67aa758f989d28">日常使用中，我们可以用来做 goruntine 的同步，channel 使用简单效果可靠，下面来看看如何 channel 吧</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-35b8e4af272c49669ae1944bc4de8d66" data-id="35b8e4af272c49669ae1944bc4de8d66"><span><div id="35b8e4af272c49669ae1944bc4de8d66" class="notion-header-anchor"></div><a class="notion-hash-link" href="#35b8e4af272c49669ae1944bc4de8d66" title="初始化"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">初始化</span></span></h2><div class="notion-text notion-block-a4d9ba7ebf6a4fb29e12ad16851e12e8">使用 make 来初始化一个 channel，channel 支持多种类型元素</div><blockquote class="notion-quote notion-block-fc8fe9c06c004178b327bcdf536e751a">除了 channel 以外，make 还可以用来初始化 slice、map</blockquote><pre class="notion-code language-go"><code class="language-go"><span class="token literal-property property">ch1</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span> <span class="token comment">// 初始化一个大小为 3 的 channel</span>
<span class="token literal-property property">ch2</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">)</span>    <span class="token comment">// 初始化一个无缓冲的 channel</span></code></pre><div class="notion-text notion-block-a8736c6747af415280b2001410f585c7">channel 根据我们初始化的大小分为有缓冲 channel 和无缓冲 channel</div><div class="notion-text notion-block-6d594fd267944fef9035e0c52a132e21"><b>有缓冲 channel</b></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-a43517fcb3e5480b96a47777591c41a0"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F61dd0dd3-79a3-4e44-ab24-68b8d43fe1e3%2FUntitled.png%3Fid%3Da43517fc-b3e5-480b-96a4-7777591c41a0%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DbaVexZy5v2EpDYDx3AC0YO6JPmlXHpTB_F9ZgBzaGJU?table=block&amp;id=a43517fc-b3e5-480b-96a4-7777591c41a0&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-5f69223dad1c40cb851291adf56f471d">这种 channel，就相当于生产者消费者模型里面的缓冲队列，在这里我们初始化了大小为 3 的队列。</div><div class="notion-text notion-block-2abc01eabeff4837aa9f60d39e95bd6c">这种有缓冲队列的最大特点是<b>异步</b>，生产者和消费者之间不必同步执行。但是也会引起阻塞：</div><ul class="notion-list notion-list-disc notion-block-bb6cc21bf9dc4fe3ab0322d5e9d2904f"><li>队列满时生产者阻塞</li></ul><ul class="notion-list notion-list-disc notion-block-32396addcdd04f598601b3aac4502c36"><li>队列空时消费者阻塞</li></ul><div class="notion-text notion-block-5d84710d30c444ca825821f42e34bbae">队列未满且存有一定数据的时候，生产者和消费者可以各司其职的完成存取。</div><div class="notion-text notion-block-fdb654979f114d6f8a172fd3b738b01c">举一个生活中的例子，有缓冲的 channel 相当于信箱，信箱未满的时候邮差直接把信件放到信箱里面就行了，而我们可以随时去检查信箱取出信件。（这里其实不太准确，日常生活中我们检查如果空信箱的话直接走掉了，但是程序中如果 channel 是空的话等待的 goroutine 是会被阻塞的，举这个例子只是为了突出异步的特点。）</div><div class="notion-text notion-block-380d3e0e52c04bee9382c2354ef223e1"><b>无缓冲 channel</b></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-44e648a9d4dc498f8a8097db361ffe1a"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fd027bf46-2c07-42db-ba57-240f594948dd%2FUntitled.png%3Fid%3D44e648a9-d4dc-498f-8a80-97db361ffe1a%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DQyz-SoSYd0jiVCQDvR-8cpgad7C4zT8g7pZECp6WWfg?table=block&amp;id=44e648a9-d4dc-498f-8a80-97db361ffe1a&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-8f3f9877e0b44bdf923b5065e9416867">这种情况的消费者和生产者是<b>同步</b>的，同步指的是要求双方都准备好才能进行下一步。生产者生产数据后阻塞到数据被取走，而消费者获取数据之前也是阻塞在尝试获取的状态。下面这张图更加形象：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-9a4ab4a1efca4fc0902bc183aef7d896"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fba418f76-0195-47b0-8dd9-fd8a06aae594%2FUntitled.png%3Fid%3D9a4ab4a1-efca-4fc0-902b-c183aef7d896%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DARvXGhYB75kudvv3097ckzCo4GcDLSV_a7jq9FBcVeM?table=block&amp;id=9a4ab4a1-efca-4fc0-902b-c183aef7d896&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-45f909403949498e8384d121798fd069">还是信箱的例子，无缓冲的 channel 相当于没有信箱了，快递员和收件人必须同时准备好才能结束发件和收件的动作。</div><div class="notion-text notion-block-dc744ef06b2d437bac047642b49e26ce">开发中如果我们使用无缓冲 channel，务必要小心死锁的情况</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestChannel</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch2</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">)</span>
	ch2 <span class="token operator">&lt;</span><span class="token operator">-</span> <span class="token number">1</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">&lt;</span><span class="token operator">-</span>ch2<span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-961b3d1a6e284f99a480aed4f4563523">这种情况运行后是会发生死锁的</div><div class="notion-text notion-block-18a4ad0ef7754adbb3fc72d31582973e"><code class="notion-inline-code">fatal error: all goroutines are asleep - deadlock!</code></div><div class="notion-text notion-block-97867ef1368a43f590e5b7e1d18b10e1">正确的写法是使用 goroutine 提前准备好接收：</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestChannel</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch2</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">)</span>
	go <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">&lt;</span><span class="token operator">-</span>ch2<span class="token punctuation">)</span>
	<span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	ch2 <span class="token operator">&lt;</span><span class="token operator">-</span> <span class="token number">1</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-2c8f97e2ffdd45578cb3b7535467a8fb">另外需要注意的是，我们需要提前准备好接收，不然还是会死锁</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestChannel</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch2</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">)</span>
	ch2 <span class="token operator">&lt;</span><span class="token operator">-</span> <span class="token number">1</span> <span class="token comment">// 提前发送, deadlock</span>
	go <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">&lt;</span><span class="token operator">-</span>ch2<span class="token punctuation">)</span>
	<span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-4a044f168f98418fbf4b554d9f7ab691" data-id="4a044f168f98418fbf4b554d9f7ab691"><span><div id="4a044f168f98418fbf4b554d9f7ab691" class="notion-header-anchor"></div><a class="notion-hash-link" href="#4a044f168f98418fbf4b554d9f7ab691" title="channel 的使用"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">channel 的使用</span></span></h2><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-e689bcabb635420f97b72ece1c8e620c" data-id="e689bcabb635420f97b72ece1c8e620c"><span><div id="e689bcabb635420f97b72ece1c8e620c" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e689bcabb635420f97b72ece1c8e620c" title="遍历"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">遍历</span></span></h3><div class="notion-text notion-block-de05ae49d51945769c8d1550b84d41e2">遍历有两种方式</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestFor</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch1</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span>
	<span class="token keyword">for</span> <span class="token literal-property property">i</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">100</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		ch1 <span class="token operator">&lt;</span><span class="token operator">-</span> i
	<span class="token punctuation">}</span>
	
	<span class="token comment">// 方式一, 用 for 取出所有元素，建议使用</span>
	<span class="token keyword">for</span> <span class="token literal-property property">i</span> <span class="token operator">:</span><span class="token operator">=</span> range ch1 <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"-"</span><span class="token punctuation">)</span>

	<span class="token comment">// 方式二</span>
	<span class="token keyword">for</span> <span class="token punctuation">{</span>
		i<span class="token punctuation">,</span> <span class="token literal-property property">ok</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token operator">&lt;</span><span class="token operator">-</span>ch1 <span class="token comment">// 通道关闭后再取值ok=false</span>
		<span class="token keyword">if</span> <span class="token operator">!</span>ok <span class="token punctuation">{</span>
			<span class="token keyword">break</span>
		<span class="token punctuation">}</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-469132cd1c194e39b399b0447f476f7a">推荐使用方式一，简单又清晰</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-0492324d62704c528055fc03f3e5e583" data-id="0492324d62704c528055fc03f3e5e583"><span><div id="0492324d62704c528055fc03f3e5e583" class="notion-header-anchor"></div><a class="notion-hash-link" href="#0492324d62704c528055fc03f3e5e583" title="select"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">select</span></span></h3><div class="notion-text notion-block-c22c2f735f5e478b9ba8f2acfd3b3e9f">搭配 select 是的我们在遍历 channel 的时候可以进行选择性操作，例如下面的例子：</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">fibonacci</span><span class="token punctuation">(</span><span class="token parameter">c<span class="token punctuation">,</span> quit chan int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	x<span class="token punctuation">,</span> <span class="token literal-property property">y</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span>
	<span class="token keyword">for</span> <span class="token punctuation">{</span>
		select <span class="token punctuation">{</span>
		<span class="token keyword">case</span> c <span class="token operator">&lt;</span><span class="token operator">-</span> x<span class="token operator">:</span>
			x<span class="token punctuation">,</span> y <span class="token operator">=</span> y<span class="token punctuation">,</span> x<span class="token operator">+</span>y
		<span class="token keyword">case</span> <span class="token operator">&lt;</span><span class="token operator">-</span>quit<span class="token operator">:</span>
			fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"quit"</span><span class="token punctuation">)</span>
			<span class="token keyword">return</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

func <span class="token function">TestSelect</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">c</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">)</span>
	<span class="token literal-property property">quit</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">)</span>
	go <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">for</span> <span class="token literal-property property">i</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
			fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">&lt;</span><span class="token operator">-</span>c<span class="token punctuation">)</span>
		<span class="token punctuation">}</span>
		quit <span class="token operator">&lt;</span><span class="token operator">-</span> <span class="token number">0</span>
	<span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token function">fibonacci</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> quit<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// output:</span>
<span class="token comment">//0</span>
<span class="token comment">//1</span>
<span class="token comment">//1</span>
<span class="token comment">//2</span>
<span class="token comment">//3</span>
<span class="token comment">//5</span>
<span class="token comment">//8</span>
<span class="token comment">//13</span>
<span class="token comment">//21</span>
<span class="token comment">//34</span>
<span class="token comment">//quit</span></code></pre><div class="notion-text notion-block-88beb74ed9d944e1915986187917f143">上面的例子中，我们启动了一个 goruntine，读取 c 中前 10 个元素，然后我们调用 fibonacci，它执行了一个<code class="notion-inline-code">for … select</code> 的操作，第一个 select 不断往 c 中写入，第二个 select 从 quit 中读取，一旦从 quit 中获取到元素后，就退出循环</div><div class="notion-text notion-block-233c6537e560437d96072e67d917955c">而在外部，我们成功获取到了 fibonacci 数，可以看到 select 的使用和 Go 简单易用且强大的并发能力。</div><div class="notion-text notion-block-58e243fcc9944d4c8a83b6c1e12bbb5c"><b>进阶：超时 channel</b></div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestTimeout</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">c1</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan string<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>
	go <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Second <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span>
		c1 <span class="token operator">&lt;</span><span class="token operator">-</span> <span class="token string">"result 1"</span>
	<span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	select <span class="token punctuation">{</span>
	<span class="token keyword">case</span> <span class="token literal-property property">res</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token operator">&lt;</span><span class="token operator">-</span>c1<span class="token operator">:</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span>
	<span class="token keyword">case</span> <span class="token operator">&lt;</span><span class="token operator">-</span>time<span class="token punctuation">.</span><span class="token function">After</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Second <span class="token operator">*</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">:</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"timeout 1"</span><span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">// output:</span>
<span class="token comment">// timeout 1</span>

func <span class="token function">After</span><span class="token punctuation">(</span>d Duration<span class="token punctuation">)</span> <span class="token operator">&lt;</span><span class="token operator">-</span>chan Time <span class="token punctuation">{</span>
	<span class="token keyword">return</span> <span class="token function">NewTimer</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token constant">C</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-2a7cc26a56ab4fc8870be61fc147abbf">我们通过计时器和 select，可以完成定时任务，比如说上面的代码，我们调用After获取到一个 <code class="notion-inline-code">&lt;-chan</code>，触发了相应的 case，终止了 select</div><div class="notion-text notion-block-4d8ce04055f24abc989e1c647275093e">上面的思想可以扩展到在某时刻 or 某条件下做相应操作的功能</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-d23480cc11e94cdc85fe812a45f69bb3" data-id="d23480cc11e94cdc85fe812a45f69bb3"><span><div id="d23480cc11e94cdc85fe812a45f69bb3" class="notion-header-anchor"></div><a class="notion-hash-link" href="#d23480cc11e94cdc85fe812a45f69bb3" title="通道的限制"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">通道的限制</span></span></h3><div class="notion-text notion-block-1459375836c241f68f8a81c5c8760bd1">有的时候我们需要限制通道的读写，那么我们可以提前声明通道的类型，这种通道称为单向通道，特点是限制了写入或者读取的操作：</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">op</span><span class="token punctuation">(</span><span class="token parameter">writer <span class="token operator">&lt;</span><span class="token operator">-</span>chan int<span class="token punctuation">,</span> reader chan<span class="token operator">&lt;</span><span class="token operator">-</span> int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> <span class="token literal-property property">data</span> <span class="token operator">:</span><span class="token operator">=</span> range writer<span class="token punctuation">{</span>
		reader <span class="token operator">&lt;</span><span class="token operator">-</span> data
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-39e8470d5ffc43eb80fe8855cb185f5c" data-id="39e8470d5ffc43eb80fe8855cb185f5c"><span><div id="39e8470d5ffc43eb80fe8855cb185f5c" class="notion-header-anchor"></div><a class="notion-hash-link" href="#39e8470d5ffc43eb80fe8855cb185f5c" title="通道的关闭和异常"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">通道的关闭和异常</span></span></h3><div class="notion-text notion-block-754dbdf4a16d4d23bd5f2b4e09e5f662">使用 <code class="notion-inline-code">close()</code> 函数可以关闭一个通道，如果我们确认通道不再读写，那么记得及时关闭</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestClose</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span>
	defer <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
		<span class="token function">close</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span>
	<span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token comment">// do something ... </span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-a65b2800db9c4690a6344fa549dfc998">这里我们会好奇，对已经关闭的通道读写会有什么影响？这里也是面试会经常问到的，我们自己实验一下看看会有什么结果：</div><div class="notion-text notion-block-a52aa8009cca42798470355890c98700">首先我们根据缓冲类型分为无缓冲 channel 和有缓冲 channel 实验：</div><div class="notion-text notion-block-f4183c6fe51f4112bac609371615add2"><b>无缓冲 channel</b></div><div class="notion-row notion-block-482846820b2f467b8a87004db28426b9"><div class="notion-column notion-block-56a2995d4b9b4f6eb5b44c41d460db3a" style="width:calc((100% - (1 * min(32px, 4vw))) * 0.5)"><pre class="notion-code language-go"><code class="language-go"><span class="token comment">// 无缓冲 channel，读</span>
func <span class="token function">TestClose</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">)</span>
	<span class="token function">close</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">&lt;</span><span class="token operator">-</span>ch<span class="token punctuation">)</span> <span class="token comment">// 0</span>
<span class="token punctuation">}</span></code></pre></div><div class="notion-spacer"></div><div class="notion-column notion-block-5f891c8c3709428e8e629de8aa7e2154" style="width:calc((100% - (1 * min(32px, 4vw))) * 0.5)"><pre class="notion-code language-go"><code class="language-go"><span class="token comment">// 无缓冲 channel，写</span>
func <span class="token function">TestClose</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">)</span>
	<span class="token function">close</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span>
	ch <span class="token operator">&lt;</span><span class="token operator">-</span> <span class="token number">0</span> <span class="token comment">// panic: send on closed channel</span>
<span class="token punctuation">}</span></code></pre></div><div class="notion-spacer"></div></div><div class="notion-text notion-block-2414047a16f84d1d87324cfa2093150f">读会读取到零值，而写会 panic</div><div class="notion-text notion-block-ca4a6aaeada9412ebe0d3b2456ae0f9e"><b>有缓冲 channel</b></div><div class="notion-row notion-block-c5359ba4597a48cb8f90967cb8768eb8"><div class="notion-column notion-block-63796a71c330484598470a9a17ec9bf0" style="width:calc((100% - (1 * min(32px, 4vw))) * 0.5)"><pre class="notion-code language-go"><code class="language-go"><span class="token comment">// 有缓冲 channel, 无写入值， 读</span>
func <span class="token function">TestClose</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span>
	<span class="token function">close</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">&lt;</span><span class="token operator">-</span>ch<span class="token punctuation">)</span> <span class="token comment">// 0</span>
<span class="token punctuation">}</span></code></pre><pre class="notion-code language-go"><code class="language-go"><span class="token comment">// 有缓冲 channel，有写入值，读</span>
func <span class="token function">TestClose</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span>
	<span class="token keyword">for</span> <span class="token literal-property property">i</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		ch <span class="token operator">&lt;</span><span class="token operator">-</span> i
	<span class="token punctuation">}</span>
	<span class="token function">close</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span>
	<span class="token keyword">for</span> <span class="token literal-property property">i</span> <span class="token operator">:</span><span class="token operator">=</span> range ch <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token comment">// 输出 1 到 10</span>
	<span class="token punctuation">}</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">&lt;</span><span class="token operator">-</span>ch<span class="token punctuation">)</span> <span class="token comment">// 输出 0</span>
<span class="token punctuation">}</span></code></pre></div><div class="notion-spacer"></div><div class="notion-column notion-block-6119b0057a2944299f87954ba3b4936e" style="width:calc((100% - (1 * min(32px, 4vw))) * 0.5)"><pre class="notion-code language-go"><code class="language-go"><span class="token comment">// 有缓冲 channel, 写</span>
func <span class="token function">TestClose</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span>
	<span class="token function">close</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span>
	ch <span class="token operator">&lt;</span><span class="token operator">-</span> <span class="token number">1</span> <span class="token comment">// panic: send on closed channel</span>
<span class="token punctuation">}</span></code></pre><div class="notion-blank notion-block-b35dd4ee17ac44f0bb6ba0943e02bd04"> </div></div><div class="notion-spacer"></div></div><div class="notion-text notion-block-3aedddba0b634916b8e71d052ea588cd">可以看到有缓冲 channel 的行为读取的时候，如果有预留值则读取预留值，没有则读取到零值，写同样会 panic</div><div class="notion-text notion-block-19fc853abf2b49b6974c7a677ab120cc">而我们再次 <code class="notion-inline-code">close</code> 的话，不管有无缓冲，都会提示我们 <code class="notion-inline-code">panic: close of closed channel</code></div><hr class="notion-hr notion-block-19a81de2bb1c491a9fba710295929068"/><div class="notion-text notion-block-add7e5761b164d66818f3d70036f47c4">现在我们可以总结了，从<b>已经关闭的 channel 里面操作</b>：</div><table class="notion-simple-table notion-block-109195cb8a184779a37ceeca2dfb70d4"><tbody><tr class="notion-simple-table-row notion-block-3ad7502180d84b50ba63d24470bff4a2"><td class="" style="width:120px"><div class="notion-simple-table-cell">操作 / channel 类型 </div></td><td class="" style="width:259px"><div class="notion-simple-table-cell">有缓冲 channel</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"> 无缓冲 channel</div></td></tr><tr class="notion-simple-table-row notion-block-7d8c016612644344b6f0ec1d7c697978"><td class="" style="width:120px"><div class="notion-simple-table-cell">读</div></td><td class="" style="width:259px"><div class="notion-simple-table-cell">如果有预留值则返回，否则返回零值</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">返回零值</div></td></tr><tr class="notion-simple-table-row notion-block-9b11f047a9e248febe3275f6fdc1b19f"><td class="" style="width:120px"><div class="notion-simple-table-cell">写</div></td><td class="" style="width:259px"><div class="notion-simple-table-cell">panic: send on closed channel</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">panic: send on closed channel</div></td></tr><tr class="notion-simple-table-row notion-block-d26e341e46664381b3bd7325468d3f67"><td class="" style="width:120px"><div class="notion-simple-table-cell">再次 close</div></td><td class="" style="width:259px"><div class="notion-simple-table-cell">panic: close of closed channel</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">panic: close of closed channel</div></td></tr></tbody></table><div class="notion-text notion-block-71e15345b1614f6ba5efed9edfc95326">如果是 nil 的 channel 呢？</div><div class="notion-text notion-block-d91d5fb32b7d43debb5adb3425e9bd64">我们依次尝试读，写，close，发现都是 panic</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestClose</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">ch</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan int<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>
	ch <span class="token operator">=</span> nil
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">&lt;</span><span class="token operator">-</span>ch<span class="token punctuation">)</span> <span class="token comment">// fatal error: all goroutines are asleep - deadlock!</span>
	ch <span class="token operator">&lt;</span><span class="token operator">-</span> <span class="token number">1</span>           <span class="token comment">// fatal error: all goroutines are asleep - deadlock!</span>
	<span class="token function">close</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span>         <span class="token comment">// panic: close of nil channel</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-a363db3c40f742d69be12ed63b01d3ae">那么我们可以进一步总结，读取<b>已经关闭的 channel </b>操作结果：</div><table class="notion-simple-table notion-block-267dffec23e14e60b740c75de855dcd7"><tbody><tr class="notion-simple-table-row notion-block-cdc41da1b92f43bebd8f20b9f9c17f8f"><td class="" style="width:120px"><div class="notion-simple-table-cell">操作 / channel 类型 </div></td><td class="" style="width:250px"><div class="notion-simple-table-cell">有缓冲 channel</div></td><td class="" style="width:226.5px"><div class="notion-simple-table-cell"> 无缓冲 channel</div></td><td class="" style="width:221.65625px"><div class="notion-simple-table-cell">nil chahnel</div></td></tr><tr class="notion-simple-table-row notion-block-3ecf29a2b824408d8a217fdbdfcb8825"><td class="" style="width:120px"><div class="notion-simple-table-cell">读</div></td><td class="" style="width:250px"><div class="notion-simple-table-cell">如果有预留值则返回，否则返回零值</div></td><td class="" style="width:226.5px"><div class="notion-simple-table-cell">返回零值</div></td><td class="" style="width:221.65625px"><div class="notion-simple-table-cell">死锁</div></td></tr><tr class="notion-simple-table-row notion-block-9b8371880b674042877faf62895a324b"><td class="" style="width:120px"><div class="notion-simple-table-cell">写</div></td><td class="" style="width:250px"><div class="notion-simple-table-cell">panic: send on closed channel</div></td><td class="" style="width:226.5px"><div class="notion-simple-table-cell">panic: send on closed channel</div></td><td class="" style="width:221.65625px"><div class="notion-simple-table-cell">死锁</div></td></tr><tr class="notion-simple-table-row notion-block-e0b652bd4731433a915a5e0372548a83"><td class="" style="width:120px"><div class="notion-simple-table-cell">再次 close</div></td><td class="" style="width:250px"><div class="notion-simple-table-cell">panic: close of closed channel</div></td><td class="" style="width:226.5px"><div class="notion-simple-table-cell">panic: close of closed channel</div></td><td class="" style="width:221.65625px"><div class="notion-simple-table-cell">panic: close of closed channel</div></td></tr></tbody></table><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-622603a1a46c4a239ae1760fb6bd08d0" data-id="622603a1a46c4a239ae1760fb6bd08d0"><span><div id="622603a1a46c4a239ae1760fb6bd08d0" class="notion-header-anchor"></div><a class="notion-hash-link" href="#622603a1a46c4a239ae1760fb6bd08d0" title="源码分析"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">源码分析</span></span></h2><div class="notion-text notion-block-57fb93d7f8f14bfbab446ada92d20d00"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://juejin.cn/post/6844904016254599176#heading-7">深入理解Golang之channel - 掘金 (juejin.cn)</a> 这篇文章分析得非常好，我先学习一下，后面再补充</div><div class="notion-blank notion-block-b633944242254ba8bfa2710ecae7af7f"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-440692534dd5466d976e286b6f96ecb6" data-id="440692534dd5466d976e286b6f96ecb6"><span><div id="440692534dd5466d976e286b6f96ecb6" class="notion-header-anchor"></div><a class="notion-hash-link" href="#440692534dd5466d976e286b6f96ecb6" title="Ref"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Ref</span></span></h2><div class="notion-text notion-block-fe55236221884162af647bf3dac9111d"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.runoob.com/w3cnote/go-channel-intro.html">Go Channel 详解 | 菜鸟教程 (runoob.com)</a></div><div class="notion-text notion-block-6ed978b6d07448abaf6da7890a16d68d"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://juejin.cn/post/6844904016254599176">深入理解Golang之channel - 掘金 (juejin.cn)</a></div><div class="notion-text notion-block-5fc6669f3a6646f5857f5621952e4067"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/channel.html">Channel · Go语言中文文档 (topgoer.com)</a></div><div class="notion-blank notion-block-4e64a4e46e5d47fe9f0cb748b33eec96"> </div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[CS144 - lab 0]]></title>
            <link>https://hhmy27.github.io//cs144-lab0</link>
            <guid>https://hhmy27.github.io//cs144-lab0</guid>
            <pubDate>Fri, 25 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[简单的热身运动]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-be5d776a0cae4ad0b823fc7c1371aca9"><div class="notion-viewport"></div><div class="notion-table-of-contents notion-gray notion-block-97df9679a8d845ad8e0ebb16a292fb70"><a href="#ae1dc021a21345e0bed60baee7865bde" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">简介</span></a><a href="#0c3623c8d57548bf9f9a6796e8249a4f" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">环境</span></a><a href="#d99bc0ac3fe04a4e987a554bdb795a49" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">2 Networking by hand</span></a><a href="#a08b82fedaf742aab6cc0bde9df8fe73" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">2.1 Fetch a Web page</span></a><a href="#e856d35b7afc4748930b42456af20817" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">2.2 Send yourself an email</span></a><a href="#91f05b08390441c49630832ed6090f59" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">2.3 Listening and connecting</span></a><a href="#cd07ecd1bea0458abf3e5d841b0499c3" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">3 Writing a network program using an OS stream socket</span></a><a href="#7c38f02103cd45228e6391d24bac2716" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">3.1 Let’s get started —— fetching and building the starter code</span></a><a href="#f621f1ea80af457cbeabdb3bde02ca76" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">3.2 Modern C++: mostly safe but still fast and low-level</span></a><a href="#8a1ff620a6004d1dbe79f79838152c30" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">3.3 Reading the Sponge documentation</span></a><a href="#70760acfe73247eba35b2f8b4d43317d" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">3.4 Writing webget</span></a><a href="#e33b9c73e52d49129eeda444d5ea0c81" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">4 An in-memory reliable byte stream</span></a><a href="#85d3de1c1fd84f4bb5dc6596c13551b9" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">总结</span></a></div><div class="notion-blank notion-block-fa76a2033eec43d98745333f06cfc67a"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-ae1dc021a21345e0bed60baee7865bde" data-id="ae1dc021a21345e0bed60baee7865bde"><span><div id="ae1dc021a21345e0bed60baee7865bde" class="notion-header-anchor"></div><a class="notion-hash-link" href="#ae1dc021a21345e0bed60baee7865bde" title="简介"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">简介</span></span></h2><div class="notion-text notion-block-ee6f62a0795e47dea3a3fa297d2ec454"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://cs144.github.io/">CS 144: Introduction to Computer Networking</a></div><div class="notion-text notion-block-15d9353a3ad44b3cbbb0ee3b0400ddc3">我跟的是 Fall 2021 的课程</div><div class="notion-text notion-block-9ddc6a4768264a579d5c83766926ece2">本次 lab 对应 week 1，阅读资料都是一些基本的网络概念，这里就不介绍了，感兴趣可以自己翻阅一下</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-49289c9e0a38410c8a849c155a758228"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F8b6c78a1-1a99-4731-96ed-a254a6489f67%2FUntitled.png%3Fid%3D49289c9e-0a38-410c-8a84-9c155a758228%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DdoGmpl1w1udL2dUdWT5aGFO6z65USI2y3yV-zMqrAZk?table=block&amp;id=49289c9e-0a38-410c-8a84-9c155a758228&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-0c3623c8d57548bf9f9a6796e8249a4f" data-id="0c3623c8d57548bf9f9a6796e8249a4f"><span><div id="0c3623c8d57548bf9f9a6796e8249a4f" class="notion-header-anchor"></div><a class="notion-hash-link" href="#0c3623c8d57548bf9f9a6796e8249a4f" title="环境"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">环境</span></span></h2><div class="notion-text notion-block-51e956a1018a4415a16f090962f196b5">我的是 m1pro 的 mbp，用 pd 开了一个虚拟机，然后用 vscode ssh 链接进行 coding</div><div class="notion-text notion-block-f0b6f16f669e43dba740df7594770b4c">首先是安装环境</div><pre class="notion-code language-bash"><code class="language-bash">apt<span class="token operator">-</span><span class="token keyword">get</span> install g<span class="token operator">++</span> git clang<span class="token operator">-</span>tidy clang<span class="token operator">-</span>format libpcap<span class="token operator">-</span>dev \
    iptables mininet tcpdump telnet wireshark socat netcat<span class="token operator">-</span>openbsd \
    doxygen graphviz</code></pre><div class="notion-text notion-block-50931b8196374820a98fa0ac08bc2013">下面就可以愉快的学习了</div><div class="notion-file notion-block-f58b6c3b60664e5a9243cf597cb7488d"><a target="_blank" rel="noopener noreferrer" class="notion-file-link" href="https://file.notion.so/f/s/e9dbe224-eae9-4a79-978b-6887bc2e51c0/lab0.pdf?id=f58b6c3b-6066-4e5a-9243-cf597cb7488d&amp;table=block&amp;spaceId=e7bb21a8-53c5-4141-a2a6-9c3a436ecfe4&amp;expirationTimestamp=1693584000000&amp;signature=Aa5chLCl7GYgBGzKZMNHB6ZDncacl6gQqfKQ6Bh-Opg"><svg class="notion-file-icon" viewBox="0 0 30 30"><path d="M22,8v12c0,3.866-3.134,7-7,7s-7-3.134-7-7V8c0-2.762,2.238-5,5-5s5,2.238,5,5v12c0,1.657-1.343,3-3,3s-3-1.343-3-3V8h-2v12c0,2.762,2.238,5,5,5s5-2.238,5-5V8c0-3.866-3.134-7-7-7S6,4.134,6,8v12c0,4.971,4.029,9,9,9s9-4.029,9-9V8H22z"></path></svg><div class="notion-file-info"><div class="notion-file-title">lab0.pdf</div><div class="notion-file-size">207.5KB</div></div></a></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-d99bc0ac3fe04a4e987a554bdb795a49" data-id="d99bc0ac3fe04a4e987a554bdb795a49"><span><div id="d99bc0ac3fe04a4e987a554bdb795a49" class="notion-header-anchor"></div><a class="notion-hash-link" href="#d99bc0ac3fe04a4e987a554bdb795a49" title="2 Networking by hand"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">2 Networking by hand</span></span></h2><div class="notion-text notion-block-1310c862caed4045854f918f1c43e43f">finish two tasks:</div><ul class="notion-list notion-list-disc notion-block-dcc1e690b2f04fb3b0644ebf46888561"><li>retrieving a web page</li></ul><ul class="notion-list notion-list-disc notion-block-79136bb9f92b4920878800be0dcf6f5c"><li>sending an email message</li></ul><div class="notion-text notion-block-9016fd5bf98348348951e6d034de9d56">这些任务依赖于一个叫做 <em>reliable bidirectional byte stream </em>的网络抽象</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-a08b82fedaf742aab6cc0bde9df8fe73" data-id="a08b82fedaf742aab6cc0bde9df8fe73"><span><div id="a08b82fedaf742aab6cc0bde9df8fe73" class="notion-header-anchor"></div><a class="notion-hash-link" href="#a08b82fedaf742aab6cc0bde9df8fe73" title="2.1 Fetch a Web page"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">2.1 Fetch a Web page</span></span></h3><div class="notion-text notion-block-06a354682f06436599579c1bf828f9c7">两个操作：</div><ul class="notion-list notion-list-disc notion-block-d76fbcead99145f0ade4650a5a92135e"><li>在浏览器中访问 <a target="_blank" rel="noopener noreferrer" class="notion-link" href="http://cs144.keithw.org/hello">http://cs144.keithw.org/hello</a>，并观察结果</li></ul><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-636ad1ade8dd4bd18f519f36f249bfee"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:432px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F9c87e26d-167d-4b77-8b8b-7ad03b6a1601%2FUntitled.png%3Fid%3D636ad1ad-e8dd-4bd1-8f51-9f36f249bfee%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DhZXGmcy08I4c593B8z66Qve1CoA7t-b1G45jtJ978hg?table=block&amp;id=636ad1ad-e8dd-4bd1-8f51-9f36f249bfee&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><ul class="notion-list notion-list-disc notion-block-81a9ab5873594cfdac036372eac50fa7"><li>输入<code class="notion-inline-code">$ telnet </code><code class="notion-inline-code"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="http://cs144.keithw.org">cs144.keithw.org</a></code><code class="notion-inline-code"> http</code></li></ul><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-0ff582c4bb0448d09c981485f1ee4fc1"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F55a7034b-6ac6-4e82-afe2-40d734305f2e%2FUntitled.png%3Fid%3D0ff582c4-bb04-48d0-9c98-1485f1ee4fc1%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DjO_SH2mHZJjeNrynHgTwFb-fmZQLjCISrYuX0-Csuv8?table=block&amp;id=0ff582c4-bb04-48d0-9c98-1485f1ee4fc1&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-d7bc2b80b5294bc9b407e0f163540389">等待链接建立后，输入以下命令并观察结果</div><pre class="notion-code language-bash"><code class="language-bash"><span class="token constant">GET</span> <span class="token operator">/</span>hello <span class="token constant">HTTP</span><span class="token operator">/</span><span class="token number">1.1</span>
<span class="token literal-property property">Host</span><span class="token operator">:</span> cs144<span class="token punctuation">.</span>keithw<span class="token punctuation">.</span>org
<span class="token literal-property property">Connection</span><span class="token operator">:</span> close
</code></pre><div class="notion-text notion-block-8ca02ecda1984513b0d1c6cc27f6b459">我们请求命令的含义分别是：</div><ul class="notion-list notion-list-disc notion-block-56547ab0758243ceb7bb73ae78ae7edc"><li><code class="notion-inline-code">GET /hello HTTP/1.1</code>，告诉 server 我们想要访问的 URL 路径，这里是 <code class="notion-inline-code">/hello</code></li></ul><div class="notion-text notion-block-1e5e2e64145b457d8fb22f708f59fde1">也就是我们的请求行</div><ul class="notion-list notion-list-disc notion-block-3cec229df1954cfdae4b12deffdd2f96"><li><code class="notion-inline-code">Host: cs144.keithw.org</code>，告诉 server 我们想要访问的 host，指的是 <code class="notion-inline-code">http://</code> 到 <code class="notion-inline-code">/hello</code> 中间的数据</li></ul><div class="notion-text notion-block-f4529d71e6e741dead0b82ed29d47a58">完整的路径是 <code class="notion-inline-code">http://cs144.keithw.org/hello</code></div><ul class="notion-list notion-list-disc notion-block-f2c66fe1763b4934a72c614819b79d43"><li><code class="notion-inline-code">Connection: close</code>，告诉 server，一旦返回结果就可以立即关闭</li></ul><div class="notion-text notion-block-ba415729a4574ca19d8087dc4d676c55">像这种 <code class="notion-inline-code">K:V</code> 型的数据就是请求头啦</div><div class="notion-blank notion-block-05f2654b882b439295d72ffac9cd93a6"> </div><div class="notion-text notion-block-962e8074b60146dbb5a5272686cd796e">输入后得到结果，cool</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-77361b9fef5144b0a0d2a28c51b340cb"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F2213ade9-cfd1-410d-9e9e-7449fce11e43%2FUntitled.png%3Fid%3D77361b9f-ef51-44b0-a0d2-a28c51b340cb%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DJzKWF-ZsdAswasRfqogHNEJsh1665KGFcsDm02MtPSQ?table=block&amp;id=77361b9f-ef51-44b0-a0d2-a28c51b340cb&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-16e6905c6657481f88b4ac292130e712">这里有一个任务</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-4a8b71bb9b4b4aa69593ef5e0476f89b"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fb7c62a66-b7b5-4df2-80d6-a6314c8f16ce%2FUntitled.png%3Fid%3D4a8b71bb-9b4b-4aa6-9593-ef5e0476f89b%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DFsYWCTIIo7Y9e73LESLEGjEv0ji9pdVQckjaDq4nuuE?table=block&amp;id=4a8b71bb-9b4b-4aa6-9593-ef5e0476f89b&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-923fe8c7526748b0a670459b096e9daf">就是按照上面的流程走一遍，我不知道SUNet ID在哪里获取，我自己随便打了一个</div><pre class="notion-code language-bash"><code class="language-bash"><span class="token constant">GET</span> <span class="token operator">/</span>lab0<span class="token operator">/</span><span class="token number">1808</span> <span class="token constant">HTTP</span><span class="token operator">/</span><span class="token number">1.1</span>
<span class="token literal-property property">Host</span><span class="token operator">:</span> cs144<span class="token punctuation">.</span>keithw<span class="token punctuation">.</span>org
<span class="token literal-property property">Connection</span><span class="token operator">:</span> close
</code></pre><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-ffcc41e630e547ac9d29c107eab42cfc"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fababc708-3b84-4040-af2e-3adc7d474ae6%2FUntitled.png%3Fid%3Dffcc41e6-30e5-47ac-9d29-c107eab42cfc%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DZ_3pAoWA39u_Yqhj1wFvPxn0_1LG5iyDUjgxe5xT42c?table=block&amp;id=ffcc41e6-30e5-47ac-9d29-c107eab42cfc&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-28e9854180044e97bd7ce368dbc47153">注意看header里面的字段，确实有一个997480的值返回</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-e856d35b7afc4748930b42456af20817" data-id="e856d35b7afc4748930b42456af20817"><span><div id="e856d35b7afc4748930b42456af20817" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e856d35b7afc4748930b42456af20817" title="2.2 Send yourself an email"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">2.2 Send yourself an email</span></span></h3><div class="notion-text notion-block-bdc0d9bb58f543688ff0b0a14413531b">用 smtp（Simple Mail Transfer Protocol）协议给自己发一封邮件 </div><div class="notion-text notion-block-f5019c6113eb45e0bee4f1c5e350e41e">这个环节需要用 standford 的网络环境才能完成，跳过了</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-91f05b08390441c49630832ed6090f59" data-id="91f05b08390441c49630832ed6090f59"><span><div id="91f05b08390441c49630832ed6090f59" class="notion-header-anchor"></div><a class="notion-hash-link" href="#91f05b08390441c49630832ed6090f59" title="2.3 Listening and connecting"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">2.3 Listening and connecting</span></span></h3><div class="notion-text notion-block-81bf4bf3f3044abb834d5e40cf8cefb1">下面用 <code class="notion-inline-code">netcat</code> 和 <code class="notion-inline-code">telnet</code> 来做一个双向通信</div><ol start="1" class="notion-list notion-list-numbered notion-block-26ed021c3e7e45bf8c801f410bd39c52"><li>用 <code class="notion-inline-code">netcat -v -l -p 9090</code> 来监听 9090 端口</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-d6278978a8924cc4a79784b0ce15d868"><li>在另一个 terminal，用 <code class="notion-inline-code">telnet localhost 9090</code> 来链接 9090 端口</li></ol><div class="notion-text notion-block-8aaa2a64dd514475ac23df2f75cff980">然后你会发现它们可以双向通信了</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-b42d4051f93846c6ace19ef2016a5da2"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Ffdac4559-5c91-47fa-a8a8-157514d30fea%2FUntitled.png%3Fid%3Db42d4051-f938-46c6-ace1-9ef2016a5da2%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DT5GPJrRyWNv_4jRw4cSUmXMdB4ctVWpQeZTn37E9FqA?table=block&amp;id=b42d4051-f938-46c6-ace1-9ef2016a5da2&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-a0ef389869fe432eaf91357c60a5f206"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-cd07ecd1bea0458abf3e5d841b0499c3" data-id="cd07ecd1bea0458abf3e5d841b0499c3"><span><div id="cd07ecd1bea0458abf3e5d841b0499c3" class="notion-header-anchor"></div><a class="notion-hash-link" href="#cd07ecd1bea0458abf3e5d841b0499c3" title="3 Writing a network program using an OS stream socket"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">3 Writing a network program using an OS stream socket</span></span></h2><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-7c38f02103cd45228e6391d24bac2716" data-id="7c38f02103cd45228e6391d24bac2716"><span><div id="7c38f02103cd45228e6391d24bac2716" class="notion-header-anchor"></div><a class="notion-hash-link" href="#7c38f02103cd45228e6391d24bac2716" title="3.1 Let’s get started —— fetching and building the starter code"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">3.1 Let’s get started —— fetching and building the starter code</span></span></h3><div class="notion-text notion-block-31506cfefd3b4ab89dd541caf5d1c982">运行 <code class="notion-inline-code">git clone </code><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://github.com/cs144/sponge"><code class="notion-inline-code">https://github.com/cs144/sponge</code></a> 下载lab的源代码</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-664242ffb495431088307b8ae0746ae4"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F0c370b02-98b7-4674-bfe9-6efb4a3a4d5c%2FUntitled.png%3Fid%3D664242ff-b495-4310-8830-7b8ae0746ae4%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DZ9F7Mx9vBaYDXVRPDr_NSZd2JsWV0e074TJ493medtg?table=block&amp;id=664242ff-b495-4310-8830-7b8ae0746ae4&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-f0739b2671b14e1a923cdb6098576467">文档里面的准备工作写得很详细，我自己没有遇到问题，可以直接进入3.2了</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-f621f1ea80af457cbeabdb3bde02ca76" data-id="f621f1ea80af457cbeabdb3bde02ca76"><span><div id="f621f1ea80af457cbeabdb3bde02ca76" class="notion-header-anchor"></div><a class="notion-hash-link" href="#f621f1ea80af457cbeabdb3bde02ca76" title="3.2 Modern C++: mostly safe but still fast and low-level"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">3.2 Modern C++: mostly safe but still fast and low-level</span></span></h3><div class="notion-text notion-block-0c915438078f43689ac813f38d290438">又到了喜闻乐见的 C++ 环节，课程用的是 C++ 11，课程提出了一些要求：</div><ul class="notion-list notion-list-disc notion-block-ceca1dac81104f5096ae501aa4431654"><li>尽量避免使用成对操作，比如说 malloc/free, new/delete，因为后面的操作可能会因为异常或者提前退出而不能正确执行</li></ul><ul class="notion-list notion-list-disc notion-block-4fbef21576344427ba1b2e550b3d147d"><li>Resource acquisition is initializatio（RAII）风格</li><ul class="notion-list notion-list-disc notion-block-4fbef21576344427ba1b2e550b3d147d"><div class="notion-text notion-block-f0412b1e46fa408fbe8984f78213933c"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://zhuanlan.zhihu.com/p/34660259">c++经验之谈一：RAII原理介绍 - 知乎 (zhihu.com)</a></div><div class="notion-text notion-block-d53f77d893d4437a8444e26cfca40638">具体分为四步</div><ol start="1" class="notion-list notion-list-numbered notion-block-c2a9848d60a549cf8fe29a3e7b778b59"><li>设计一个类封装资源</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-4ffb2a082ee94a4fb12f63ac30731bb2"><li>在构造函数中初始化</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-5d5866b92fab499fb7a2b2ae43823b67"><li>在析构函数中执行销毁操作</li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-8e87c3ccbd4d423ba7d85f0efeb52a4b"><li>使用的时候声明一个该对象的类（而不要使用new）</li></ol></ul></ul><div class="notion-text notion-block-838453be4bbc4308a80f43386d6afb1e">下面还有一些规范，这里就不翻译了：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-26fe59c29c6c4f63ba6d8721acd36af8"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F38e0de57-e4f9-4a66-b6cd-58439a902a83%2FUntitled.png%3Fid%3D26fe59c2-9c6c-4f63-ba6d-8721acd36af8%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D1IZXs_dFUHUCKK2Y5j0WdzpEKkOFnwhwRrUwFE6joaY?table=block&amp;id=26fe59c2-9c6c-4f63-ba6d-8721acd36af8&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-8a1ff620a6004d1dbe79f79838152c30" data-id="8a1ff620a6004d1dbe79f79838152c30"><span><div id="8a1ff620a6004d1dbe79f79838152c30" class="notion-header-anchor"></div><a class="notion-hash-link" href="#8a1ff620a6004d1dbe79f79838152c30" title="3.3 Reading the Sponge documentation"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">3.3 Reading the Sponge documentation</span></span></h3><div class="notion-text notion-block-4297f6b33e4f4c999f2d473f718f9ae6">ok，然后这边给了几个文档读</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-dd96c8d9e9084da5a97057b6c6dba4ed"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Ff7f11050-eacc-4ab2-9b7d-47f3d44febd0%2FUntitled.png%3Fid%3Ddd96c8d9-e908-4da5-a970-57b6c6dba4ed%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D8uC52vBe2qm0yi1vFSSXtYX5TejQz56a6CLRcari_E4?table=block&amp;id=dd96c8d9-e908-4da5-a970-57b6c6dba4ed&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-18f65db8cba9486dbd641d3d09f43ab8">这里给了官方文档说明，主要是四个类要我们阅读</div><ul class="notion-list notion-list-disc notion-block-6aef36f121dd4c72b87e8b7da60b49dd"><li>FileDescriptor ⇒ Socket ⇒ TCPSocket</li></ul><ul class="notion-list notion-list-disc notion-block-6e2296ea18ad4c199e78b9eac15ed670"><li>Address 是 Socket 需要绑定的地址类</li></ul><ul class="notion-list notion-list-disc notion-block-43a92273bcdb49f89b0fa099fc1c040b"><li>然后看这个 libsponge/util 里面有源码</li></ul><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-70760acfe73247eba35b2f8b4d43317d" data-id="70760acfe73247eba35b2f8b4d43317d"><span><div id="70760acfe73247eba35b2f8b4d43317d" class="notion-header-anchor"></div><a class="notion-hash-link" href="#70760acfe73247eba35b2f8b4d43317d" title="3.4 Writing webget"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">3.4 Writing webget</span></span></h3><div class="notion-text notion-block-d64fe132a5df47ada8911b45e671b8f4">接下来就可以愉快的写代码了，第一个任务呢只给了我们一个函数，要求我们补全它的功能。</div><div class="notion-text notion-block-4cac6aa5dfdb4060942cf840f1485364">注释写得很详细，同时这里文档也给出了一些提示</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-3d75892e23424de4b57d2cbcd750c206"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fa8aa3674-c024-49c0-98ef-1471494dfe53%2FUntitled.png%3Fid%3D3d75892e-2342-4de4-b57d-2cbcd750c206%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DGhEQCCboENizFqCTHRKq1hxIYLCTclddf4fr1kIElxc?table=block&amp;id=3d75892e-2342-4de4-b57d-2cbcd750c206&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-cdc5a19991824b3f9555e8943bfd37b1">怎么做的我们先看注释</div><pre class="notion-code language-c++"><code class="language-c++"><span class="token keyword">void</span> <span class="token function">get_URL</span><span class="token punctuation">(</span><span class="token parameter"><span class="token keyword">const</span> string <span class="token operator">&amp;</span>host<span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&amp;</span>path</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// Your code here.</span>

    <span class="token comment">// You will need to connect to the "http" service on</span>
    <span class="token comment">// the computer whose name is in the "host" string,</span>
    <span class="token comment">// then request the URL path given in the "path" string.</span>
    
    <span class="token comment">// Then you'll need to print out everything the server sends back,</span>
    <span class="token comment">// (not just one call to read() -- everything) until you reach</span>
    <span class="token comment">// the "eof" (end of file).</span>
    
    <span class="token comment">// cerr &lt;&lt; "Function called: get_URL(" &lt;&lt; host &lt;&lt; ", " &lt;&lt; path &lt;&lt; ").\n";</span>
    <span class="token comment">// cerr &lt;&lt; "Warning: get_URL() has not been implemented yet.\n";</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-89749aada39f42cb8bd243160dd1e55f">首先注释要求我们去链接 <code class="notion-inline-code">http</code> service，那么自然我们需要绑定<code class="notion-inline-code">Address</code>，看一下它里面的构造函数。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-4e3efcca7d654c17aec8c97ea919a58e"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:336px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Faa9c1998-a9ff-4a0f-840b-ef0b18f589ed%2FUntitled.png%3Fid%3D4e3efcca-7d65-4c17-aec8-c97ea919a58e%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DMsLSm_b38LPE-rc65F6swHeMVp58fsce3zPRHis_qMM?table=block&amp;id=4e3efcca-7d65-4c17-aec8-c97ea919a58e&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-483160f12b394588b7bcd1fe41b21eb6">这个看起来符合我们的要求，这里也给了提示，<code class="notion-inline-code">http</code>是80端口，另外</div><blockquote class="notion-quote notion-block-0da8719b53584cda90711836c7b8d0ac"><code class="notion-inline-code">/etc/services</code> 文件包含网络服务和它们映射端口的列表。详细介绍可以看：
<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://zh.wikipedia.org/zh-tw/Services_(%E7%BD%91%E7%BB%9C%E6%9C%8D%E5%8A%A1%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6)">Services (網路服務設定檔) - 維基百科，自由的百科全書 (wikipedia.org)</a>
<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://linux.cn/article-10460-1.html">技术|理解 Linux 中的 /etc/services 文件</a></blockquote><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-b4333e4c17a94dd286fa7f455a457417"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F3f7a7983-c6ed-4146-ae50-cb09050f67f9%2FUntitled.png%3Fid%3Db4333e4c-17a9-4dd2-86fa-7f455a457417%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DhME645qAMKt1puxpqabvSwRtdoh10e_MeGJGMBMFfRk?table=block&amp;id=b4333e4c-17a9-4dd2-86fa-7f455a457417&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-481b0c73df894830abdbe80b8fdafe26">我们看看里面的说明，果然http是绑定到了80端口，而https绑定了443接口，根据协议还分为基于tcp的https和基于udp的https（也就是HTTP/3）</div><div class="notion-text notion-block-6b4d6d4790dd40078c2e4a5a805609a0">那么我们简单地使用<code class="notion-inline-code">Address addr(host, &quot;http&quot;);</code> 获取一个地址，回头继续看文档，还提示了我们用TCPSocket，经过一番摸索之后终于理解了这个get_URL的含义：</div><ul class="notion-list notion-list-disc notion-block-f21c07b5bf2248b199b6fa5f43fdd824"><li>用TCPSocket链接指定的host，然后写入相应的请求，并打印返回值</li></ul><div class="notion-text notion-block-8f9569829031486e88bb5a3ad9cc154d">通过测试了：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-97d27156437a4b5c9bcdd48861ac4430"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fa24d4858-8685-486f-8d6c-fdefdecc441e%2FUntitled.png%3Fid%3D97d27156-437a-4b5c-9bcd-d48861ac4430%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DuCK5a9AgzjEnvO1kA5QDCxsc8XtM81BWRmbXHQggofM?table=block&amp;id=97d27156-437a-4b5c-9bcd-d48861ac4430&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-92b6fefa48ee4c6f9945b2e374e1ccb0">内容其实就是开始的时候，让我们手写HTTP请求获取结果一样，这里写了一个GET的请求，还有请求头说明host和connection的方式</div><div class="notion-text notion-block-ade1e1e5d37642e5b4dae4b2a57205c3">比如我们用写好的函数，测试<code class="notion-inline-code"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="http://cs144.keithw.org/">cs144.keithw.org</a></code><code class="notion-inline-code"> /hello</code> ，也能够得到正确的结果</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-44f0e0b1310d4fbfa9a9adbc97275362"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fb2d8f2cd-b6a1-4483-94f3-ff307329c2d5%2FUntitled.png%3Fid%3D44f0e0b1-310d-4fbf-a9a9-adbc97275362%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DJH-HrHC4j5ApHdIc901couj9eCWLCtHo7-rnTmlAg08?table=block&amp;id=44f0e0b1-310d-4fbf-a9a9-adbc97275362&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-e33b9c73e52d49129eeda444d5ea0c81" data-id="e33b9c73e52d49129eeda444d5ea0c81"><span><div id="e33b9c73e52d49129eeda444d5ea0c81" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e33b9c73e52d49129eeda444d5ea0c81" title="4 An in-memory reliable byte stream"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">4 An in-memory reliable byte stream</span></span></h2><div class="notion-text notion-block-ea574969866e4585bd5f95ab885e8c07">接下来要我们实现一个<b>An in-memory reliable byte stream</b>，特点：</div><ul class="notion-list notion-list-disc notion-block-c7cdbc11f5db40cca55990b34404b25c"><li>长度限制writer写入的数量，但是并不限制整个传输的信号量。</li></ul><ul class="notion-list notion-list-disc notion-block-851c5764dec4444f9b574094785494ea"><li>当writer写入到limit的时候就不能再写了，只有reader取出数据才能继续写，就像生产者和消费者模型一样。</li></ul><div class="notion-text notion-block-c8e93b67b57e4b94a6c4636faaccad46">writer的接口：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-31856b532e4e4b2cb7f7b0823febc7bb"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fbffc4db6-2955-4816-ac89-b05abfe0571f%2FUntitled.png%3Fid%3D31856b53-2e4e-4b2c-b7f7-b0823febc7bb%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D3PG_FvLSX96hK-U5zBHeYGJ_PenKw0zRy-WiBOUyZ6Y?table=block&amp;id=31856b53-2e4e-4b2c-b7f7-b0823febc7bb&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-4da3d432f9514a6fa4563ea32a754f10">reader的接口：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-2d78f41f506f42ff86edfd61027f29ba"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fbc3608bb-8082-442e-bb67-a97aa244fc2a%2FUntitled.png%3Fid%3D2d78f41f-506f-42ff-86ed-fd61027f29ba%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DntdeLBtqk-KPlw25Tfgo17Wm8rQvNlOwYk6AV-2Wyqc?table=block&amp;id=2d78f41f-506f-42ff-86ed-fd61027f29ba&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-c4c1bcbacc894732b9a85531ff838dc6">实现的地方在<code class="notion-inline-code">libsponge/byte stream.hh</code> and <code class="notion-inline-code">libsponge/byte_stream.cc</code></div><div class="notion-text notion-block-fcc61d5150e3402089a6da9bd4ff9791">想了一下，这个需求双端队列应该很好写，write往队首写，reader向队尾取，队列的size提前给定了</div><div class="notion-text notion-block-373f6115986347d28a133b6679f13361">注意到文档没有给出边界情况怎么处理：</div><ul class="notion-list notion-list-disc notion-block-98f5426df9ce4c8db02457a97f5ba80b"><li>reader想要读取的字符长度超过了队列的大小，应该怎么处理，直接报错还是直接读取尽可能大的字符？</li></ul><ul class="notion-list notion-list-disc notion-block-f5c9e98ea4584933954cdce740dbd61e"><li>我写入的字符串大于队列大小，应该丢弃多余的字符，还是保存下来再写入？</li></ul><div class="notion-text notion-block-e68d044b0789463bb6c42af65e6514f8">文档开头也说了，本次课程的实验部分的需求不会写得很详细，需要我们自己摸索然后设计一个解决方案，这是因为以后工作的时候遇到很多需求都是很模糊的，这么做是为了提前训练学生的设计能力。</div><div class="notion-text notion-block-e5a1858f86514a289406918ac6100276">想了一下，这是一个经典的生产者消费者问题，用双端队列能够很好的满足我们的需求</div><ul class="notion-list notion-list-disc notion-block-bd567afd34e54cfdb3c5e0eda8074752"><li>writer在队头写，reader取队尾</li></ul><ul class="notion-list notion-list-disc notion-block-a680270c019f4d83a2d7681f95e33011"><li>限制最大存储容量cap，通过队列元素个数和cap计算得到剩余可以写入的元素个数</li></ul><div class="notion-text notion-block-29b638d5ba234cdfa28d6bdbad196ca0">除此之外有几个要注意的地方</div><ul class="notion-list notion-list-disc notion-block-606b89f2a9b144c697e1f166e2feadcd"><li>eof 表示 reader 是否读取结束，状态由队列当前元素个数和 writer 是否写入结束共同决定</li></ul><ul class="notion-list notion-list-disc notion-block-697ee25a8ab040ffb8c17eb3af2d202e"><li>pop_output 同样也要计算读取个数</li></ul><div class="notion-text notion-block-ae77b2e01b8d42b6b3cc32a803fbc593">搞清楚之后实现还是很简短的，跑了一下，都通过了</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-f8b3c50c7a58455b9431c7cf45eabe09"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F990a66f2-bcd6-449b-bbe6-581ebdaf9cbb%2FUntitled.png%3Fid%3Df8b3c50c-7a58-455b-9431-c7cf45eabe09%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DIBP2MBu_-P6kYuFWuO9n_rZ_3HyQX7_VPuawbH3wKvQ?table=block&amp;id=f8b3c50c-7a58-455b-9431-c7cf45eabe09&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-3487073b81334c139a0a6e1d36c936a9"><b>debug技巧</b></div><div class="notion-text notion-block-528326ead71f4aa082f9ae3ec42ff7c7">测试文件位于tests文件夹中，点进去能直接看到测试用例和预期结果，每个测试用例还会提示你完成了多少命令，在哪一个命令出错了，通过这种方式可能节省很多debug的时间。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-3dcdb3d1c15f48e1b6e699662c88e7d1"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:240px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fdcb4ff32-20a2-46a6-845c-4eccb438c30c%2FUntitled.png%3Fid%3D3dcdb3d1-c15f-48e1-b6e6-99662c88e7d1%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DobBiygko0PouYyxXSLoIUuAou5mwQWpvueyzatkkNCI?table=block&amp;id=3dcdb3d1-c15f-48e1-b6e6-99662c88e7d1&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-85d3de1c1fd84f4bb5dc6596c13551b9" data-id="85d3de1c1fd84f4bb5dc6596c13551b9"><span><div id="85d3de1c1fd84f4bb5dc6596c13551b9" class="notion-header-anchor"></div><a class="notion-hash-link" href="#85d3de1c1fd84f4bb5dc6596c13551b9" title="总结"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">总结</span></span></h2><div class="notion-text notion-block-6bd26e07b0d94c388a3f4956271a19fb">lab0 整体上是一个热身运动</div><ul class="notion-list notion-list-disc notion-block-b39c16754a67444caf2e94a9a50514ad"><li>通过 linux 自带的指令体验了一下 http 请求</li></ul><ul class="notion-list notion-list-disc notion-block-7c002475081340cbbab685718ace4535"><li>要求我们自己实现一个简单的 get_URL 函数去发送请求并获取结果，涉及到 Socket 和网络通信的基本知识</li></ul><ul class="notion-list notion-list-disc notion-block-f564d510ce3349148f183ec6b4413737"><li>最后的 An in-memory reliable byte stream 要求我们实现了一个生产者消费者模型，还是比较有意思的</li></ul><div class="notion-blank notion-block-82530730a1ee4f759803df91abb5a566"> </div><div class="notion-blank notion-block-6139e1700ab5438e829adf7354ef54dd"> </div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[TCP的重传机制、滑动窗口、流量控制、拥塞控制]]></title>
            <link>https://hhmy27.github.io//tcp</link>
            <guid>https://hhmy27.github.io//tcp</guid>
            <pubDate>Tue, 22 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[针对不稳定的网络环境提出的补偿机制]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-951d4753cc764e109427bc1517d34a8c"><div class="notion-viewport"></div><div class="notion-table-of-contents notion-gray notion-block-e0e84d9a375c4f659a81f2dc28ad6f1f"><a href="#b183366868dd422697e0614ce1b707ff" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">简介</span></a><a href="#f64b28fe5e2345c09fc4f2390363bbae" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">重传机制</span></a><a href="#9345229724ff48f1b52e608964263aa9" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">超时重传</span></a><a href="#a41ce7b2122a440ea4491859e5d5268b" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">快速重传</span></a><a href="#6f66d4dd768e432d83a6a1809e58a65b" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">SACK方法</span></a><a href="#5e309e36bb5a4b6ca01dfb34d0ecdd1a" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">DSACK方法</span></a><a href="#f3eac9c1d0cb4d34a82ddd4c665a3119" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">滑动窗口</span></a><a href="#f394c3d0528c4e30836db1cf1aade706" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">流量控制</span></a><a href="#57969268dcf540f9bb7da5f89e2111aa" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">拥塞控制</span></a><a href="#b48ae3199de640a8b2c8a3334bc577e2" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">什么是拥塞窗口</span></a><a href="#78bcaa7caf5847078769d9f323a7391f" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">慢启动</span></a><a href="#b37f6e42076f4322b1a2e3272bcca9a3" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">拥塞避免</span></a><a href="#f7415be02917493f9b9cfd0443e8e41c" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">拥塞发生算法</span></a><a href="#97028e27aa3d4ba9b41099c57cfa23a1" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">快速恢复</span></a><a href="#874fef272d30445fbc0e722e59292e20" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">Ref</span></a></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-b183366868dd422697e0614ce1b707ff" data-id="b183366868dd422697e0614ce1b707ff"><span><div id="b183366868dd422697e0614ce1b707ff" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b183366868dd422697e0614ce1b707ff" title="简介"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">简介</span></span></h2><blockquote class="notion-quote notion-block-34afd03e7abf44db8e2a7b47d01ccf61">本文是阅读<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://xiaolincoding.com/network/3_tcp/tcp_feature.html#%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3">4.2 TCP 重传、滑动窗口、流量控制、拥塞控制 | 小林coding (xiaolincoding.com)</a>的笔记，想要了解更多细节请看原文</blockquote><div class="notion-text notion-block-749bcda685c84055bc9fc189084fcafd">先说结论</div><ul class="notion-list notion-list-disc notion-block-a0928fb281c04069ad8e330963bb5c90"><li>流量控制：针对发送方和接收方速度不匹配的问题，限制发送方的速度，是端对端的场景</li></ul><ul class="notion-list notion-list-disc notion-block-59300179550746edac988d8e8e5643b1"><li>拥塞控制：根据整个网络环境来调整发送速度，避免引起网络拥塞导致传输质量下降，是针对整个网络环境而言的</li></ul><div class="notion-text notion-block-7bb65b7add1f4a859a4dda84d322394c">为了更详细的介绍这两种控制机制，我们先引入重传和滑动窗口这两个概念。</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-f64b28fe5e2345c09fc4f2390363bbae" data-id="f64b28fe5e2345c09fc4f2390363bbae"><span><div id="f64b28fe5e2345c09fc4f2390363bbae" class="notion-header-anchor"></div><a class="notion-hash-link" href="#f64b28fe5e2345c09fc4f2390363bbae" title="重传机制"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">重传机制</span></span></h2><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-507902508909484db483f7c6ba54892a"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F71a8f7c0-1b8d-4e77-88db-56364a4037df%2FUntitled.png%3Fid%3D50790250-8909-484d-b483-f7c6ba54892a%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DDfBrKYXx6sfwmeEJbWDUEnwsBT0aS0ka82WVepfZxRM?table=block&amp;id=50790250-8909-484d-b483-f7c6ba54892a&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><blockquote class="notion-quote notion-block-2ef7aadcb74846fab90ae664cf97380a">针对网络环境不稳定，可能会出现丢包情况设计的补偿机制。一共有四种：超时重传，快速重传，SACK方法，D-SACK方法</blockquote><div class="notion-text notion-block-27019a6a2c5845b9ba8251201338164a">首先说我们为什么需要重传？理想状态下的网络环境不会丢失，发送方发送一个，接收方就返回一个ACK确认消息。但是实际情况下，发送方的消息和接收方的ACK都有可能会丢失，如果不做重传补偿的话，丢失的数据是无法正常交付的。</div><div class="notion-text notion-block-3f79b2c488c040faa491b782d7992094">针对这种情况，TCP设计了重传机制，一共有四种重传机制：</div><ul class="notion-list notion-list-disc notion-block-23ee905e7e624a5a98bc64a5a465159f"><li>超时重传：设计一个时间间隔</li></ul><ul class="notion-list notion-list-disc notion-block-126b78b6ddf0425fa4c48821dd92aed0"><li>快速重传：重复3次ACK就重传</li></ul><ul class="notion-list notion-list-disc notion-block-f9ec8086ad7b4f3e8e605ead9587280e"><li>SACK 方法：通过额外信息告知发送方如何重传</li></ul><ul class="notion-list notion-list-disc notion-block-671b97f5a0bb4b3e926bd75360df41d8"><li>Duplicate SACK：告知发送方丢失包的原因</li></ul><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-9345229724ff48f1b52e608964263aa9" data-id="9345229724ff48f1b52e608964263aa9"><span><div id="9345229724ff48f1b52e608964263aa9" class="notion-header-anchor"></div><a class="notion-hash-link" href="#9345229724ff48f1b52e608964263aa9" title="超时重传"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">超时重传</span></span></h3><div class="notion-text notion-block-0a34a8722be74483b704273658a90aa0">核心思想就是设置一个定时器，如果一定时间内发送方没有接收到ACK，那么就视为数据包丢失，需要进行重传。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-ca9aca596f144673ad10259cf45401f6"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F40b0315e-7141-40c1-b83a-744687b9ccac%2FUntitled.png%3Fid%3Dca9aca59-6f14-4673-ad10-259cf45401f6%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DaZfwYBge770huDGYr_b2yCOEI1ymYNcQ5JuxFKDTiZc?table=block&amp;id=ca9aca59-6f14-4673-ad10-259cf45401f6&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-3cc5d76c3f32456ea60ce7b2011c5a0e">那么现在问题的难点在于怎么定义这个超时时间：</div><ul class="notion-list notion-list-disc notion-block-cc6744d2056a47e68b3c8d19eb1e2253"><li>如果RT0过小的时候，可能会频繁传输已经接受到的数据</li></ul><ul class="notion-list notion-list-disc notion-block-cd818c71fff24395b0aa4bb73d0c01f3"><li>如果RT0过大，接收方会迟迟收不到丢失的数据包</li></ul><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-36d549c84df24a2195e005c9e4c46b40"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fb83a4c93-86f5-4234-9fd9-5e51309df2d1%2FUntitled.png%3Fid%3D36d549c8-4df2-4a21-95e0-05c9e4c46b40%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D1wCEZR-zlt6NYX9YzWz0yBbcsIS5pO9HWkjhw44S5Bk?table=block&amp;id=36d549c8-4df2-4a21-95e0-05c9e4c46b40&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-1ed901bd3d694285b134546c99696ade">为此TCP引入了<code class="notion-inline-code">RTT( Round-Trip Time 往返时延)</code>的概念，RTT指的是数据发送时刻到收到确认时刻的差值，也就是接收ACK时间-发送时间。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-dec043dd8cac438d9436ba6396ae4253"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F37fa9b0d-bbf9-4a5d-80a2-068d2a5a7b80%2FUntitled.png%3Fid%3Ddec043dd-8cac-438d-9436-ba6396ae4253%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DjvbykZwScQGWVo5YmI0GUgVXpAbG7AjD26rgmtFbRuE?table=block&amp;id=dec043dd-8cac-438d-9436-ba6396ae4253&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-84a0dcb069204aa993d05957a8f59314">超时重传时间是<code class="notion-inline-code">RT0（Retransmission Timeout）</code> ，我们应该把RT0设置为略大于RTT。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-c38562ec6ce74420b7367999b462098b"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:617px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F641888fd-331c-4ce2-8b9e-d73bc35e5d14%2FUntitled.png%3Fid%3Dc38562ec-6ce7-4420-b736-7999b462098b%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DuZJPxvYHoKtRAGKaf1gkK9tWekTkC57nQ4TpKECp8hI?table=block&amp;id=c38562ec-6ce7-4420-b736-7999b462098b&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-d0fc40221dd7404596c03e5d6c328ff1">网络环境是不断变化的，RTT也是动态的，RTT的计算方法有很多，可以看这篇文章的介绍：<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://xiaolincoding.com/network/3_tcp/tcp_feature.html#%E8%B6%85%E6%97%B6%E9%87%8D%E4%BC%A0">4.2 TCP 重传、滑动窗口、流量控制、拥塞控制 | 小林coding (xiaolincoding.com)</a></div><div class="notion-text notion-block-a63a573e9b354d13b7ee309b02d4b491">超时重传的缺陷就是要设定这些超参数，快速重传设计思想不依赖于这些超参数。不过我们应该了解到重传机制并不是互斥的，我们可以在超时重传的基础上搭配快速重传。</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-a41ce7b2122a440ea4491859e5d5268b" data-id="a41ce7b2122a440ea4491859e5d5268b"><span><div id="a41ce7b2122a440ea4491859e5d5268b" class="notion-header-anchor"></div><a class="notion-hash-link" href="#a41ce7b2122a440ea4491859e5d5268b" title="快速重传"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">快速重传</span></span></h3><div class="notion-text notion-block-103e201497b64657abb55e9464abce6a">快速重传的核心思想很简单：<b>收到三次重复的ACK就进行重传</b></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-26c61e62c81445b7bffcd00ae2af54ae"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:647px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fb1bebcc0-1b32-43e7-ba8d-09562ebcaa67%2FUntitled.png%3Fid%3D26c61e62-c814-45b7-bffc-d00ae2af54ae%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D_Zc7KsNc-sT8WhHf61JVRVdHa7ysNl0-ymXnUF5FZDU?table=block&amp;id=26c61e62-c814-45b7-bffc-d00ae2af54ae&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-b56e4f4a18fc4e58baa21f15ce94c29d">比如上面的例子，发送方在发送了Seq3、4、5的时候累计收到了3次ACK2的信号，那么就会重发Seq2。</div><div class="notion-text notion-block-8811bcedf2a943ddbab6056fcf86b52f">但其实这里还有一个问题，就是<b>发送方应该重传所有包，还是单个包</b>？</div><ul class="notion-list notion-list-disc notion-block-f1c44ac7c8f547199a2a704d140b8590"><li>重传单个包：重传效率很低，如果ACK3也发生丢失，那么接下来收到3个ACK3，才能重传</li></ul><ul class="notion-list notion-list-disc notion-block-0aa86bbb450148368f8aeb508ae4d245"><li>重传所有包：如果没有发生丢失，会发送大量的重复数据，导致资源浪费</li></ul><div class="notion-text notion-block-8636692f206c4908a605188890939bba">我们可以看出来这里问题的关键点在于，发送方不知道哪些包发生了丢失，因此无法很好的选择重传的范围。因此设计了SACK选项，让接收方告诉发送方应该重传哪些包。</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-6f66d4dd768e432d83a6a1809e58a65b" data-id="6f66d4dd768e432d83a6a1809e58a65b"><span><div id="6f66d4dd768e432d83a6a1809e58a65b" class="notion-header-anchor"></div><a class="notion-hash-link" href="#6f66d4dd768e432d83a6a1809e58a65b" title="SACK方法"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">SACK方法</span></span></h3><div class="notion-text notion-block-5167730d99794ea1bf6ff0a04a551304">SACK是TCP的一个选项，也就是头部字段里面的一个值，用来告知发送方真正接受的包。要使用SACK，两个设备都必须同时支持SACK。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-ffbc1f3dfa9d4854bee6fae2003402ff"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Ff51888a6-b04f-4331-803d-03b4a083a106%2FUntitled.png%3Fid%3Dffbc1f3d-fa9d-4854-bee6-fae2003402ff%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DRcNSaUX0hetADzj5WTgC3hUH0HQMw4vojhdRp0YQ3K8?table=block&amp;id=ffbc1f3d-fa9d-4854-bee6-fae2003402ff&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-dab83a5579f2437c8eaf1e5e77a3bd34">上面这张图，在200~299的时候发送了丢失，接收方重传了3次ACK 200，此时接收方知道了200数据包发送丢失。而SACK返回了 300~500，这表示接收方已经接受了300~500的数据包。那么发送方此时根据ACK和SACK的值，重传200～299的数据包即可</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-5e309e36bb5a4b6ca01dfb34d0ecdd1a" data-id="5e309e36bb5a4b6ca01dfb34d0ecdd1a"><span><div id="5e309e36bb5a4b6ca01dfb34d0ecdd1a" class="notion-header-anchor"></div><a class="notion-hash-link" href="#5e309e36bb5a4b6ca01dfb34d0ecdd1a" title="DSACK方法"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">DSACK方法</span></span></h3><div class="notion-text notion-block-01f83ff2ee17475ea1feeb4fcf1ad327">D-SACK即Duplicate SACK，它可以告知发送方哪些数据被重复接受了。</div><div class="notion-text notion-block-0ef442dff0fe412a8ba45d0589409247">下面用两个例子来说明D-SACK的作用</div><div class="notion-text notion-block-1a780314f64841b48fe4fb3db74915c8"><b>例子1 ACK丢包</b></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-0c554c5f49b5477b87982c7f670349e7"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fa0cd0960-f406-431a-8285-bd2bb4a2aa9c%2FUntitled.png%3Fid%3D0c554c5f-49b5-477b-8798-2c7f670349e7%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D7K6XHs1m6RL2o4L22tvcJ0wggefbsizMrEX4a4M9_kc?table=block&amp;id=0c554c5f-49b5-477b-8798-2c7f670349e7&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-f35a664c45984d93ac60eaafff54b2ce">如果ACK号&gt;SACK号，那么就表示这是一个D-SACK包。上面是ACK丢失的情况，此时出发超时重传，重发3000~3499的数据包，后续收到了ACK=4000，表示此时4000之前的数据都被收到了，同时SACK=3000~3500，那么表示3000~3500的数据包被重复接受了，发送方就会得知：<b>是ACK丢失了</b></div><div class="notion-text notion-block-2d03a3b0b42c4dc39ad3b4b2bf1708e9"><b>例子2 网络延时</b></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-302be0fae00f4ea191660bd12aae0339"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F0dc5ece1-935f-4ba0-b89a-e1427dfae4bf%2FUntitled.png%3Fid%3D302be0fa-e00f-4ea1-9166-0bd12aae0339%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D1m0xbmhEjouWEfGwu08F83tfDOizstQsazo61PjoiGU?table=block&amp;id=302be0fa-e00f-4ea1-9166-0bd12aae0339&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-4e7bd2379a3241b380eb213db0f8ba70">这个例子中，ACK没有丢失，同时重发了三次，而1000~1499因为网络延迟没有到达，于是接收方重发了1000~1499。</div><div class="notion-text notion-block-4d3d67a2f0144d34852c011ab8eb4091">后续接收方收到延时的数据后会重发一个ACK=3000，SACK=1000~1500，那么接收方就知道了重传的原因是：<b>网络延时</b></div><div class="notion-text notion-block-59150f896d6940128f10fb3b89779c25">我们可以通过上面两个例子得知D-SACK的好处：</div><ul class="notion-list notion-list-disc notion-block-54329299181e45ee953cc498068e4af8"><li>可以让发送方知道重传的原因</li></ul><ul class="notion-list notion-list-disc notion-block-ba9b4c2362904b1a918ebc1de321f204"><li>可以知道哪些数据是被重复接收的。</li></ul><div class="notion-text notion-block-aedcb58daeb146efa8f327e617d4d3f9">而D-SACK和SACK的判断依据在于：</div><div class="notion-text notion-block-790b1f640132465cacc84520135c5226">如果ACK&gt;SACK，那么SACK就是一个D-SACK包，其中的数据是重复接收的</div><div class="notion-text notion-block-c99a18c899ee4ad7883e7abd4d8de819">如果ACK&lt;SACK，那么SACK就是表示已经接收的数据。</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-f3eac9c1d0cb4d34a82ddd4c665a3119" data-id="f3eac9c1d0cb4d34a82ddd4c665a3119"><span><div id="f3eac9c1d0cb4d34a82ddd4c665a3119" class="notion-header-anchor"></div><a class="notion-hash-link" href="#f3eac9c1d0cb4d34a82ddd4c665a3119" title="滑动窗口"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">滑动窗口</span></span></h2><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-b9059df6ff5141be97f04997e7645578"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F8a2ddda7-46de-41a3-8de4-46a221ef174b%2FUntitled.png%3Fid%3Db9059df6-ff51-41be-97f0-4997e7645578%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DlgdepJZeqT72OLseXrU5pEBOPOlVBgRj2zvPehJzhF0?table=block&amp;id=b9059df6-ff51-41be-97f0-4997e7645578&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-e1a5ea0b88a24e939d8ac7ba520baa96"><b>提高通信效率</b></div><div class="notion-text notion-block-25272d5dd80b4bb9b648a1981de4c25d">上面介绍的通信模式都是发送方发送一个包，接收方返回一个ACK。放到现实生活中就是A说一句话，B要等A说完再说话，就这样轮流交替。这种通信模式效率太低下了</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-05db9914360f49c6ba053c202cca44d6"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:480px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fcb054a56-3bde-4d3a-a0d6-10224a11acb3%2FUntitled.png%3Fid%3D05db9914-360f-49c6-ba05-3c202cca44d6%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D8s2MVLdafW--yo6Fv1BB29FrHQgiZzm2UNcCL4hubCk?table=block&amp;id=05db9914-360f-49c6-ba05-3c202cca44d6&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-1b83fe7b442e40069220774e3e23749e">为了解决这个问题，TCP引入了<b>窗口</b>的概念，窗口可以支持批量发送数据，<b>窗口大小</b>指的就是<b>可以继续发送数据的最大值</b>。</div><div class="notion-text notion-block-7b2b0aa4b71c45babe1672caf0729254">窗口的底层实际上是操作系统为网络通信开辟的一个缓存空间，发送主机在等到ACK之前，必须在缓冲区中保存发送过的数据，如果收到了ACK，那么就可以清除这些旧数据。</div><div class="notion-text notion-block-0838f1f6c5d848408b97795fa3f1dabe">假设窗口大小为3，那么发送方可以连续发送3个包，如果中途有ACK丢失，可以根据最新的ACK来选择重发的数据。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-4c3f9bcb194f4c6689815bacec8aebc2"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F2a8c03a8-645e-4265-a9f3-b0c95b9f88f4%2FUntitled.png%3Fid%3D4c3f9bcb-194f-4c66-8981-5bacec8aebc2%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DpgBLQT4aKyGAfNJ-emq5bFXJCqiCIwpUPR_LOyfk0RE?table=block&amp;id=4c3f9bcb-194f-4c66-8981-5bacec8aebc2&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-1b261ea0e79043bcbfbd68012908981f">比如说上图中，ACK 600丢失了，但是最新返回的是ACK 700，这表示700之前的数据都被接收了，600不需要重发就可以继续进行发送。</div><blockquote class="notion-quote notion-block-5649ecd51f14425786e53ca8aa1eab1d">在HTTP1.1中，开启管道后也是类似的通信方式，每次都可以发送一批数据</blockquote><div class="notion-text notion-block-1ef85d0649ba4326ac730d71cf11bf2e"><b>窗口大小由哪一方决定？</b></div><div class="notion-text notion-block-ce93295d17a540b0a87d50a2ac03a05d">发送速度和消费速度可能会不一致，我们应该考虑消费速度，避免接收方处理不过来。</div><div class="notion-text notion-block-f32afdb72a5e49cdb2505f26126705c8">TCP头里面有一个字段叫做<code class="notion-inline-code">Window</code> ，也就是窗口大小，一般是以接收方的窗口大小决定的。</div><div class="notion-text notion-block-f2635396659441318c8832dc63a82b05"><b>发送方的滑动窗口</b></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-c96199930cde4a7c892dd50a1df807ef"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F49889321-d15b-4891-9111-b13423648fa1%2FUntitled.png%3Fid%3Dc9619993-0cde-4a7c-892d-d50a1df807ef%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DJ49GpdjWmsONF__y1h4F4wPjjkwgfA_rIvqqUXgMoig?table=block&amp;id=c9619993-0cde-4a7c-892d-d50a1df807ef&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-4aa0219674814592b103fdbf015f4f9b">发送方的滑动窗口可以分为四个部分：</div><ul class="notion-list notion-list-disc notion-block-141d25814cee4a0698e613c7e3127175"><li>已经发送且收到ACK的数据</li></ul><ul class="notion-list notion-list-disc notion-block-a2819766385e4aaaadd55fee7168a1d8"><li><b>发送窗口</b>：已经发送但未收到ACK的数据</li></ul><ul class="notion-list notion-list-disc notion-block-919fb2d8c0964262ac3a6e5f6a3caa66"><li><b>可用窗口</b>：未发送但是可以发送的数据</li></ul><ul class="notion-list notion-list-disc notion-block-5ba0d23196474b2b849395e8be472ea2"><li>未发送且超过接收方处理范围的数据</li></ul><div class="notion-text notion-block-91a6860973a243f0a148994b74270151">当发送方全部发送数据后，可用窗口就为0，发送窗口就是整个窗口大小了</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-94e7d1fea059401aad4d8750c0bb63f7"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F02d3f17a-ff2a-470d-b0a9-4c9474727b9b%2FUntitled.png%3Fid%3D94e7d1fe-a059-401a-ad4d-8750c0bb63f7%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DaDeGOTaBB0GOPBo5q0x38He_K0pTUQG8C_zvqbPuWjQ?table=block&amp;id=94e7d1fe-a059-401a-ad4d-8750c0bb63f7&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-blank notion-block-a66d113d95284d8e8c8d5bdd9e67ae96"> </div><div class="notion-text notion-block-3abea9ec9b214dc6b149700660e4f34a">如果此时收到37的ACK，那么就可以往后移动窗口了</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-c708db8d18af4fcf808eda7f0bd598b3"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F796a38c6-32ac-4ad5-a8dc-f82f7a1b464e%2FUntitled.png%3Fid%3Dc708db8d-18af-4fcf-808e-da7f0bd598b3%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DOy5VRmWpz4q795ZDvMwxvwenPNxH_lmE9v40umgATQE?table=block&amp;id=c708db8d-18af-4fcf-808e-da7f0bd598b3&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-c9eadedddd0e45a0b5a8eba34d3af334">注意发送方的滑动窗口移动依据是<b>ACK号</b></div><div class="notion-text notion-block-ac2932c22d9442e6820fde48d361254a"><b>发送方的窗口表达</b></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-75f4cf9b188d40f58002ff92e6a62e74"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F15236f2b-27b6-447c-9e41-80dc8e796608%2FUntitled.png%3Fid%3D75f4cf9b-188d-40f5-8002-ff92e6a62e74%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DSxKf2mdkdrgermZTj4Q6TQm-X-zKhL_n47jQEnxrusw?table=block&amp;id=75f4cf9b-188d-40f5-8002-ff92e6a62e74&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-blank notion-block-843c725ceb18496e9f5edfd3fe03a897"> </div><ul class="notion-list notion-list-disc notion-block-0f4ac931060340808f86c4e25aee573f"><li><code class="notion-inline-code">SND.WND</code>：表示发送窗口的大小（大小是由接收方指定的）；</li></ul><ul class="notion-list notion-list-disc notion-block-90389ba3118d405d900882ff4e73f0cc"><li><code class="notion-inline-code">SND.UNA</code>（<em>Send Unacknoleged</em>）：是一个绝对指针，它指向的是已发送但未收到确认的第一个字节的序列号，也就是 #2 的第一个字节。</li></ul><ul class="notion-list notion-list-disc notion-block-dd74b5bb09214584bde65c05862fe0c3"><li><code class="notion-inline-code">SND.NXT</code>：也是一个绝对指针，它指向未发送但可发送范围的第一个字节的序列号，也就是 #3 的第一个字节。</li></ul><ul class="notion-list notion-list-disc notion-block-90d89b2de37e434fa1fdbdc3adeddc2c"><li>指向 #4 的第一个字节是个相对指针，它需要 <code class="notion-inline-code">SND.UNA</code> 指针加上 <code class="notion-inline-code">SND.WND</code> 大小的偏移量，就可以指向 #4 的第一个字节了。</li></ul><div class="notion-text notion-block-53f42715d5d04974956c54c5c2ae7a01">那么可用窗口大小的计算就可以是：</div><div class="notion-text notion-block-f51f12a7ea5141e4a0e4708b29c2f9fe"><span class="notion-red_background">可用窗口大小 = SND.WND -（SND.NXT - SND.UNA）</span></div><div class="notion-text notion-block-61721dd591934cbf94a93be49c5e3da5"><b>接收方的滑动窗口</b></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-3466e124c3ed46aebd05715de7a066f6"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fc7c27898-a57b-461a-a034-4b7eee1f96d4%2FUntitled.png%3Fid%3D3466e124-c3ed-46ae-bd05-715de7a066f6%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D2fsmb_RqABpkrF0pqZ9BsFcNB9HuaELtCtp3C3yoPMk?table=block&amp;id=3466e124-c3ed-46ae-bd05-715de7a066f6&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><ul class="notion-list notion-list-disc notion-block-e30de54611ff4f00853d87895bb2ba45"><li><code class="notion-inline-code">RCV.WND</code>：表示接收窗口的大小，它会通告给发送方。</li></ul><ul class="notion-list notion-list-disc notion-block-e38642fb491e4fd3bd7d8e66590c3758"><li><code class="notion-inline-code">RCV.NXT</code>：是一个指针，它指向期望从发送方发送来的下一个数据字节的序列号，也就是 #3 的第一个字节。</li></ul><ul class="notion-list notion-list-disc notion-block-ec69962f4953419992e16ed20d81bf22"><li>指向 #4 的第一个字节是个相对指针，它需要 <code class="notion-inline-code">RCV.NXT</code> 指针加上 <code class="notion-inline-code">RCV.WND</code> 大小的偏移量，就可以指向 #4 的第一个字节了。</li></ul><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-f394c3d0528c4e30836db1cf1aade706" data-id="f394c3d0528c4e30836db1cf1aade706"><span><div id="f394c3d0528c4e30836db1cf1aade706" class="notion-header-anchor"></div><a class="notion-hash-link" href="#f394c3d0528c4e30836db1cf1aade706" title="流量控制"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">流量控制</span></span></h2><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-89043d034b58406bbc796985cc8faef3"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F977f3a00-5d9c-41fc-8b54-5b3f31947801%2FUntitled.png%3Fid%3D89043d03-4b58-406b-bc79-6985cc8faef3%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DiXHnJSL49mUijD06bOxo90AOcwZwx691dwOrWVpId2M?table=block&amp;id=89043d03-4b58-406b-bc79-6985cc8faef3&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-d6f9fcf0ad0d4f0c8bf10dc21f723a41">流量控制指的是端对端通信时，协调发送方的发送速度和接收方的处理速度，避免接收方处理不过来。TCP使用流量控制让发送方根据接收方的实际接受能力控制发送数据量。</div><div class="notion-text notion-block-41090323da5a4199a3d57e7370682b8c">假设接收方的可用窗口大小是400，那么这个值会在建立连接的时候告诉发送方，同时在通信过程中不断告知当前接收方的可用窗口大小<code class="notion-inline-code">rwnd</code>，发送方会根据这个大小发送数据。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-b284920d46764852b24becfd7fb44112"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fbd03514d-1645-4f82-8da7-15926664367b%2FUntitled.png%3Fid%3Db284920d-4676-4852-b24b-ecfd7fb44112%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DedBdDLUsPJNsBoK9VvoVk4rLVXSy_AefkXveqvCp70c?table=block&amp;id=b284920d-4676-4852-b24b-ecfd7fb44112&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-71b150058d954402b28156dd9d0b45d1">简述一下就是返回的信息里面带有接收方的可用窗口大小，让发送方避免发出处理范围之外的数据。</div><div class="notion-text notion-block-d2b1e8c6660c472b93ddc7ab22f50df9">另外这里有一个额外的知识点，那就是滑动窗口的大小依赖于缓冲区大小，如果操作系统调小了缓冲区大小，那么就会引起滑动窗口的收缩。而这个事件的发生顺序是要<b>先收缩滑动窗口，再调小缓冲区</b></div><div class="notion-text notion-block-7d588aa21cb24680b1f60a17b1419a25">否则的话可能还没来得及读取滑动窗口内的数据，就已经被限制读取范围了，导致部分数据永远读取不到了。</div><div class="notion-text notion-block-097b03c236f34b588d21a1261beb4baf"><b>窗口关闭</b></div><div class="notion-text notion-block-a54e1f28d2834780878465faaa162301">窗口关闭指的是接收方告知发送方可用窗口大小为0，此时发送方只有等待接收方告诉他可用窗口增加后才能发送数据。那么这里可能就有一个问题：非0窗口通知丢失。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-35a7e8d7c463406b885b6d3db5072a5d"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F662e7b61-6210-441f-a796-3c4b7742f3ae%2FUntitled.png%3Fid%3D35a7e8d7-c463-406b-885b-6d3db5072a5d%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DyE74PP-DXwQNZxTraT1nhyPxA18dTgPpz3k8QW4JXWs?table=block&amp;id=35a7e8d7-c463-406b-885b-6d3db5072a5d&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-ad3f747b0752478ca6bd09d02a44c683">如果非0窗口丢失了，那么就会出现循环等待的现象。</div><div class="notion-text notion-block-ac571f657c764de5bb6dd6fad4f2c8db">解决这个问题其实很简单，发送方收到0窗口通知后，就会开启一个计时器，一段时间后会发送一个<b>探测报文</b>，要求对方给出当前的可用窗口大小，那么这样就能避免死锁的现象了。</div><div class="notion-text notion-block-7baa552c7f8241f4aadbd4a9d5f3fe17"><b>糊涂窗口综合症</b></div><div class="notion-text notion-block-ac9a765478014b0bafa53e100c89bf08">就是接收方处理不过来，导致每次可用窗口大小都是几个字节，而发送方收到大小后就发送几个字节数据，这样的开销其实是很大的。</div><div class="notion-text notion-block-1cc550edc6774377a72cc25de253ea5d">这个问题现象产生于：</div><ul class="notion-list notion-list-disc notion-block-31dc83c48220400283b1a1b5cd10a539"><li>接收方告诉小窗口给发送方</li></ul><ul class="notion-list notion-list-disc notion-block-068463d370df4d22832df5567ab84bfa"><li>发送方可以发送小数据</li></ul><div class="notion-text notion-block-18531586ee4a4ef0bb9f0909e0cf8b6a">解决这个问题只需要把这两个现象处理掉就好了</div><ul class="notion-list notion-list-disc notion-block-8495646699cc49af8dc1a97ff1e1eaa5"><li>接收方不告知小窗口</li></ul><ul class="notion-list notion-list-disc notion-block-13dcaa53599844a2bda2977085af9d68"><li>发送方等待一批数据后再发送</li></ul><blockquote class="notion-quote notion-block-57d9d6ca87d0463ab13556d6d0a21a65">怎么让接收方不通告小窗口呢？</blockquote><div class="notion-text notion-block-a5bb0ac4ba8d465faf735bac9a6e8103">接收方通常的策略如下:</div><div class="notion-text notion-block-97d04442e5d24afe8a27b5a8ad416a54">当「窗口大小」小于 min( MSS，缓存空间/2 ) ，也就是小于 MSS 与 1/2 缓存大小中的最小值时，就会向发送方通告窗口为 <code class="notion-inline-code">0</code>，也就阻止了发送方再发数据过来。</div><div class="notion-text notion-block-526c71bed8c84bcc8df9e276779037db">等到接收方处理了一些数据后，窗口大小 &gt;= MSS，或者接收方缓存空间有一半可以使用，就可以把窗口打开让发送方发送数据过来。</div><blockquote class="notion-quote notion-block-2c52658df99e4e5ca3a2dc36bdf30e96">怎么让发送方避免发送小数据呢？</blockquote><div class="notion-text notion-block-88045983170f4d0e857f333aa4a93e5f">发送方通常的策略如下:</div><div class="notion-text notion-block-984958dd7dc642b28325ffa9bd175c34">使用 Nagle 算法，该算法的思路是延时处理，只有满足下面两个条件中的任意一个条件，才可以发送数据：</div><ul class="notion-list notion-list-disc notion-block-24e37a05dd4f49d4aa4f60abace5867b"><li>条件一：要等到窗口大小 &gt;= <code class="notion-inline-code">MSS</code> 并且 数据大小 &gt;= <code class="notion-inline-code">MSS</code>；</li></ul><ul class="notion-list notion-list-disc notion-block-dc0669502dcf48c3a613c49ed657115b"><li>条件二：收到之前发送数据的 <code class="notion-inline-code">ack</code> 回包；</li></ul><div class="notion-text notion-block-2c05b6e73d404307a8645efba71b4a82">只要上面两个条件都不满足，发送方一直在囤积数据，直到满足上面的发送条件。</div><div class="notion-text notion-block-c0c337a67210435c9db21226b05804c5">Nagle 伪代码如下：</div><pre class="notion-code language-go"><code class="language-go"><span class="token keyword">if</span> 有数据要发送 <span class="token punctuation">{</span>
    <span class="token keyword">if</span> 可用窗口大小 <span class="token operator">>=</span> <span class="token constant">MSS</span> and 可发送的数据 <span class="token operator">>=</span> <span class="token constant">MSS</span> <span class="token punctuation">{</span>
    	立刻发送<span class="token constant">MSS</span>大小的数据
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> 有未确认的数据 <span class="token punctuation">{</span>
            将数据放入缓存等待接收<span class="token constant">ACK</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
            立刻发送数据
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-4c8ec648eca6475aa24026be8a020f78">注意，如果接收方不能满足「不通告小窗口给发送方」，那么即使开了 Nagle 算法，也无法避免糊涂窗口综合症，因为如果对端 ACK 回复很快的话（达到 Nagle 算法的条件二），Nagle 算法就不会拼接太多的数据包，这种情况下依然会有小数据包的传输，网络总体的利用率依然很低。</div><div class="notion-text notion-block-79fd21e556404f1986e9beb47f335981">所以，<b>接收方得满足「不通告小窗口给发送方」+ 发送方开启 Nagle 算法，才能避免糊涂窗口综合症</b>。</div><div class="notion-text notion-block-459992772e08419fa47525d00cc36c0e">另外，Nagle 算法默认是打开的，如果对于一些需要小数据包交互的场景的程序，比如，telnet 或 ssh 这样的交互性比较强的程序，则需要关闭 Nagle 算法。</div><div class="notion-text notion-block-f52c8a63c21e4334b55251d20054d9ae">可以在 Socket 设置 <code class="notion-inline-code">TCP_NODELAY</code> 选项来关闭这个算法（关闭 Nagle 算法没有全局参数，需要根据每个应用自己的特点来关闭）</div><div class="notion-text notion-block-682cdc40e8af4579bb7f0763933da257"><code class="notion-inline-code">setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&amp;value, sizeof(int));</code></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-57969268dcf540f9bb7da5f89e2111aa" data-id="57969268dcf540f9bb7da5f89e2111aa"><span><div id="57969268dcf540f9bb7da5f89e2111aa" class="notion-header-anchor"></div><a class="notion-hash-link" href="#57969268dcf540f9bb7da5f89e2111aa" title="拥塞控制"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">拥塞控制</span></span></h2><div class="notion-blank notion-block-eeb9718466484e54a3fbde1c96d93301"> </div><div class="notion-text notion-block-afaeec7627364a6eaf797123e8fdf891">前面的流量控制是避免「发送方」的数据填满「接收方」的缓存，但是并不知道网络的中发生了什么。</div><div class="notion-text notion-block-299277b209354fe88741c39173d9294e">一般来说，计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。</div><div class="notion-text notion-block-9377b229087f47959c090c6ec84ac517"><b>在网络出现拥堵时，如果继续发送大量数据包，可能会导致数据包时延、丢失等，这时 TCP 就会重传数据，但是一重传就会导致网络的负担更重，于是会导致更大的延迟以及更多的丢包，这个情况就会进入恶性循环被不断地放大....</b></div><div class="notion-text notion-block-98ebfd3e30474aa8a9e41ef2f9c34a67">所以，TCP 不能忽略网络上发生的事，它被设计成一个无私的协议，当网络发送拥塞时，TCP 会自我牺牲，降低发送的数据量。</div><div class="notion-text notion-block-472b2802d72743c68e601de7ebad101d">于是，就有了<b>拥塞控制</b>，控制的目的就是<b>避免「发送方」的数据填满整个网络。</b></div><div class="notion-text notion-block-790e0a6943fd492cb5da03d5e7d1727b">为了在「发送方」调节所要发送数据的量，定义了一个叫做「<b>拥塞窗口</b>」的概念。</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-b48ae3199de640a8b2c8a3334bc577e2" data-id="b48ae3199de640a8b2c8a3334bc577e2"><span><div id="b48ae3199de640a8b2c8a3334bc577e2" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b48ae3199de640a8b2c8a3334bc577e2" title="什么是拥塞窗口"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">什么是拥塞窗口</span></span></h3><div class="notion-text notion-block-51cf397a0a1646d3889d9a5ae02c0bc4">TCP中一共有三种窗口：</div><ul class="notion-list notion-list-disc notion-block-e67fae02ef6e4e24b85a9b971b701160"><li>rwnd：接收窗口</li></ul><ul class="notion-list notion-list-disc notion-block-0f87cda949b74cc49cfeb432dd712b66"><li>swnd：发送窗口</li></ul><ul class="notion-list notion-list-disc notion-block-c974b4e79b3346968e3a958254559e15"><li>cwnd：拥塞窗口</li></ul><div class="notion-text notion-block-32cfe85dd0b046388ef38cdc0bbf92a1"><b>拥塞窗口 cwnd</b>是<b>发送方</b>维护的一个的状态变量，它会根据<b>网络的拥塞程度动态变化的</b>。</div><div class="notion-text notion-block-1b7ddf3ab90e4ebf8a8c819d2112854f">我们在前面提到过发送窗口 <code class="notion-inline-code">swnd</code> 和接收窗口 <code class="notion-inline-code">rwnd</code> 是约等于的关系，那么由于加入了拥塞窗口的概念后，此时发送窗口的值是swnd = min(cwnd, rwnd)，也就是拥塞窗口和接收窗口中的最小值。</div><div class="notion-text notion-block-437e0e56b0554df2b6b8c60bcf963ead">拥塞窗口 <code class="notion-inline-code">cwnd</code> 变化的规则：</div><ul class="notion-list notion-list-disc notion-block-99629183379540e7b96a0680043d674c"><li>只要网络中没有出现拥塞，<code class="notion-inline-code">cwnd</code> 就会增大；</li></ul><ul class="notion-list notion-list-disc notion-block-24c32a0cf35a4d84bd9d5b23fe828f92"><li>但网络中出现了拥塞，<code class="notion-inline-code">cwnd</code> 就减少；</li></ul><div class="notion-text notion-block-8be24be7f2404f56b4dd9f4031656103"><span class="notion-yellow_background">那么怎么知道当前网络是否出现了拥塞呢？</span></div><div class="notion-text notion-block-246f1683b106410096f7901508cd344f">其实只要「发送方」没有在规定时间内接收到 ACK 应答报文，也就是<b>发生了超时重传，就会认为网络出现了拥塞。</b></div><div class="notion-text notion-block-bca67e3315af403a8ff7460c41bf5bed"><span class="notion-yellow_background">拥塞控制有哪些控制算法？</span></div><div class="notion-text notion-block-d6bfda36061042cda250b267dc93117e">拥塞控制主要是四个算法：</div><ul class="notion-list notion-list-disc notion-block-086dd2394a884f409d303f30f416d239"><li>慢启动</li></ul><ul class="notion-list notion-list-disc notion-block-2b9da106e9ba46ce8db0921c145a299c"><li>拥塞避免</li></ul><ul class="notion-list notion-list-disc notion-block-9c42ea7479b64a87958eeaee1d786e1e"><li>拥塞发生</li></ul><ul class="notion-list notion-list-disc notion-block-1052ec37119f43b498d3b7dbac83ea09"><li>快速恢复</li></ul><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-78bcaa7caf5847078769d9f323a7391f" data-id="78bcaa7caf5847078769d9f323a7391f"><span><div id="78bcaa7caf5847078769d9f323a7391f" class="notion-header-anchor"></div><a class="notion-hash-link" href="#78bcaa7caf5847078769d9f323a7391f" title="慢启动"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">慢启动</span></span></h3><div class="notion-text notion-block-a3737bdbaff14fad8993467d4331e8e0">慢启动的核心思想是控制一开始发送方的发送速度，不要一上来速度就拉满。</div><div class="notion-text notion-block-c2a4f462ab2c468680ebc4a6d7cd17fd">规则：当发送方每接受一个ACK，拥塞窗口cwnd的大小就会加1</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-736742f198534d3fbf4c9ac5617d1c12"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F1f8bbd60-210b-4891-894f-10775abb247f%2FUntitled.png%3Fid%3D736742f1-9853-4d3f-bf4c-9ac5617d1c12%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D-SDl9BUwPopzMJxqIYb4K2kuVgkvpNrNCG4bh5nB4jM?table=block&amp;id=736742f1-9853-4d3f-bf4c-9ac5617d1c12&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-f6a1af6afd484504bf9f250d2948adeb">每个ACK都使得cwnd大小加1，而cwnd变大的同时还能发送多个数据包，因此它的增长速度就是指数性的增长。</div><div class="notion-text notion-block-f8aacec1eca94af6873efe1696e42744">但是cwnd并不是无限增长的，有一个叫做慢启动门限 <code class="notion-inline-code">ssthresh (slow start threshold</code>) 状态变量</div><ul class="notion-list notion-list-disc notion-block-6835e74b47054e33817dfc0351931353"><li>当cwnd &lt; ssthresh，使用慢启动算法</li></ul><ul class="notion-list notion-list-disc notion-block-60cf7f4f4c594ece94b76d61d3d6974e"><li>当cwnd ≥ ssthresh，使用拥塞避免算法</li></ul><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-b37f6e42076f4322b1a2e3272bcca9a3" data-id="b37f6e42076f4322b1a2e3272bcca9a3"><span><div id="b37f6e42076f4322b1a2e3272bcca9a3" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b37f6e42076f4322b1a2e3272bcca9a3" title="拥塞避免"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">拥塞避免</span></span></h3><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-0a79a897393a45609c230fb337243690"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2F863a108b-cb4c-46a0-b046-81ca6a23aea8%2FUntitled.png%3Fid%3D0a79a897-393a-4560-9c23-0fb337243690%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D7U2M8rHWCaE8fVz8iW8iVLJ4Mo540I_MojvlZcJj5A8?table=block&amp;id=0a79a897-393a-4560-9c23-0fb337243690&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-a43e1284c77f49ea90cf41d5b0adeb38">前面说道，当拥塞窗口 <code class="notion-inline-code">cwnd</code> 「超过」慢启动门限 <code class="notion-inline-code">ssthresh</code> 就会进入拥塞避免算法。</div><div class="notion-text notion-block-d5b0a19a62c14e9a8422cfabc6f79a19">一般来说 <code class="notion-inline-code">ssthresh</code> 的大小是 <code class="notion-inline-code">65535</code> 字节。</div><div class="notion-text notion-block-a5906fd98b114e8fa3698a8045b69390">那么进入拥塞避免算法后，它的规则是：<b>每当收到一个 ACK 时，cwnd 增加 1/cwnd。</b></div><div class="notion-text notion-block-4593186cdb23412d9b8be785749f5f80">接上前面的慢启动的栗子，现假定 <code class="notion-inline-code">ssthresh</code> 为 <code class="notion-inline-code">8</code>：</div><ul class="notion-list notion-list-disc notion-block-2ed93c891ee1483eb3efcba06271f94b"><li>当 8 个 ACK 应答确认到来时，每个确认增加 1/8，8 个 ACK 确认 cwnd 一共增加 1，于是这一次能够发送 9 个 <code class="notion-inline-code">MSS</code> 大小的数据，变成了<b>线性增长。</b></li></ul><div class="notion-text notion-block-054ab90f9c464c32a1fb9736c198ea18">拥塞避免算法的变化过程如下图：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-43af1140967c4a2e9a1ff601efb5f356"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fb45ef61b-54f7-4324-9c7f-7f20cad46c6b%2FUntitled.png%3Fid%3D43af1140-967c-4a2e-9a1f-f601efb5f356%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3Dw4jHB47QQvixZ8tFSS1leIT3IftbResC8ACp8V6ICtQ?table=block&amp;id=43af1140-967c-4a2e-9a1f-f601efb5f356&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-4ec3fd624f854e278538f35f7fb136dd">也就是说一旦cwnd越过了阈值，那么就会变成线性增长</div><div class="notion-text notion-block-0c4dcb3f6a9b4b66abbfd8052b53d69f">所以，我们可以发现，拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长，还是增长阶段，但是增长速度缓慢了一些。</div><div class="notion-text notion-block-aec4d95fa9f440baaaf5bd58c22952f0">就这么一直增长着后，网络就会慢慢进入了拥塞的状况了，于是就会出现丢包现象，这时就需要对丢失的数据包进行重传。</div><div class="notion-text notion-block-1388e3eb28864e9eb02801421a7225cd">当触发了重传机制，也就进入了「<b>拥塞发生算法</b>」。</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-f7415be02917493f9b9cfd0443e8e41c" data-id="f7415be02917493f9b9cfd0443e8e41c"><span><div id="f7415be02917493f9b9cfd0443e8e41c" class="notion-header-anchor"></div><a class="notion-hash-link" href="#f7415be02917493f9b9cfd0443e8e41c" title="拥塞发生算法"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">拥塞发生算法</span></span></h3><div class="notion-text notion-block-829326cd99b146c189108a4a8b5e1bb2">我们之前介绍了两种重传机制，分别是：</div><ul class="notion-list notion-list-disc notion-block-607eecd7a0ed43f080860aec2153000d"><li>超时重传</li></ul><ul class="notion-list notion-list-disc notion-block-d142a83205714d16a534817942ac0e7f"><li>快速重传</li></ul><div class="notion-text notion-block-f717d6d4558c49c1a34145b812a68442">根据重传机制不同，拥塞发生算法也会不同。</div><div class="notion-text notion-block-06d78042b8c149e1a259b3324e150c2f"><b>发生超时重传的拥塞算法</b></div><div class="notion-text notion-block-4194ea0b82734115a33c2976d76f8bf0">当发生了「超时重传」，则就会使用拥塞发生算法。</div><div class="notion-text notion-block-8cdd6d53aba540399f276e48a0af83df">这个时候，ssthresh 和 cwnd 的值会发生变化：</div><ul class="notion-list notion-list-disc notion-block-7c8cc86a0e7d4c8a866c6b4a26eb03cd"><li><code class="notion-inline-code">ssthresh</code> 设为 <code class="notion-inline-code">cwnd/2</code>，</li></ul><ul class="notion-list notion-list-disc notion-block-97cfc71807524dd6a9e264f98e4cdd0d"><li><code class="notion-inline-code">cwnd</code> 重置为 <code class="notion-inline-code">1</code> （是恢复为 cwnd 初始化值，我这里假定 cwnd 初始化值 1）</li></ul><div class="notion-text notion-block-eb07cdc6576c434c96ad75c873ed5cd6"><span class="notion-yellow_background">怎么查看系统的cwnd初始值？</span></div><div class="notion-text notion-block-5364abc7005a40a180821a13c4c88baf">Linux 针对每一个 TCP 连接的 cwnd 初始化值是 10，也就是 10 个 MSS，我们可以用 ss -nli 命令查看每一个 TCP 连接的 cwnd 初始化值，如下图</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-32eb20f3d0d443f3a1225c05636973b3"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fe04caa44-63ab-4981-9f02-cbbe366b0f8f%2FUntitled.png%3Fid%3D32eb20f3-d0d4-43f3-a122-5c05636973b3%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3DyEIBHxI13MlzFCHajosjbgUQTjq8T63nnZWjTwOmcKo?table=block&amp;id=32eb20f3-d0d4-43f3-a122-5c05636973b3&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-4ccd8704330642a892e8bc6263d0b078">拥塞发生算法的变化如下图：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-d62c01d6f0004d10bb88046976edf4d5"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fbd4240a0-bc30-461e-ad3d-8b7cba2951ec%2FUntitled.png%3Fid%3Dd62c01d6-f000-4d10-bb88-046976edf4d5%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3D3FiLmKCUcoQMy4OnwBHfecQkD91JL_0dc9UCVgGqoOg?table=block&amp;id=d62c01d6-f000-4d10-bb88-046976edf4d5&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-79c43aaec73e44799137412dea032884">接着，就重新开始慢启动，慢启动是会突然减少数据流的。这真是一旦「超时重传」，马上回到解放前。</div><div class="notion-text notion-block-e5f22886a78642f4b4092b9dcf3c48c7">但是这种方式太激进了，反应也很强烈，会造成网络卡顿。</div><div class="notion-text notion-block-b7b63e2adbd14825a2759680cce824d0"><b>发生快速重传的拥塞发生算法</b></div><div class="notion-text notion-block-978965722cc64a2ca5d5d0b7a4fa59d4">还有更好的方式，前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候，发送三次前一个包的 ACK，于是发送端就会快速地重传，不必等待超时再重传。</div><div class="notion-text notion-block-a129f65233c0464bab53a9dbd4bf5083">TCP 认为这种情况不严重，因为大部分没丢，只丢了一小部分，则 <code class="notion-inline-code">ssthresh</code> 和 <code class="notion-inline-code">cwnd</code> 变化如下：</div><ul class="notion-list notion-list-disc notion-block-ab75d6f8ca5143fc9e9d497f648105c2"><li><code class="notion-inline-code">cwnd = cwnd/2</code> ，也就是设置为原来的一半;</li></ul><ul class="notion-list notion-list-disc notion-block-3d3338cbf47f402f81b5d4d8aae9d6ec"><li><code class="notion-inline-code">ssthresh = cwnd</code>;</li></ul><ul class="notion-list notion-list-disc notion-block-a58b9ccc933d4e36ac306decd5665a99"><li><b>进入快速恢复算法</b></li></ul><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-97028e27aa3d4ba9b41099c57cfa23a1" data-id="97028e27aa3d4ba9b41099c57cfa23a1"><span><div id="97028e27aa3d4ba9b41099c57cfa23a1" class="notion-header-anchor"></div><a class="notion-hash-link" href="#97028e27aa3d4ba9b41099c57cfa23a1" title="快速恢复"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">快速恢复</span></span></h3><div class="notion-text notion-block-ecafa4786a194e4b9b3cf5d88bb9f0d7">快速重传和快速恢复算法一般同时使用，快速恢复算法是认为，你还能收到 3 个重复 ACK 说明网络也不那么糟糕，所以没有必要像 <code class="notion-inline-code">RTO</code> 超时那么强烈。</div><div class="notion-text notion-block-0954483aa2e24e688b2eb0e82f327bb4">正如前面所说，进入快速恢复之前，<code class="notion-inline-code">cwnd</code> 和 <code class="notion-inline-code">ssthresh</code> 已被更新了：</div><ul class="notion-list notion-list-disc notion-block-c7a27e5352174524bd05b1769a2c8425"><li><code class="notion-inline-code">cwnd = cwnd/2</code> ，也就是设置为原来的一半;</li></ul><ul class="notion-list notion-list-disc notion-block-8ce1629d423f4d7bbc45717ea27dc39e"><li><code class="notion-inline-code">ssthresh = cwnd</code>;</li></ul><div class="notion-text notion-block-dd170cb906f748c8aab88a233c628a40">然后，进入快速恢复算法如下：</div><ul class="notion-list notion-list-disc notion-block-c0cb1ee53a7c4c419a34b10193f11ac9"><li>拥塞窗口 <code class="notion-inline-code">cwnd = ssthresh + 3</code> （ 3 的意思是确认有 3 个数据包被收到了）；</li></ul><ul class="notion-list notion-list-disc notion-block-3081b6751dc2464f86c126e69cc7902e"><li>重传丢失的数据包；</li></ul><ul class="notion-list notion-list-disc notion-block-9b4dd01c300f4e96b61ca8196a60e81c"><li>如果再收到重复的 ACK，那么 cwnd 增加 1；</li></ul><ul class="notion-list notion-list-disc notion-block-5cf2bdd7d8e7435e8d03afb3c4f35a33"><li>如果收到新数据的 ACK 后，把 cwnd 设置为第一步中的 ssthresh 的值，原因是该 ACK 确认了新的数据，说明从 duplicated ACK 时的数据都已收到，该恢复过程已经结束，可以回到恢复之前的状态了，也即再次进入拥塞避免状态；</li></ul><div class="notion-text notion-block-b814fb1cb4aa426597ad86beb043f5d0">也就是没有像「超时重传」一夜回到解放前，而是还在比较高的值，后续呈线性增长。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-ac728d7c5a3842b6a5de0688cc41f7da"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Fs%2Fdf302fba-21b0-41ee-b0a0-542a3da54b70%2FUntitled.png%3Fid%3Dac728d7c-5a38-42b6-a5de-0688cc41f7da%26table%3Dblock%26spaceId%3De7bb21a8-53c5-4141-a2a6-9c3a436ecfe4%26expirationTimestamp%3D1693584000000%26signature%3Df8o4zk2VslcAeA0L2RKiVF4J5yMNSvqVcB1MyQCkKeU?table=block&amp;id=ac728d7c-5a38-42b6-a5de-0688cc41f7da&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-5bba1988e4644774a2a9e360014ed58d">很多人问题，快速恢复算法过程中，为什么收到新的数据后，cwnd 设置回了 ssthresh ？</div><ol start="1" class="notion-list notion-list-numbered notion-block-3bb73c10a60e40ab91ab6f8e7c6fc4b1"><li>在快速恢复的过程中，首先 ssthresh = cwnd/2，然后 cwnd = ssthresh + 3，表示网络可能出现了阻塞，所以需要减小 cwnd 以避免，加 3 代表快速重传时已经确认接收到了 3 个重复的数据包；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-6ca27fc6ca224950bc8cc625bab3c996"><li>随后继续重传丢失的数据包，如果再收到重复的 ACK，那么 cwnd 增加 1。加 1 代表每个收到的重复的 ACK 包，都已经离开了网络。这个过程的目的是尽快将丢失的数据包发给目标。</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-44259fad313443cdab59492610fafcd4"><li>如果收到新数据的 ACK 后，把 cwnd 设置为第一步中的 ssthresh 的值，恢复过程结束。</li></ol><div class="notion-text notion-block-455b074b0ead43ada31d98c31af343e8"><b>首先，快速恢复是拥塞发生后慢启动的优化，其首要目的仍然是降低 cwnd 来减缓拥塞，所以必然会出现 cwnd 从大到小的改变。</b></div><div class="notion-text notion-block-4302b9194dfe42649fb1b56cee61c3de"><b>其次，过程2（cwnd逐渐加1）的存在是为了尽快将丢失的数据包发给目标，从而解决拥塞的根本问题（三次相同的 ACK 导致的快速重传），所以这一过程中 cwnd 反而是逐渐增大的。</b></div><div class="notion-text notion-block-fe5f0879ecee4336ab43366020f8138e"><span class="notion-yellow_background">总结：</span></div><ul class="notion-list notion-list-disc notion-block-b7a5aa7457c74268a95c8ac76ff93b96"><li>首先减半cwnd，然后将阈值设置为减半后的cwnd</li></ul><ul class="notion-list notion-list-disc notion-block-eda707fa712a4967a6bec0a37e6bdf1e"><li>进入恢复阶段，此时的目标是将数据包尽可能的重传给对方，cwnd += 3表示之前接收了3个相同的ACK，然后进入线性增长</li></ul><ul class="notion-list notion-list-disc notion-block-b5172fbf5902494296b7882a37c3f9eb"><li>如果收到新的ACK，表示恢复阶段结束，将cwnd设置回阈值，避免拥塞</li></ul><div class="notion-blank notion-block-0447e7d387644a6ab1ff333157f489e1"> </div><div class="notion-blank notion-block-2b508539f113452fac6149805dca28e6"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-874fef272d30445fbc0e722e59292e20" data-id="874fef272d30445fbc0e722e59292e20"><span><div id="874fef272d30445fbc0e722e59292e20" class="notion-header-anchor"></div><a class="notion-hash-link" href="#874fef272d30445fbc0e722e59292e20" title="Ref"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Ref</span></span></h2><div class="notion-text notion-block-2f9599a8b3ea402dbb37965dc19fd2c9"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://juejin.cn/post/7080339555995369503">【计算机网络】TCP的流量控制和拥塞控制 - 掘金 (juejin.cn)</a></div><div class="notion-text notion-block-ade79624e7b7459ab13940b14279c2df"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.zhihu.com/question/38749788">流量控制与拥塞控制的区别。为什么要把流量控制与拥塞控制分为两个名词？ - 知乎 (zhihu.com)</a></div><div class="notion-text notion-block-9ee0f12195874f97ade8845bb1918d95"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://xiaolincoding.com/network/3_tcp/tcp_feature.html#%E8%B6%85%E6%97%B6%E9%87%8D%E4%BC%A0">4.2 TCP 重传、滑动窗口、流量控制、拥塞控制 | 小林coding (xiaolincoding.com)</a></div><div class="notion-text notion-block-35bfa4a49f1b40918663922f66b7f7f2"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://blog.csdn.net/wdscq1234/article/details/52503315"> TCP-IP详解：SACK选项（Selective Acknowledgment）_CQ小子的博客-CSDN博客</a></div><div class="notion-blank notion-block-811c6228f41b41a3a0d8c72c8ca30e16"> </div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Go 的 new 和 make 区别]]></title>
            <link>https://hhmy27.github.io//new-and-make</link>
            <guid>https://hhmy27.github.io//new-and-make</guid>
            <pubDate>Mon, 21 Nov 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-14b6937ec53648c795f8b71b569ebc4d"><div class="notion-viewport"></div><div class="notion-table-of-contents notion-gray notion-block-1d25098fe6914ab68e052daa2ad1ec11"><a href="#05368d46c4f14a12a42c66c31f0baf82" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">简介</span></a><a href="#3d6d675234cc43b098f1324b156e2e18" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">make</span></a><a href="#46ae8ed7d7ee4b67ae48a689a9253e8c" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">new</span></a><a href="#9124ef644fc444ec9ca272a3146d0bcf" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">扩展</span></a><a href="#814be095d01d4014813bc448fb6039fe" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">指针比较原则</span></a><a href="#539338a95f7a465e949fa9795cdf6c44" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">传引用？还是传值？</span></a><a href="#3bf8e86a442b4df58c50ed9f3d1c2648" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">Ref</span></a></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-05368d46c4f14a12a42c66c31f0baf82" data-id="05368d46c4f14a12a42c66c31f0baf82"><span><div id="05368d46c4f14a12a42c66c31f0baf82" class="notion-header-anchor"></div><a class="notion-hash-link" href="#05368d46c4f14a12a42c66c31f0baf82" title="简介"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">简介</span></span></h2><div class="notion-text notion-block-9e7841fe7d184f47848feba93d01fde4">先上结论</div><div class="notion-text notion-block-c270d5ba017749a89bde6a28d3611cfb"><code class="notion-inline-code">new(T)</code>返回T类型的指针，指向T类型的零值</div><div class="notion-text notion-block-35b4c30f029c4b19990fa0eeba0c36aa"><code class="notion-inline-code">make(T)</code>返回初始化后的T的引用，只能用于<code class="notion-inline-code">slice、map、channel</code></div><div class="notion-text notion-block-44cf9019a007420caa4dd1753719b8a3">在Go里面，你几乎很少会遇到必须使用new的场景</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-3d6d675234cc43b098f1324b156e2e18" data-id="3d6d675234cc43b098f1324b156e2e18"><span><div id="3d6d675234cc43b098f1324b156e2e18" class="notion-header-anchor"></div><a class="notion-hash-link" href="#3d6d675234cc43b098f1324b156e2e18" title="make"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">make</span></span></h2><div class="notion-text notion-block-38ac86c4c1684d43b055aed779b709d9">make用来初始化<code class="notion-inline-code">slice、map、channel</code>，并返回这些结构的引用</div><div class="notion-text notion-block-db3665094e09475a80d94803a0d31cf8">如果我们使用<code class="notion-inline-code">var</code> 来声明一个结构体，将会是nil值，对nil值操作可能会引起panic，只有赋值之后才能使用，下面我们看看这个测试用例</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestMake</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">var</span> m1 map<span class="token punctuation">[</span>int<span class="token punctuation">]</span>string
	fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%#v\n"</span><span class="token punctuation">,</span> m1<span class="token punctuation">)</span>

	<span class="token literal-property property">m2</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>map<span class="token punctuation">[</span>int<span class="token punctuation">]</span>string<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%#v\n"</span><span class="token punctuation">,</span> m2<span class="token punctuation">)</span>

	<span class="token keyword">var</span> c1 chan string
	fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%#v\n"</span><span class="token punctuation">,</span> c1<span class="token punctuation">)</span>

	<span class="token literal-property property">c2</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>chan string<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%#v\n"</span><span class="token punctuation">,</span> c2<span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-43b536c593ee4afa90e59b959545d0cf">输出结果：</div><pre class="notion-code language-go"><code class="language-go">map<span class="token punctuation">[</span>int<span class="token punctuation">]</span><span class="token function">string</span><span class="token punctuation">(</span>nil<span class="token punctuation">)</span>
map<span class="token punctuation">[</span>int<span class="token punctuation">]</span>string<span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">(</span>chan string<span class="token punctuation">)</span><span class="token punctuation">(</span>nil<span class="token punctuation">)</span>
<span class="token punctuation">(</span>chan string<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token number">0x140000c0240</span><span class="token punctuation">)</span></code></pre><div class="notion-text notion-block-bb2b6747131f4535aa7ca7c7997211a5">可以看到确实是符合我们上面的分析</div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-46ae8ed7d7ee4b67ae48a689a9253e8c" data-id="46ae8ed7d7ee4b67ae48a689a9253e8c"><span><div id="46ae8ed7d7ee4b67ae48a689a9253e8c" class="notion-header-anchor"></div><a class="notion-hash-link" href="#46ae8ed7d7ee4b67ae48a689a9253e8c" title="new"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">new</span></span></h2><div class="notion-text notion-block-e95f4078f6264a648b3df362559ebd28"><code class="notion-inline-code">new(T)</code> 适用的T范围更广，它返回T类型的指针，指向T类型的零值</div><div class="notion-text notion-block-8804f3e1d8ff44e5b423f33b4486ddd9">比如说这个例子：</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestNew</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">var</span> p1 <span class="token operator">*</span>int
	<span class="token literal-property property">a</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token number">1</span>
	<span class="token comment">// fmt.Println(*p1) p1 is nil, panic</span>
	p1 <span class="token operator">=</span> <span class="token operator">&amp;</span>a
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">*</span>p1<span class="token punctuation">)</span>

	<span class="token literal-property property">p2</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>int<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">*</span>p2<span class="token punctuation">)</span>
	p2 <span class="token operator">=</span> <span class="token operator">&amp;</span>a
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token operator">*</span>p2<span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-acc7768e1dd9492ba52ac32484a0841c">输出：</div><pre class="notion-code language-go"><code class="language-go"><span class="token number">1</span>
<span class="token number">0</span>
<span class="token number">1</span></code></pre><div class="notion-text notion-block-a006a90d6fdf4ff9aa6bb3958140ed99">我们可以通过这个例子看到<code class="notion-inline-code">var</code> 和<code class="notion-inline-code">new</code> 的区别，<code class="notion-inline-code">var</code>一个指针此时是nil的，未经赋值直接使用会panic，而<code class="notion-inline-code">new</code>就不会有这种问题出现</div><div class="notion-text notion-block-c9e79494a043470d9662528e870b1ad5">实际上我们很少会用到<code class="notion-inline-code">new</code> 因为它总是可以被其它操作替代</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestNew2</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">var</span> car1 <span class="token operator">*</span>Car
	<span class="token literal-property property">InnerCar</span> <span class="token operator">:</span><span class="token operator">=</span> Car<span class="token punctuation">{</span><span class="token punctuation">}</span>
	car1 <span class="token operator">=</span> <span class="token operator">&amp;</span>InnerCar

	<span class="token keyword">var</span> car2 <span class="token operator">*</span>Car <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>Car<span class="token punctuation">)</span>

	<span class="token literal-property property">car3</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token operator">&amp;</span>Car<span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token comment">// 最简洁的方式</span>

	<span class="token literal-property property">car4</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>Car<span class="token punctuation">)</span>

	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>car1<span class="token punctuation">.</span>id<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>car2<span class="token punctuation">.</span>id<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>car3<span class="token punctuation">.</span>id<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>car4<span class="token punctuation">.</span>id<span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-66653bda38a0442eb98dfffaf3b23a5f">输出结果：</div><pre class="notion-code language-go"><code class="language-go"><span class="token number">0</span>
<span class="token number">0</span>
<span class="token number">0</span>
<span class="token number">0</span></code></pre><div class="notion-text notion-block-c96ab88ec44147c88709cd1154cd4d69">你可以看到上面四种Car都是指针，它们输出结果都是一致的，其中<code class="notion-inline-code">&amp;Car</code>是最简洁的声明方式了</div><div class="notion-text notion-block-36600af4f04e4f23854c9b8c28523575">C/C++语言的选手可能会感到疑惑，为什么不用<code class="notion-inline-code">(&amp;Car3).id</code> 也能拿到<code class="notion-inline-code">id</code>的值呢？</div><div class="notion-text notion-block-72d013a6b112411ab11aa0978304f6b3">这是因为Go从语言层面做了优化：</div><blockquote class="notion-quote notion-block-43f29b0b6df14708b7dc306597802f59">如果x是指针，&amp;x里面属性集合包含了m，那么x.m和(&amp;x).m是等价的，Go会自动转换</blockquote><div class="notion-blank notion-block-4e64fe4926c943a2893625bb0b4775cd"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-9124ef644fc444ec9ca272a3146d0bcf" data-id="9124ef644fc444ec9ca272a3146d0bcf"><span><div id="9124ef644fc444ec9ca272a3146d0bcf" class="notion-header-anchor"></div><a class="notion-hash-link" href="#9124ef644fc444ec9ca272a3146d0bcf" title="扩展"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">扩展</span></span></h2><div class="notion-text notion-block-b1db98e9a70e4a49ab56ecc99055e7bf">既然提到了指针，我们来聊聊指针相关的东西吧</div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-814be095d01d4014813bc448fb6039fe" data-id="814be095d01d4014813bc448fb6039fe"><span><div id="814be095d01d4014813bc448fb6039fe" class="notion-header-anchor"></div><a class="notion-hash-link" href="#814be095d01d4014813bc448fb6039fe" title="指针比较原则"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">指针比较原则</span></span></h3><div class="notion-text notion-block-c52c04f2c09843cca8dada1c3337d495">Go定义了指针比较原则：</div><ul class="notion-list notion-list-disc notion-block-37f7998564464e7fb920567738d83ed8"><li>只有同类型的指针才能比较</li></ul><div class="notion-text notion-block-3897df8f0195455f84a1928936c038e5">如果你在1.18之前尝试对不同类型的指针比较，就会报错，提示你：</div><pre class="notion-code language-go"><code class="language-go"><span class="token punctuation">.</span><span class="token operator">/</span>slice_test<span class="token punctuation">.</span>go<span class="token operator">:</span><span class="token number">120</span><span class="token operator">:</span><span class="token number">17</span><span class="token operator">:</span> invalid operation<span class="token operator">:</span> p4 <span class="token operator">==</span> <span class="token function">p5</span> <span class="token punctuation">(</span>mismatched types <span class="token operator">*</span>int and <span class="token operator">*</span>string<span class="token punctuation">)</span>
<span class="token literal-property property">note</span><span class="token operator">:</span> module requires Go <span class="token number">1.18</span></code></pre><ul class="notion-list notion-list-disc notion-block-f8325c96b53647edb5e96f3ae3425cc8"><li>指针指向同一个对象，或者都是nil的时候比较结果是<code class="notion-inline-code">true</code></li></ul><ul class="notion-list notion-list-disc notion-block-5ed686df2cde4eccb3be792a1a6c4986"><li>否则比较结果都是<code class="notion-inline-code">false</code></li></ul><div class="notion-text notion-block-fa0122580af9484bb6c2db9d5c939a73">比如这个例子</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestNil</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">var</span> p1 <span class="token operator">*</span>int
	<span class="token keyword">var</span> p2 <span class="token operator">*</span>int
	<span class="token literal-property property">a</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token number">1</span>
	<span class="token literal-property property">p3</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>int<span class="token punctuation">)</span>
	p3 <span class="token operator">=</span> <span class="token operator">&amp;</span>a
	<span class="token literal-property property">p4</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token operator">&amp;</span>a

	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>p1 <span class="token operator">==</span> p2<span class="token punctuation">)</span> <span class="token comment">// true, p1 and p2 is nil</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>p2 <span class="token operator">==</span> p3<span class="token punctuation">)</span> <span class="token comment">// false, p2 is nil, p3 non-nil </span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>p4 <span class="token operator">==</span> p3<span class="token punctuation">)</span> <span class="token comment">// true, p3 and p4 指向同一个对象</span>
<span class="token punctuation">}</span></code></pre><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-539338a95f7a465e949fa9795cdf6c44" data-id="539338a95f7a465e949fa9795cdf6c44"><span><div id="539338a95f7a465e949fa9795cdf6c44" class="notion-header-anchor"></div><a class="notion-hash-link" href="#539338a95f7a465e949fa9795cdf6c44" title="传引用？还是传值？"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">传引用？还是传值？</span></span></h3><div class="notion-text notion-block-eef75676265a4557be2d98a7ff370022">先说结论：Go是值传递，所有类型数据都会拷贝一个副本到函数里面。</div><div class="notion-text notion-block-0345897226dd484e90c5731c25ba9aa3">但是Go又把数据类型分为两种：</div><ul class="notion-list notion-list-disc notion-block-5052e00a25cf4f9b91c300d08bb1ceb0"><li>引用类型：指针、slice、map、channel、接口、函数等</li></ul><ul class="notion-list notion-list-disc notion-block-f08bafb7c5944e6cba96df46cfb94f1d"><li>非引用类型：int、string、float、bool、数组和struct</li></ul><div class="notion-text notion-block-6643b0e10d45456293be0faefee5d5ca">引用类型传递的时候依然会拷贝一个副本，但是这个副本和引用指向了同一个对象，因此函数内的修改会影响原引用数据</div><div class="notion-text notion-block-79b28e868638493dae8bb175f78c39d1">而非引用类型就是普通的值传递了，不会影响到原有的数据</div><pre class="notion-code language-go"><code class="language-go">func <span class="token function">TestModify</span><span class="token punctuation">(</span><span class="token parameter">t <span class="token operator">*</span>testing<span class="token punctuation">.</span><span class="token constant">T</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token literal-property property">a</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token number">1</span>
	<span class="token function">modifyInt</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token comment">// 没有改变</span>

	<span class="token literal-property property">b</span> <span class="token operator">:</span><span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span>int<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span>
	<span class="token function">modifySlice</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span> <span class="token comment">// 被改变了</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-4d76754c1cb64041b56caaf2048b95ac">输出结果：</div><pre class="notion-code language-go"><code class="language-go"><span class="token operator">-</span><span class="token number">1</span>
<span class="token number">1</span>
<span class="token punctuation">[</span><span class="token number">1</span> <span class="token number">0</span> <span class="token number">0</span><span class="token punctuation">]</span>
<span class="token punctuation">[</span><span class="token number">1</span> <span class="token number">0</span> <span class="token number">0</span><span class="token punctuation">]</span></code></pre><div class="notion-blank notion-block-e5db92411295470b86e4d51a2fb4cf3e"> </div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-3bf8e86a442b4df58c50ed9f3d1c2648" data-id="3bf8e86a442b4df58c50ed9f3d1c2648"><span><div id="3bf8e86a442b4df58c50ed9f3d1c2648" class="notion-header-anchor"></div><a class="notion-hash-link" href="#3bf8e86a442b4df58c50ed9f3d1c2648" title="Ref"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Ref</span></span></h2><div class="notion-text notion-block-4a1fe34105a04d5e8888a67ed4395bd7"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://sanyuesha.com/2017/07/26/go-make-and-new/">理解 Go make 和 new 的区别 | 三月沙 (sanyuesha.com)</a></div><div class="notion-text notion-block-d60227a1cee04074bee019e88c482f0b"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.nhooo.com/golang/go-comparing-pointers.html">Go 语言指针比较 - Golang教程 - 基础教程在线 (nhooo.com)</a></div><div class="notion-text notion-block-247bc3c955be4b26a880c853de1b0ec0"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://zhuanlan.zhihu.com/p/383737884">Go的参数是传值还是传引用问题 - 知乎 (zhihu.com)</a></div></main>]]></content:encoded>
        </item>
    </channel>
</rss>