<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[苏辰]]></title><description><![CDATA[世上的路有很多条.]]></description><link>https://blog.chiran.top</link><image><url>https://blog.chiran.top/innei.svg</url><title>苏辰</title><link>https://blog.chiran.top</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Mon, 27 Jul 2026 23:11:45 GMT</lastBuildDate><atom:link href="https://blog.chiran.top/feed" rel="self" type="application/rss+xml"/><pubDate>Mon, 27 Jul 2026 23:11:45 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[晚安，东野圭吾🕯️]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.chiran.top/notes/1">https://blog.chiran.top/notes/1</a></blockquote><div><p>第一次读您的书还是初中，我和您的初遇，并非从脍炙人口的《解忧杂货店》开始。</p><p>不知从何时起，我深深陷入了您编织的世界中。从最初空闲时翻读，渐渐到上课也偷握着老年机，一头扎进那些悬念迭起的故事里。您陪着我走过了一天又一天。</p><p>可现在，您突然离去，却让书里那些鲜活的人，都永远停在了时光里：</p><ul><li>加贺恭一郎的脚步停在了日本桥</li><li>汤川学的粉笔落下最后一道公式</li><li>雪穗消失在白夜的尽头</li><li>石神再也不会出现在便当店门口</li><li>浪矢杂货店的卷帘门，再也不会升起</li><li>假面饭店的灯火，终是熄灭</li></ul><p>您写下的所有明暗与温柔</p>
<p>都成了我们读过的痕迹</p><p><strong>您终究为自己写就了不在场证明</strong></p><p>愿天堂有滑雪，有热爱的一切</p>
<p>我们在书里，等您</p></div><p style="text-align:right"><a href="https://blog.chiran.top/notes/1#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.chiran.top/notes/1</link><guid isPermaLink="true">https://blog.chiran.top/notes/1</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Mon, 27 Jul 2026 16:00:58 GMT</pubDate></item><item><title><![CDATA[中国移动宽带 DNS 劫持与 Windows msftconnecttest 自动跳转：原理、排查与解决方法]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.chiran.top/posts/生活日常/DNS">https://blog.chiran.top/posts/生活日常/DNS</a></blockquote><div><p>近日，国内西南地区以及江苏等地陆续有中国移动宽带用户遇到相似问题：</p><ul><li>电脑实际上可以访问互联网；</li><li>Windows 却提示“需要操作，没有 Internet”；</li><li>随后默认浏览器自动打开：</li></ul><pre class="language-text lang-text"><code class="language-text lang-text">http://www.msftconnecttest.com/redirect
</code></pre>
<ul><li>部分用户最终被重定向到陌生的博彩网站。</li></ul><p>这些现象组合在一起，指向了 Windows 网络连接状态检测与异常 DNS 解析之间的一次连锁反应。攻击者并不需要修改 Windows 注册表，也不需要直接控制浏览器；只要干预 Windows 联网检测域名的解析，就可能借助系统默认开启的主动探测功能触发恶意网页。</p><blockquote><p>本文根据用户侧现象和 Windows 官方公开的 NCSI 工作机制进行分析。截至发文时，如果运营商或有关机构尚未发布完整调查报告，具体影响范围、劫持位置和责任主体仍应以正式调查结果为准。</p></blockquote>
<h2 id="">先说结论</h2><p>遇到此问题时，建议按以下顺序处理：</p><ol start="1"><li>不要在自动打开的陌生页面中输入账号、密码、银行卡或验证码，也不要下载任何所谓的“网络修复工具”；</li><li>同时检查并修改 <strong>IPv4 DNS 和 IPv6 DNS</strong>，不能只修改 <code>114.114.114.114</code> 或 <code>8.8.8.8</code> 这样的 IPv4 地址；</li><li>尽可能启用 Windows 系统级或路由器级 <strong>DNS over HTTPS（DoH）</strong>，避免明文 DNS 被透明拦截；</li><li>清空 DNS 缓存，重新连接网络，并验证微软探测域名的解析和 HTTP 返回内容；</li><li>检查路由器、光猫、代理和 Hosts 文件；</li><li>只有在暂时无法处理底层问题时，才考虑禁用 <code>EnableActiveProbing</code> 阻止自动探测和弹窗。</li></ol><p>修改 <code>EnableActiveProbing</code> 只能关闭触发自动打开网页的功能，<strong>不能修复 DNS 劫持本身</strong>。</p><h2 id="windows--internet">Windows 为什么会提示“需要操作，没有 Internet”</h2><p>Windows 10 和 Windows 11 使用 <strong>NCSI</strong>（Network Connectivity Status Indicator，网络连接状态指示器）判断当前网络是否真正具备 Internet 访问能力。</p><p>当网卡连接、断开，网络配置、代理或路由状态发生变化时，NCSI 会进行主动探测。对于较新的 Windows，过程主要包括：</p><ol start="1"><li>解析 <code>www.msftconnecttest.com</code>；</li><li>通过明文 HTTP 请求访问：</li></ol><pre class="language-text lang-text"><code class="language-text lang-text">http://www.msftconnecttest.com/connecttest.txt
</code></pre>
<ol start="3"><li>检查 HTTP 状态和正文，正常响应应为：</li></ol><pre class="language-text lang-text"><code class="language-text lang-text">HTTP/1.1 200 OK

Microsoft Connect Test
</code></pre>
<ol start="4"><li>NCSI 还会查询 <code>dns.msftncsi.com</code>，并检查其 DNS 响应是否符合预期；</li><li>Windows 会结合主动探测和被动网络状态判断当前连接是“Internet”“仅本地网络”还是“可能需要认证”。</li></ol><p>微软文档说明，Windows 会分别进行 IPv4 和 IPv6 主动探测。如果探测未完成、超时、返回错误状态，或者收到的正文不是预期的 <code>Microsoft Connect Test</code>，Windows 就可能显示“无 Internet”或“需要操作”。</p><p>这并不一定意味着所有网络流量都已中断。它只表示 Windows 用于判断联网状态的特定探测没有通过，所以有时会出现“网页能打开，但系统仍说没有 Internet”的情况。</p><h2 id="windows--redirect">Windows 为什么会自动打开 <code>/redirect</code></h2><p>酒店、机场和校园网经常使用需要登录的认证页面，也就是 Captive Portal。为了帮助用户完成认证，当 NCSI 怀疑当前网络需要额外操作时，Windows 可能打开：</p><pre class="language-text lang-text"><code class="language-text lang-text">http://www.msftconnecttest.com/redirect
</code></pre>
<p>在正常网络中，这个地址后续可能跳转到微软的 MSN 门户，例如：</p><pre class="language-text lang-text"><code class="language-text lang-text">https://www.msn.cn/zh-cn
</code></pre>
<p>这是 Windows 和微软服务设计的正常行为。微软还会利用 MSN 页面是否成功加载，辅助判断电脑能否访问互联网。</p><p>因此必须区分两种情况：</p><table><thead><tr><th> 最终结果 </th><th> 含义 </th></tr></thead><tbody><tr><td> 跳转到 <code>msn.cn</code> 等微软页面 </td><td> 通常是 NCSI 的正常后续流程 </td></tr><tr><td> 跳转到博彩、仿冒登录、软件下载等陌生页面 </td><td> 重定向链路已遭到异常干预 </td></tr></tbody></table><p>也就是说，<strong>自动打开 <code>/redirect</code> 本身不是攻击证据，最终被导向非微软恶意网站才是关键异常。</strong></p><h2 id="dns-">DNS 劫持如何利用这套机制</h2><p>问题的关键在于，NCSI 的 Web 探测和 <code>/redirect</code> 入口都使用明文 HTTP。</p><p>如果攻击者让 <code>www.msftconnecttest.com</code> 或 IPv6 探测域名解析到错误地址，就可能控制后续响应：</p><pre class="language-text lang-text"><code class="language-text lang-text">Windows 检测到网络变化
        ↓
查询 msftconnecttest 域名
        ↓
DNS 返回攻击者指定的地址
        ↓
connecttest.txt 返回错误内容、重定向或空响应
        ↓
NCSI 认为网络需要操作
        ↓
Windows 打开 /redirect
        ↓
同一域名再次被解析到攻击者服务器
        ↓
攻击者返回 302，将浏览器导向博彩网站
</code></pre>
<p>攻击者不需要“开启”受害者电脑中的 <code>EnableActiveProbing</code>。这个选项在正常 Windows 中默认就是开启的，攻击者只是利用了系统原本会执行的联网检测流程。</p><p>严格来说，异常可能发生在多个位置：</p><ul><li>运营商递归 DNS 返回了错误结果；</li><li>路由器或光猫使用了异常的上游 DNS；</li><li>明文 UDP/TCP 53 查询被透明拦截；</li><li>路由器 DNS 缓存被污染；</li><li>终端的 Hosts、代理或恶意软件篡改了访问目标；</li><li>DNS 正常，但后续明文 HTTP 请求在传输链路上被劫持。</li></ul><p>如果要严格区分“DNS 劫持”和“HTTP 劫持”，需要保存事发时的 DNS 响应、HTTP 响应头或网络抓包。仅凭事后页面截图通常无法确定劫持发生在哪一跳。</p><h2 id="-windows-">为什么其他设备没有提示，只有 Windows 会</h2><p>不同操作系统使用不同的联网检测域名、返回内容和交互方式：</p><ul><li>Windows 使用 <code>msftconnecttest.com</code> 和 NCSI；</li><li>Android、iOS、macOS 等系统使用各自的探测地址；</li><li>有的系统只显示 Wi-Fi 认证提示；</li><li>有的系统不会主动打开完整浏览器；</li><li>同一网络中的设备还可能使用不同的 IPv4/IPv6 DNS、缓存、VPN 或加密 DNS。</li></ul><p>因此，攻击者如果只干预微软的探测域名，Windows 会最先出现明显症状，其他设备仍可能看起来正常。这并不代表其他设备的 DNS 一定安全，只是它们没有触发同样的系统行为。</p><h2 id="-114--8888">为什么已经设置 114 和 8.8.8.8，仍可能受到影响</h2><p>很多用户只在网卡中修改了 IPv4 DNS，例如：</p><pre class="language-text lang-text"><code class="language-text lang-text">114.114.114.114
8.8.8.8
</code></pre>
<p>但 Windows 的 IPv4 和 IPv6 DNS 是两套独立配置。即使 IPv4 DNS 已经手动指定，IPv6 DNS 仍可能由路由器自动下发。</p><p>可以使用以下命令查看：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">Get-DnsClientServerAddress |
  Format-Table InterfaceAlias, AddressFamily, ServerAddresses -AutoSize
</code></pre>
<p>其中：</p><ul><li><code>AddressFamily 2</code> 表示 IPv4；</li><li><code>AddressFamily 23</code> 表示 IPv6。</li></ul><p>如果看到类似：</p><pre class="language-text lang-text"><code class="language-text lang-text">以太网  IPv4  {114.114.114.114, 8.8.8.8}
以太网  IPv6  {fe80::xxxx:xxxx:xxxx:xxxx}
</code></pre>
<p>说明 IPv6 DNS 仍然指向路由器。<code>fe80::/10</code> 是链路本地地址，电脑会把 DNS 查询交给局域网中的路由器，再由路由器转发到光猫或运营商上游。</p><h3 id="ipv6-">IPv6 桥接模式的影响</h3><p>如果路由器启用了 IPv6 桥接、透传或 Passthrough 模式，它可能把光猫或运营商下发的 IPv6 前缀、默认路由和 DNS 信息继续传递给局域网设备。</p><p>此时的链路可能是：</p><pre class="language-text lang-text"><code class="language-text lang-text">Windows
  ↓
路由器的 fe80:: 链路本地 DNS
  ↓
光猫或运营商下发的上游 DNS
</code></pre>
<p>因此，仅修改 IPv4 DNS 不能覆盖这条 IPv6 解析链路。</p><p>还要注意：“IPv6 DNS”指 DNS 服务器通过 IPv6 地址提供服务，它既可以回答 <code>AAAA</code> 记录，也可以回答 IPv4 使用的 <code>A</code> 记录。问题并不局限于 IPv6 网站。</p><h2 id="-dns-">普通公共 DNS 为什么仍不能完全防止劫持</h2><p>手动填写公共 DNS 可以绕开部分运营商递归 DNS 故障，但传统 DNS 通常仍通过明文 UDP/TCP 53 端口传输。</p><p>这意味着，网络中的中间设备理论上仍然可以：</p><ul><li>拦截发往 <code>8.8.8.8</code> 或其他 DNS 的请求；</li><li>伪造一个更快到达的响应；</li><li>阻断指定 DNS，迫使系统改用其他服务器；</li><li>对特定域名返回错误地址。</li></ul><p>因此，“设置了公共 DNS”不等于“DNS 查询已经加密并验证了服务器身份”。要减少透明劫持，应进一步使用 DoH 或 DoT。</p><h2 id="doh-">DoH 能否避免这种问题</h2><p><strong>如果攻击仅发生在 DNS 查询阶段，系统级 DoH 通常可以有效避免。</strong></p><p>DoH 使用 HTTPS 传输 DNS 查询，中间设备不容易直接看到和篡改具体查询内容，并且客户端会验证 DoH 服务器的 TLS 证书。</p><p>但需要注意：</p><ol start="1"><li>只在浏览器中开启 DoH，不一定覆盖 Windows NCSI。NCSI 使用 Windows 系统 DNS 客户端，而不是浏览器自己的解析器；</li><li>应使用 Windows 系统级 DoH，或者在路由器上为整个局域网部署 DoH；</li><li>如果设置允许“加密优先、失败时回退明文”，DoH 被阻断后仍可能回到普通 DNS；</li><li>DoH 只能保护 DNS 查询，无法修复 Hosts、恶意代理、被入侵的路由器或后续 HTTP 链路劫持；</li><li>如果域名被正确解析，但明文 HTTP 流量仍被中间设备篡改，DoH 也不能单独解决问题。</li></ol><p>因此更完整的防护是：</p><pre class="language-text lang-text"><code class="language-text lang-text">可信 DNS + 系统级 DoH + 正确的 IPv4/IPv6 配置 + 路由器安全检查
</code></pre>
<h2 id="-ipv4--ipv6-dns">解决方法一：同时设置 IPv4 和 IPv6 DNS</h2><p>以下为常见公共 DNS：</p><table><thead><tr><th> 服务 </th><th> IPv4 首选 </th><th> IPv4 备用 </th><th> IPv6 首选 </th><th> IPv6 备用 </th></tr></thead><tbody><tr><td> 阿里公共 DNS </td><td> <code>223.5.5.5</code> </td><td> <code>223.6.6.6</code> </td><td> <code>2400:3200::1</code> </td><td> <code>2400:3200:baba::1</code> </td></tr><tr><td> Google Public DNS </td><td> <code>8.8.8.8</code> </td><td> <code>8.8.4.4</code> </td><td> <code>2001:4860:4860::8888</code> </td><td> <code>2001:4860:4860::8844</code> </td></tr><tr><td> 114DNS </td><td> <code>114.114.114.114</code> </td><td> <code>114.114.115.115</code> </td><td> — </td><td> — </td></tr></tbody></table><p>中国大陆不同地区对各公共 DNS 的可达性和延迟可能不同。可以优先选择连接稳定且支持加密 DNS 的服务。</p><h3 id="windows-11-">Windows 11 图形界面</h3><ol start="1"><li>打开“设置”；</li><li>进入“网络和 Internet”；</li><li>打开当前使用的“以太网”或“Wi-Fi”；</li><li>找到“DNS 服务器分配”，点击“编辑”；</li><li>将“自动（DHCP）”改为“手动”；</li><li>同时打开 IPv4 和 IPv6；</li><li>填入首选与备用 DNS；</li><li>如果系统提供“DNS over HTTPS”选项，将其开启；</li><li>保存并重新连接网络。</li></ol><p>如果使用阿里公共 DNS，DoH 地址为：</p><pre class="language-text lang-text"><code class="language-text lang-text">https://dns.alidns.com/dns-query
</code></pre>
<h3 id="">使用命令设置</h3><p>以网卡名“以太网”和阿里公共 DNS 为例，以管理员身份打开 PowerShell：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">netsh interface ipv4 set dnsservers name=&quot;以太网&quot; static 223.5.5.5 primary validate=no
netsh interface ipv4 add dnsservers name=&quot;以太网&quot; address=223.6.6.6 index=2 validate=no

netsh interface ipv6 set dnsservers name=&quot;以太网&quot; static 2400:3200::1 primary validate=no
netsh interface ipv6 add dnsservers name=&quot;以太网&quot; address=2400:3200:baba::1 index=2 validate=no
</code></pre>
<p>然后清空 DNS 缓存：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">ipconfig /flushdns
</code></pre>
<p>再次检查：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">Get-DnsClientServerAddress -InterfaceAlias &quot;以太网&quot;
</code></pre>
<p>如果 IPv6 DNS 仍显示路由器的 <code>fe80::...</code>，说明设置没有应用到当前网卡，或者路由器、组策略、VPN 等正在重新接管 DNS。</p><h2 id="-ipv6-dns--doh">解决方法二：在路由器中配置 IPv6 DNS 或 DoH</h2><p>如果家中多台设备都使用同一条宽带，在路由器中统一配置更方便。</p><p>进入路由器管理页面，检查：</p><ul><li>WAN 和 LAN 使用的 IPv4 DNS；</li><li>IPv6 DNS 是否为“自动获取”；</li><li>IPv6 是否处于桥接、透传或 Passthrough 模式；</li><li>是否支持自定义 IPv6 DNS；</li><li>是否支持 DoH、DoT 或加密 DNS；</li><li>是否存在陌生的静态 DNS、端口转发或远程管理配置。</li></ul><p>如果桥接模式强制继承光猫或运营商 DNS，可以选择：</p><ul><li>在每台终端上手动设置 IPv6 DNS 和系统级 DoH；</li><li>在支持的情况下，让路由器负责获取 IPv6 前缀并指定 DNS；</li><li>在路由器上部署加密 DNS 转发。</li></ul><p>不要在不了解拨号和前缀下发方式时直接修改光猫工作模式，否则可能造成 IPv4 或 IPv6 断网。</p><h2 id="-dns--ncsi-">解决方法三：验证 DNS 和 NCSI 是否恢复</h2><h3 id="-dns">检查系统 DNS</h3><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">Get-DnsClientServerAddress
</code></pre>
<h3 id="-ipv4--ipv6-">分别查询 IPv4 和 IPv6 探测域名</h3><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">Resolve-DnsName www.msftconnecttest.com -Type A
Resolve-DnsName ipv6.msftconnecttest.com -Type AAAA
Resolve-DnsName dns.msftncsi.com
</code></pre>
<p>不要依赖固定的 <code>msftconnecttest.com</code> IP 判断是否正常，因为微软探测服务由 CDN 承载，合法地址可能随地区和时间变化。更可靠的方法是：</p><ul><li>与可信 DoH 的结果进行比较；</li><li>检查 CNAME 是否指向微软使用的正常 CDN；</li><li>检查 HTTP 返回内容和重定向目标；</li><li>保存异常发生时的原始响应。</li></ul><h3 id="">检查探测文件</h3><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">curl.exe -4 --noproxy &quot;*&quot; -v --max-redirs 0 http://www.msftconnecttest.com/connecttest.txt
curl.exe -6 --noproxy &quot;*&quot; -v --max-redirs 0 http://ipv6.msftconnecttest.com/connecttest.txt
</code></pre>
<p>正常结果应包含：</p><pre class="language-text lang-text"><code class="language-text lang-text">HTTP/1.1 200 OK
Microsoft Connect Test
</code></pre>
<p>如果当前网络本身没有公网 IPv6，第二条命令失败并不一定代表劫持。</p><h3 id="-redirect">检查 <code>/redirect</code>，但不要自动跟随</h3><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">curl.exe -4 -sS -D - -o NUL --max-redirs 0 http://www.msftconnecttest.com/redirect
</code></pre>
<p>查看响应头中的：</p><pre class="language-text lang-text"><code class="language-text lang-text">Location: ...
</code></pre>
<p>如果目标是微软或 MSN 页面，通常属于正常行为；如果目标是博彩、仿冒登录或陌生下载站，应立即保存响应头和时间，不要在浏览器中继续访问。</p><h2 id="-dns--redirect-msn">为什么更换 DNS 后仍会访问 <code>/redirect</code>，但只跳到 MSN</h2><p>更换 DNS 只是恢复正确解析，不会关闭 Windows 的 NCSI 功能。</p><p>如果 Windows 已经因为前一次探测失败进入“需要操作”状态，或者在网络重新连接后再次评估连接，它仍可能打开：</p><pre class="language-text lang-text"><code class="language-text lang-text">http://www.msftconnecttest.com/redirect
</code></pre>
<p>区别在于：</p><ul><li>DNS 被劫持时，请求可能到达攻击者服务器并跳转到博彩网站；</li><li>DNS 恢复后，请求到达微软服务器并正常跳转到 MSN。</li></ul><p>因此，更换 DNS 后看到 MSN 通常说明恶意跳转已经停止，而不是 DNS 修改没有生效。</p><h2 id="-hosts-">是否需要检查 Hosts 文件</h2><p>需要，但它不是运营商级事件中最优先的嫌疑对象。</p><p>Windows 在向 DNS 服务器查询前会检查本地 Hosts 文件。如果其中存在相关映射，即使修改 DNS 也不会生效。</p><p>Hosts 文件路径：</p><pre class="language-text lang-text"><code class="language-text lang-text">C:\Windows\System32\drivers\etc\hosts
</code></pre>
<p>可以使用 PowerShell 检查：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">Select-String `
  -Path &quot;$env:SystemRoot\System32\drivers\etc\hosts&quot; `
  -Pattern &quot;msftconnecttest|msftncsi&quot;
</code></pre>
<p>正常情况下不应存在这些域名的自定义映射。</p><p>如果同一宽带下多台设备同时异常，而切换到手机流量后恢复，问题更可能位于路由器、光猫或运营商链路；如果只有一台设备异常，则应优先检查 Hosts、代理、虚拟网卡、恶意软件和浏览器扩展。</p><p>还可以检查 WinHTTP 代理：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">netsh winhttp show proxy
</code></pre>
<h2 id="-enableactiveprobing">临时措施：禁用 EnableActiveProbing</h2><p>如果暂时无法解决底层 DNS 问题，又希望立即阻止 Windows 自动触发探测和网页，可以禁用 NCSI 主动探测。</p><p>注册表路径：</p><pre class="language-text lang-text"><code class="language-text lang-text">HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet
</code></pre>
<p>将：</p><pre class="language-text lang-text"><code class="language-text lang-text">EnableActiveProbing
</code></pre>
<p>设置为：</p><pre class="language-text lang-text"><code class="language-text lang-text">0
</code></pre>
<p>也可以使用管理员 PowerShell：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">Set-ItemProperty `
  -Path &quot;HKLM:\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet&quot; `
  -Name EnableActiveProbing `
  -Type DWord `
  -Value 0
</code></pre>
<p>恢复时设置为 <code>1</code>：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">Set-ItemProperty `
  -Path &quot;HKLM:\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet&quot; `
  -Name EnableActiveProbing `
  -Type DWord `
  -Value 1
</code></pre><blockquote>
<p>修改注册表前建议备份。微软不建议把关闭 NCSI 主动探测当作长期修复方案，因为 Windows Update、Outlook 和其他应用可能依赖 NCSI 的联网状态判断。</p></blockquote>
<p>禁用后的效果是：</p><pre class="language-text lang-text"><code class="language-text lang-text">不再主动探测
  ↓
不再因为探测失败触发 /redirect
  ↓
自动弹窗消失
</code></pre>
<p>但底层问题可能仍然存在：</p><pre class="language-text lang-text"><code class="language-text lang-text">DNS 或 HTTP 链路仍不可信
  ↓
只是 Windows 不再通过 NCSI 暴露这个问题
</code></pre>
<p>所以这是一种<strong>临时止损措施</strong>，不是根治方案。</p><h2 id="">建议保留哪些证据</h2><p>如果再次遇到问题，建议在关闭页面前记录：</p><ul><li>发生时间和所在地区；</li><li>宽带运营商；</li><li>当前 IPv4 和 IPv6 DNS；</li><li><code>Resolve-DnsName</code> 的完整返回；</li><li><code>/redirect</code> 的 HTTP 状态和 <code>Location</code>；</li><li>恶意页面完整地址和截图；</li><li>同一网络下其他设备是否异常；</li><li>切换到手机流量后是否恢复。</li></ul><p>如果条件允许，可以使用 Wireshark 抓取：</p><pre class="language-text lang-text"><code class="language-text lang-text">dns || http
</code></pre>
<p>对于 IPv6 路由器通告，可使用：</p><pre class="language-text lang-text"><code class="language-text lang-text">icmpv6.type == 134
</code></pre>
<p>重点查看 Router Advertisement 中的 RDNSS 信息，它可以显示局域网设备从哪里获得 IPv6 DNS。</p><p>这些证据比事后单独的一张网页截图更有助于判断问题发生在终端、路由器、光猫还是运营商网络。</p><h2 id="">总结</h2><p>这次事件的核心不是“Windows 自己打开了博彩网站”，而是：</p><ol start="1"><li>Windows 使用 NCSI 主动检测互联网连接；</li><li>NCSI 会访问明文 HTTP 探测地址；</li><li>DNS 劫持让探测请求到达了错误服务器；</li><li>错误响应使 Windows 判断网络“需要操作”；</li><li>Windows 随后打开 <code>/redirect</code>；</li><li>被劫持的解析再次把浏览器导向恶意网站。</li></ol><p>解决问题时，应当分清三个层次：</p><ol start="1"><li><strong>同时修改 IPv4 和 IPv6 DNS，并启用系统级或路由器级 DoH，这是处理 DNS 劫持的核心；</strong></li><li><strong>检查路由器、光猫、Hosts 和代理，是排除本地篡改的必要步骤；</strong></li><li><strong>禁用 <code>EnableActiveProbing</code> 只能阻止系统自动探测和弹窗，不能修复底层劫持。</strong></li></ol><p>最后再次提醒：不要在陌生跳转页面输入任何信息，也不要下载页面推荐的软件。发现异常时，应优先断开页面、保存证据、修改 DNS，并向宽带运营商反馈。</p><h2 id="">参考资料</h2><ul><li><a href="https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/internet-explorer-edge-open-connect-corporate-public-network">Microsoft Learn：NCSI 主动探测、自动登录页与 MSN Portal</a></li><li><a href="https://learn.microsoft.com/en-us/windows-server/networking/ncsi/ncsi-overview">Microsoft Learn：Network Connectivity Status Indicator 概述</a></li><li><a href="https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/troubleshoot-ncsi-guidance">Microsoft Learn：NCSI 故障排查指南</a></li><li><a href="https://learn.microsoft.com/en-us/windows-server/networking/dns/doh-client-support">Microsoft Learn：Windows DNS over HTTPS 客户端支持</a></li><li><a href="https://learn.microsoft.com/en-us/windows-server/networking/dns/queries-lookups">Microsoft Learn：Windows DNS 查询与 Hosts 解析顺序</a></li><li><a href="https://alidns.com/">阿里公共 DNS</a></li><li><a href="https://developers.google.com/speed/public-dns">Google Public DNS</a></li></ul></div><p style="text-align:right"><a href="https://blog.chiran.top/posts/生活日常/DNS#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.chiran.top/posts/生活日常/DNS</link><guid isPermaLink="true">https://blog.chiran.top/posts/生活日常/DNS</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Sat, 25 Jul 2026 16:23:20 GMT</pubDate></item><item><title><![CDATA[MEC302]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.chiran.top/posts/大学生涯/mec302">https://blog.chiran.top/posts/大学生涯/mec302</a></blockquote><div><h2 id="0-">0. 期末复习范围总览</h2><p>Revision 课件明确说：<strong>Lectures 1–11 all relevant</strong>。也就是说，前面所有课件都可能考。<br/>但从 L12 给的 sample questions 来看，重点大概集中在这些类型：</p><table><thead><tr><th>Lecture</th><th>主题</th><th>常见考法</th></tr></thead><tbody><tr><td>L1</td><td>Embedded Systems / CPS</td><td>定义、结构、例子</td></tr><tr><td>L2</td><td>Continuous Dynamics</td><td>建模、微分方程、线性化、系统性质</td></tr><tr><td>L3</td><td>Discrete Dynamics</td><td>FSM、Mealy/Moore machine、状态图</td></tr><tr><td>L4</td><td>Sensors &amp; Actuators</td><td>选传感器、ADC/DAC、sensor metrics</td></tr><tr><td>L5</td><td>Embedded Processors</td><td>CPU、memory、bus、address calculation</td></tr><tr><td>L6</td><td>Memory Architecture</td><td>volatile/non-volatile、memory hierarchy</td></tr><tr><td>L7</td><td>Multitasking &amp; Scheduling</td><td>threads/processes、scheduler、preemptive/non-preemptive</td></tr><tr><td>L8</td><td>LTL / Model Checking</td><td>temporal operators、formal specification</td></tr><tr><td>L9</td><td>Interfacing</td><td>power、clock、reset、boot、interrupt/polling</td></tr><tr><td>L10</td><td>Serial Communication</td><td>serial/parallel、bit/baud、simplex/duplex、differential link</td></tr><tr><td>L11</td><td>Evaluation</td><td>evaluation/validation、multi-objective optimization、WCET、dependability</td></tr></tbody></table><hr/><h1 id="1-l1-embedded-systems--cyber-physical-systems">1. L1 Embedded Systems / Cyber-Physical Systems</h1><h2 id="11-embedded-system-">1.1 Embedded System 是什么？</h2><p><strong>Embedded system</strong> 是嵌入到更大产品或系统中的计算机系统，通常用于完成某个特定功能，而不是像 PC 一样作为通用计算机使用。</p><p>可以这样写：</p><blockquote><p>An embedded system is a computer system integrated into a larger device or product to perform dedicated functions. It usually consists of hardware, software, sensors, actuators, memory, and communication interfaces.</p></blockquote>
<p>关键词：</p><ul><li>dedicated function</li><li>integrated into larger system</li><li>hardware + software</li><li>real-time / resource-constrained</li><li>interacts with physical environment</li></ul><p>例子：</p><ul><li>washing machine controller</li><li>car engine control unit</li><li>drone flight controller</li><li>smart watch</li><li>robot controller</li><li>medical device</li></ul><hr/><h2 id="12-cyber-physical-system-cps">1.2 Cyber-Physical System, CPS</h2><p>CPS 是计算和物理过程紧密结合的系统。</p><p>可以这样写：</p><blockquote><p>A cyber-physical system integrates computation, communication, and physical processes. Sensors measure the physical world, the embedded computer processes the data, and actuators affect the physical system.</p></blockquote>
<p>典型结构：</p><pre class="language-text lang-text"><code class="language-text lang-text">Physical world
   ↓ sensors
Embedded computer / controller
   ↓ actuators
Physical world changes
</code></pre>
<p>例子：</p><ul><li>self-driving car</li><li>quadrotor drone</li><li>robot arm</li><li>smart grid</li><li>industrial automation system</li></ul><hr/><h2 id="13-embedded-system-">1.3 Embedded System 的核心组成</h2><p>一般包括：</p><pre class="language-text lang-text"><code class="language-text lang-text">CPU / processor
Memory
I/O interface
Sensors
Actuators
Power source
Clock
Communication interface
Software / firmware
</code></pre>
<p>考试如果问 “structure of embedded computer system”，可以画：</p><pre class="language-text lang-text"><code class="language-text lang-text">        Sensors
           ↓
Input interface / ADC
           ↓
CPU / Microcontroller ↔ Memory
           ↓
Output interface / DAC / PWM
           ↓
        Actuators
</code></pre>
<hr/><h1 id="2-l2-continuous-dynamics">2. L2 Continuous Dynamics</h1><p>Revision 课件特别强调：</p><blockquote><p>Many embedded systems are also dynamic physical systems.</p></blockquote>
<p>也就是说，机器人、无人机、车辆、机械系统不只是计算机系统，还是有物理动态行为的系统。</p><hr/><h2 id="21-continuous-dynamics-">2.1 Continuous Dynamics 是什么？</h2><p>Continuous dynamics 用微分方程描述系统随时间连续变化的行为。</p><p>核心公式：</p><p>$\dot{x}(t)=f(x(t),u,t)$
其中：</p><ul><li>(x(t))：state，系统状态</li><li>($\dot{x}(t)$)：state derivative，状态变化率</li><li>(u)：input/control input</li><li>(t)：time</li></ul><p>可以这样写：</p><blockquote><p>Continuous dynamics describes the evolution of a physical system over continuous time using differential or integral equations. It is suitable for mechanical systems, electrical circuits, chemical processes, biological systems, etc.</p></blockquote>
<hr/><h2 id="22-dynamic-model-">2.2 Dynamic Model 的常见来源</h2><p>主要是物理定律，例如：</p><ul><li>Newton’s second law:</li></ul><p>$F = ma = m\ddot{x}$</p><ul><li>rotational dynamics:</li></ul><p>$\tau = I\alpha$</p><ul><li>electrical circuits:</li></ul><p>$V = IR$</p><p>如果题目让你 derive dynamics，一般步骤是：</p><ol start="1"><li>Draw free-body diagram.</li><li>Define coordinates and positive directions.</li><li>List all forces/torques.</li><li>Apply Newton’s law.</li><li>Rearrange into differential equations.</li><li>Define state variables if needed.</li></ol><hr/><h2 id="23-model-properties">2.3 系统性质：Model Properties</h2><p>Revision 课件说，为了设计有效控制，系统最好满足：</p><blockquote><p>Causal, Not Memoryless, Linear, Time Invariant and Stable</p></blockquote>
<p>这几个必须会解释。</p><h3 id="1-causality">1. Causality</h3><p><strong>Causal system</strong>：输出只依赖当前和过去输入，不依赖未来输入。</p><p>可以写：</p><blockquote><p>A system is causal if its output at the current time depends only on current and past inputs, not future inputs.</p></blockquote>
<p>例如真实物理系统一般都是 causal，因为未来还没发生。</p><hr/><h3 id="2-memory--memoryless">2. Memory / Memoryless</h3><p><strong>Memoryless system</strong>：输出只依赖当前输入。</p><p>例如：</p><p>$y(t)=2x(t)$</p><p>是 memoryless。</p><p><strong>Not memoryless system</strong>：输出依赖过去状态或过去输入。</p><p>例如：</p><p>$\dot{x}(t)=f(x(t),u(t))$</p><p>这类动态系统通常不是 memoryless，因为当前状态包含过去信息。</p><p>可以写：</p><blockquote><p>A dynamic physical system is usually not memoryless because its current output depends on the current state, and the state is a summary of past behavior.</p></blockquote>
<hr/><h3 id="3-linearity">3. Linearity</h3><p>线性系统满足 superposition：</p><p>$f(ax<em>1+bx</em>2)=af(x<em>1)+bf(x</em>2)$</p><p>简单写法：</p><blockquote><p>A system is linear if the output is proportional to the input and the superposition principle holds.</p></blockquote>
<p>非线性例子：</p><p>$y=x^2$</p><p>$\sin\theta$</p><hr/><h3 id="4-time-invariance">4. Time Invariance</h3><p>如果系统行为不随时间改变，则是 time-invariant。</p><p>可以写：</p><blockquote><p>A system is time-invariant if its behavior does not explicitly depend on time. The same input applied later produces the same output shifted in time.</p></blockquote>
<p>如果方程中显式出现 (t)，例如：</p><p>$\dot{x}=tx+u$</p><p>则通常是 time-varying。</p><hr/><h3 id="5-stability">5. Stability</h3><p>课件提到 BIBO stability：</p><blockquote><p>Bounded input leads to bounded output.</p></blockquote>
<p>即：</p><p>如果输入有限，输出也有限，那么系统稳定。</p><p>可以写：</p><blockquote><p>A system is stable if bounded input signals always lead to bounded output signals.</p></blockquote>
<hr/><h2 id="24-linearization-">2.4 Linearization 线性化</h2><p>Revision 课件特别强调：</p><ul><li>Jacobian linearization</li><li>small angle linearization</li><li>assignment 1 里的 cart-pole / drone / robot locomotion 都和这个思想有关</li></ul><h3 id="1-">1. 为什么要线性化？</h3><p>真实物理系统通常是 nonlinear 的，例如：</p><p>$\sin\theta,\quad \cos\theta,\quad x^2,\quad \dot{x}^2$</p><p>但很多控制方法，例如 LQR、linear controller，需要线性模型。</p><p>所以在某个 operating point 附近，把 nonlinear system 近似成 linear system。</p><hr/><h3 id="2-small-angle-approximation">2. Small angle approximation</h3><p>当 ($\theta$) 很小时：</p><p>$\sin\theta \approx \theta$</p><p>$\cos\theta \approx 1$</p><p>$\tan\theta \approx \theta$</p><p>这个在 assignment 1 cart-pole 里面很重要。</p><hr/><h3 id="3-jacobian-linearization">3. Jacobian linearization</h3><p>非线性系统：</p><p>$\dot{x}=f(x,u)$</p><p>在 equilibrium point ((x<em>0,u</em>0)) 附近线性化：</p><p>$\delta \dot{x}=A\delta x+B\delta u$</p><p>其中：</p><p>$A=\frac{\partial f}{\partial x}\bigg|<em>{x</em>0,u_0}$</p><p>$B=\frac{\partial f}{\partial u}\bigg|<em>{x</em>0,u_0}$</p><p>可以这样解释：</p><blockquote><p>Jacobian linearization approximates a nonlinear system by its first-order Taylor expansion around an operating point.</p></blockquote>
<hr/><h2 id="25-draw-fbd-and-derive-dynamics">2.5 典型题：Draw FBD and derive dynamics</h2><p>答题框架：</p><pre class="language-text lang-text"><code class="language-text lang-text">1. Define coordinates.
2. Draw all forces.
3. Apply Newton’s second law.
4. Write equations of motion.
5. Rearrange into state-space form if required.
</code></pre>
<p>如果是质量-弹簧-阻尼系统：</p><pre class="language-text lang-text"><code class="language-text lang-text">forces:
spring force = -kx
damping force = -cẋ
external force = u
</code></pre>
<p>动力学：</p><p>$m\ddot{x}=u-c\dot{x}-kx$</p><p>整理：</p><p>$m\ddot{x}+c\dot{x}+kx=u$</p><p>state 选：</p><p>$x<em>1=x,\quad x</em>2=\dot{x}$</p><p>则：</p><p>$\dot{x}<em>1=x</em>2$
$\dot{x}<em>2=\frac{1}{m}(u-cx</em>2-kx_1)$
---</p><h1 id="3-l3-discrete-dynamics">3. L3 Discrete Dynamics</h1><h2 id="31-discrete-dynamics-">3.1 Discrete Dynamics 是什么？</h2><p>Discrete dynamics 不是连续时间变化，而是状态按离散 step 或 event 改变。</p><p>可以写：</p><blockquote><p>Discrete dynamics describes systems whose states evolve through discrete transitions rather than continuously in time. Such systems are commonly modeled by finite-state machines.</p></blockquote>
<p>例子：</p><ul><li>vending machine</li><li>traffic light</li><li>elevator</li><li>digital lock</li><li>parking lot counter</li></ul><hr/><h2 id="32-state-">3.2 State 的定义</h2><p>课件里强调：</p><blockquote><p>State is a summary of the past.</p></blockquote>
<p>可以写：</p><blockquote><p>The state of a system represents its condition at a certain moment. It determines how the system reacts to future inputs and summarizes the relevant past history.</p></blockquote>
<p>例如 parking lot counter 的 state 可以是当前车位数量：</p><p>$states={0,1,2,\dots,M}$</p><h2 id="33-fsm-">3.3 FSM 五元组</h2><p>FSM 通常可以写成：</p><p>$FSM=(S,I,O,\delta,\lambda)$
其中：</p><ul><li>(S)：states</li><li>(I)：inputs</li><li>(O)：outputs</li><li>($\delta$)：transition function</li><li>($\lambda$)：output function</li></ul><p>更完整可以加 initial state：</p><p>$FSM=(S,I,O,\delta,\lambda,s_0)$</p><h2 id="34-fsm-">3.4 FSM 图中常见元素</h2><table><thead><tr><th>元素</th><th>含义</th></tr></thead><tbody><tr><td>state</td><td>系统当前状态</td></tr><tr><td>transition</td><td>状态转移</td></tr><tr><td>guard</td><td>判断转移是否发生的条件</td></tr><tr><td>action</td><td>转移时产生的输出或执行动作</td></tr><tr><td>initial state</td><td>初始状态</td></tr><tr><td>default transition</td><td>没有其他 transition enabled 时发生</td></tr></tbody></table><hr/><h2 id="35-mealy-machine-vs-moore-machine">3.5 Mealy Machine vs Moore Machine</h2><p>Revision 课件 sample question 明确说可能让你设计 Mealy machine。</p><h3 id="mealy-machine">Mealy Machine</h3><p>Mealy machine 的输出在 transition 上产生。</p><p>特点：</p><blockquote><p>Output depends on current state and input.</p></blockquote>
<p>可以写：</p><p>$output = \lambda(state,input)$
图上一般写：</p><pre class="language-text lang-text"><code class="language-text lang-text">input / output
</code></pre>
<p>例如：</p><pre class="language-text lang-text"><code class="language-text lang-text">press / light_on
</code></pre>
<hr/><h3 id="moore-machine">Moore Machine</h3><p>Moore machine 的输出由 state 决定。</p><p>特点：</p><blockquote><p>Output depends only on the current state.</p></blockquote>
<p>可以写：</p><p>$output = \lambda(state)$
图上通常在 state 里面写 output。</p><hr/><h3 id="">对比表</h3><table><thead><tr><th>项目</th><th>Mealy</th><th>Moore</th></tr></thead><tbody><tr><td>输出取决于</td><td>state + input</td><td>state only</td></tr><tr><td>输出位置</td><td>transition 上</td><td>state 内</td></tr><tr><td>响应速度</td><td>通常更快</td><td>通常慢一个状态</td></tr><tr><td>状态数</td><td>可能更少</td><td>可能更多</td></tr><tr><td>是否 strict causal</td><td>不一定</td><td>通常 strict causal</td></tr></tbody></table><p>考试如果让画 Mealy machine，要记得把 output 写在 arrow 上，不要只写在 state 里。</p><hr/><h2 id="36-hybrid-system">3.6 Hybrid System</h2><p>Hybrid system 同时有：</p><ul><li>continuous dynamics</li><li>discrete dynamics</li></ul><p>例如：</p><ul><li>thermostat：温度连续变化，但 heater on/off 是离散状态</li><li>bouncing ball：位置连续变化，碰撞是离散事件</li><li>robot：机械运动连续，任务阶段离散</li></ul><p>可以写：</p><blockquote><p>A hybrid system combines continuous dynamics with discrete state transitions. It is useful for modeling cyber-physical systems.</p></blockquote>
<hr/><h1 id="4-l8-linear-temporal-logic-and-model-checking">4. L8 Linear Temporal Logic and Model Checking</h1><p>Revision 里把 L8 放在 L3 后面，因为它和 FSM、formal specification 关系很强。</p><hr/><h2 id="41-ltl-">4.1 LTL 是什么？</h2><p>LTL = Linear Temporal Logic。</p><p>它不仅能表达普通逻辑，还能表达时间顺序上的性质。</p><p>可以写：</p><blockquote><p>Linear Temporal Logic is a formal language used to specify temporal properties of system behavior over traces. It extends propositional logic with temporal operators such as always, eventually, next, and until.</p></blockquote>
<hr/><h2 id="42-">4.2 普通逻辑连接词</h2><table><thead><tr><th>符号</th><th>含义</th></tr></thead><tbody><tr><td>(\neg p)</td><td>not p</td></tr><tr><td>(p \land q)</td><td>p and q</td></tr><tr><td>(p \lor q)</td><td>p or q</td></tr><tr><td>(p \Rightarrow q)</td><td>if p then q</td></tr></tbody></table><hr/><h2 id="43-temporal-operators">4.3 Temporal Operators</h2><p>常见 LTL temporal operators：</p><table><thead><tr><th>符号</th><th>名称</th><th>含义</th></tr></thead><tbody><tr><td>(X p)</td><td>next</td><td>下一步 p 成立</td></tr><tr><td>(F p)</td><td>eventually / finally</td><td>未来某时刻 p 成立</td></tr><tr><td>(G p)</td><td>globally / always</td><td>从现在起 p 一直成立</td></tr><tr><td>(p U q)</td><td>until</td><td>p 一直成立直到 q 成立</td></tr></tbody></table><hr/><h2 id="44--ltl">4.4 常见英文转 LTL</h2><h3 id="1-always-p">1. Always p</h3><p>“p is always true”</p><p>$G p$</p><h3 id="2-eventually-p">2. Eventually p</h3><p>“p will eventually happen”</p><p>$F p$</p><h3 id="3-whenever-p-happens-q-eventually-happens">3. Whenever p happens, q eventually happens</h3><p>“每当 p 发生，之后 q 最终会发生”</p><p>$G(p \Rightarrow Fq)$
这是非常常考的模式。</p>
<h3 id="4-p-never-happens">4. p never happens</h3><p>“p 永远不会发生”</p><p>$G(\neg p)$</p><h3 id="5-p-must-hold-until-q-happens">5. p must hold until q happens</h3><p>“p 一直成立直到 q 发生”</p><p>$p U q$</p><h3 id="6-if-p-then-q-in-next-step">6. If p, then q in next step</h3><p>$G(p \Rightarrow Xq)$</p><h2 id="45-">4.5 课件里的例子</h2><h3 id="-1">例 1</h3><blockquote><p>Whenever the door is locked, it will not open until someone unlocks it.</p></blockquote>
<p>定义：</p><ul><li>(p)：door is locked</li><li>(q)：door is open</li><li>(r)：someone unlocks it</li></ul><p>可以写成：</p><p>$G(p \Rightarrow (\neg q \ U \ r))$
意思是：只要门锁着，在解锁之前门不能开。</p>
<h3 id="-2">例 2</h3><blockquote><p>Whenever you press ctrl-C, you will get a command line prompt.</p></blockquote>
<p>定义：</p><ul><li>(p)：press ctrl-C</li><li>(q)：command line prompt appears</li></ul><p>公式：</p><p>$G(p \Rightarrow Fq)$</p><h2 id="46-safety-vs-liveness">4.6 Safety vs Liveness</h2><p>很重要。</p><h3 id="safety-property">Safety property</h3><p>意思是：“bad thing never happens”。</p><p>例如：</p><p>$G(\neg collision)$
$G(\neg(error))$
可以写：</p><blockquote><p>A safety property states that something bad should never happen.</p></blockquote>
<hr/><h3 id="liveness-property">Liveness property</h3><p>意思是：“good thing eventually happens”。</p><p>例如：</p><p>$G(request \Rightarrow F response)$
可以写：</p><blockquote><p>A liveness property states that something good will eventually happen.</p></blockquote>
<hr/><h2 id="47-model-checking">4.7 Model Checking</h2><p>Model checking 是用数学严谨方式验证系统模型是否满足 specification。</p><p>可以写：</p><blockquote><p>Model checking is a formal verification technique that checks whether a system model satisfies a given temporal logic property.</p></blockquote>
<p>基本流程：</p><pre class="language-text lang-text"><code class="language-text lang-text">System model S
+ Environment model E
= Closed model M

Check whether M satisfies property φ
</code></pre>
<p>符号：</p><p>$M \models \varphi$
表示 model (M) satisfies property (\varphi)。</p><hr/><h1 id="5-l4-sensors-and-actuators">5. L4 Sensors and Actuators</h1><h2 id="51-sensor--actuator">5.1 Sensor 和 Actuator</h2><h3 id="sensor">Sensor</h3><blockquote><p>A sensor is a device that measures a physical quantity and converts it into a signal.</p></blockquote>
<p>例子：</p><ul><li>temperature sensor</li><li>gyroscope</li><li>accelerometer</li><li>camera</li><li>LIDAR</li><li>microphone</li><li>pressure sensor</li></ul><h3 id="actuator">Actuator</h3><blockquote><p>An actuator is a device that changes or controls a physical quantity in the environment.</p></blockquote>
<p>例子：</p><ul><li>motor</li><li>servo</li><li>LED</li><li>speaker</li><li>heater</li><li>valve</li></ul><hr/><h2 id="52-signal-chain">5.2 Signal Chain</h2><p>典型 sensor chain：</p><pre class="language-text lang-text"><code class="language-text lang-text">Physical quantity
→ Sensor
→ Signal conditioning
→ ADC
→ Digital processor
</code></pre>
<p>典型 actuator chain：</p><pre class="language-text lang-text"><code class="language-text lang-text">Digital processor
→ DAC / PWM
→ Driver
→ Actuator
→ Physical action
</code></pre>
<hr/><h2 id="53-adc--dac">5.3 ADC 和 DAC</h2><h3 id="adc">ADC</h3><p>Analog-to-Digital Converter。</p><blockquote><p>ADC converts a continuous analog signal into a digital binary representation.</p></blockquote>
<p>用于 sensor input。</p><hr/><h3 id="dac">DAC</h3><p>Digital-to-Analog Converter。</p><blockquote><p>DAC converts digital values into analog signals.</p></blockquote>
<p>用于 actuator output。</p><hr/><h2 id="54-active-vs-passive-sensors">5.4 Active vs Passive Sensors</h2><h3 id="active-sensor">Active sensor</h3><p>主动产生信号。</p><p>例子：</p><ul><li>thermocouple</li><li>piezoelectric sensor</li></ul><p>可以写：</p><blockquote><p>Active sensors generate an electrical signal in response to the measured physical quantity.</p></blockquote>
<hr/><h3 id="passive-sensor">Passive sensor</h3><p>自身不产生信号，而是改变电阻、电容、电感等 passive quantity。</p><p>例子：</p><ul><li>capacitive sensor</li><li>resistive sensor</li></ul><p>可以写：</p><blockquote><p>Passive sensors produce changes in passive electrical quantities and usually require an external excitation source.</p></blockquote>
<hr/><h2 id="55-sensor-figures-of-merit">5.5 Sensor Figures of Merit</h2><p>这一部分很容易考定义。</p><table><thead><tr><th>指标</th><th>含义</th></tr></thead><tbody><tr><td>Sensitivity</td><td>输出对输入变化的斜率</td></tr><tr><td>Range</td><td>可测量的最大最小范围</td></tr><tr><td>Precision</td><td>重复测量的一致性</td></tr><tr><td>Accuracy</td><td>测量值接近真实值的程度</td></tr><tr><td>Resolution</td><td>能检测的最小输入变化</td></tr><tr><td>Offset</td><td>输入为 0 时的非零输出</td></tr><tr><td>Linearity</td><td>输入输出关系接近直线的程度</td></tr><tr><td>Hysteresis</td><td>输入增加和减少时输出路径不同</td></tr><tr><td>Response time</td><td>输出响应输入变化所需时间</td></tr></tbody></table><hr/><h2 id="56-accuracy-vs-precision">5.6 Accuracy vs Precision</h2><p>这个很容易混。</p><h3 id="accuracy">Accuracy</h3><p>接近真实值。</p><h3 id="precision">Precision</h3><p>重复测量是否集中。</p><p>可以这样写：</p><blockquote><p>Accuracy describes closeness to the true value, while precision describes repeatability of measurements.</p></blockquote>
<p>例子：</p><ul><li>高 accuracy 高 precision：测量值都接近真实值且集中</li><li>低 accuracy 高 precision：测量值很集中，但整体偏离真实值</li><li>高 accuracy 低 precision：平均值接近真实值，但数据分散</li><li>低 accuracy 低 precision：又偏又散</li></ul><hr/><h2 id="57-sensor-model">5.7 Sensor Model</h2><p>线性 sensor：</p><p>$f(x(t))=ax(t)$
仿射 sensor：</p><p>$f(x(t))=ax(t)+b$
其中：</p><ul><li>(a)：sensitivity / gain</li><li>(b)：offset</li></ul><p>现实中 sensor 可能会：</p><ul><li>saturate</li><li>have noise</li><li>have hysteresis</li><li>have nonlinear distortion</li></ul><hr/><h2 id="58-sampling-and-nyquist-rate">5.8 Sampling and Nyquist Rate</h2><p>Uniform sampling：固定时间间隔采样。</p><p>如果信号最高频率是 (f_{max})，采样频率至少需要：</p><p>$f<em>s &gt; 2f</em>{max}$
或者理论上：</p><p>$f<em>s \ge 2f</em>{max}$
这个叫 Nyquist rate。</p><p>可以写：</p><blockquote><p>To avoid aliasing, the sampling frequency must be at least twice the maximum frequency component of the signal.</p></blockquote>
<p>Aliasing：</p><blockquote><p>Aliasing occurs when a signal is sampled too slowly, causing high-frequency components to appear as lower-frequency components.</p></blockquote>
<hr/><h2 id="59-snr">5.9 SNR</h2><p>Signal-to-noise ratio：</p><p>$SNR=\frac{signal\ power}{noise\ power}$
dB 形式：</p><p>$SNR<em>{dB}=10\log</em>{10}\frac{P<em>s}{P</em>n}$
如果是 amplitude ratio：</p><p>$SNR<em>{dB}=20\log</em>{10}\frac{A<em>s}{A</em>n}$</p><h2 id="510-">5.10 传感器选择题答题框架</h2><p>如果题目问：</p><blockquote><p>Select appropriate sensors for a dynamical system.</p></blockquote>
<p>可以按这个框架答：</p><pre class="language-text lang-text"><code class="language-text lang-text">1. Identify what physical quantities need to be measured.
2. Choose sensors that can measure these quantities.
3. Consider range, accuracy, precision, resolution, response time, and noise.
4. Consider environmental conditions.
5. Consider cost, size, power consumption, and interface compatibility.
</code></pre>
<p>例子：无人机</p><table><thead><tr><th>需求</th><th>传感器</th></tr></thead><tbody><tr><td>姿态角速度</td><td>gyroscope</td></tr><tr><td>加速度</td><td>accelerometer</td></tr><tr><td>航向</td><td>magnetometer</td></tr><tr><td>高度</td><td>barometer / lidar</td></tr><tr><td>位置</td><td>GPS / camera / optical flow</td></tr><tr><td>障碍物</td><td>ultrasonic / lidar / camera</td></tr></tbody></table><hr/><h1 id="6-l5-embedded-processors">6. L5 Embedded Processors</h1><h2 id="61-microcontroller-">6.1 Microcontroller 基本组成</h2><p>课件说 minimal set of components：</p><pre class="language-text lang-text"><code class="language-text lang-text">CPU
System memory
I/O interface
</code></pre>
<p>再加 system buses：</p><pre class="language-text lang-text"><code class="language-text lang-text">Address bus
Data bus
Control bus
</code></pre>
<hr/><h2 id="62-cpu">6.2 CPU</h2><p>CPU 的作用：</p><blockquote><p>The CPU retrieves instructions from program memory, decodes them, and executes them.</p></blockquote>
<p>也就是：</p><pre class="language-text lang-text"><code class="language-text lang-text">fetch → decode → execute
</code></pre>
<p>CPU 内部常见部件：</p><ul><li>ALU</li><li>registers</li><li>control unit</li><li>program counter</li><li>instruction decoder</li></ul><hr/><h2 id="63-system-memory">6.3 System Memory</h2><p>两类：</p><h3 id="program-memory">Program memory</h3><p>存程序指令。</p><h3 id="data-memory">Data memory</h3><p>存运行时数据。</p><p>可以写：</p><blockquote><p>Program memory stores executable instructions, while data memory stores variables and intermediate data used by the CPU.</p></blockquote>
<hr/><h2 id="64-io-interface">6.4 I/O Interface</h2><p>I/O interface 允许 CPU 和外部设备通信，例如：</p><ul><li>sensors</li><li>actuators</li><li>communication modules</li><li>timers</li><li>ADC/DAC</li><li>GPIO</li></ul><p>也叫 peripheral subsystem。</p><hr/><h2 id="65-system-bus">6.5 System Bus</h2><h3 id="address-bus">Address Bus</h3><p>用于选择 memory location 或 peripheral register。</p><p>如果 address bus 有 (m) bits，则最多可访问：</p><p>$2^m$
个 memory locations。</p><p>地址范围：</p><p>$0x000...0 \quad \text{to} \quad 2^m-1$
例子：</p><p>如果 address bus 是 12 bits：</p><p>$2^{12}=4096$
地址范围：</p><p>$0x000 \text{ to } 0xFFF$
如果 address bus 是 22 bits：</p><p>$2^{22}=4194304$
因为 22 bits = 5.5 hex digits，所以通常写成 6 位 hex：</p><p>$0x000000 \text{ to } 0x3FFFFF$</p><h3 id="data-bus">Data Bus</h3><p>data bus 传输数据和指令。</p><p>如果 data bus 是 8-bit，一次最多传 1 byte。</p><p>如果要传 16-bit 数据，需要两次 8-bit transfer。</p><p>可以写：</p><blockquote><p>The width of the data bus determines the maximum amount of data transferred in one bus transaction.</p></blockquote>
<hr/><h3 id="control-bus">Control Bus</h3><p>control bus 传控制信号，例如：</p><ul><li>read/write</li><li>clock</li><li>interrupt</li><li>reset</li><li>enable</li></ul><hr/><h2 id="66-mpu-vs-mcu">6.6 MPU vs MCU</h2><h3 id="mpu-microprocessor-unit">MPU, Microprocessor Unit</h3><p>只有 CPU 核心，其他 memory、I/O 通常外接。</p><p>常见于 PC 或复杂计算系统。</p><h3 id="mcu-microcontroller-unit">MCU, Microcontroller Unit</h3><p>CPU、memory、I/O 集成在一个芯片中。</p><p>可以写：</p><blockquote><p>A microcontroller integrates CPU, memory, and I/O peripherals on a single chip, while a microprocessor usually requires external memory and peripherals.</p></blockquote>
<hr/><h2 id="67-risc-vs-cisc">6.7 RISC vs CISC</h2><h3 id="risc">RISC</h3><p>Reduced Instruction Set Computing。</p><p>特点：</p><ul><li>指令简单</li><li>指令数量少</li><li>通常 fixed length</li><li>适合 pipeline</li><li>load/store architecture</li></ul><p>例子：</p><ul><li>ARM</li><li>RISC-V</li></ul><h3 id="cisc">CISC</h3><p>Complex Instruction Set Computing。</p><p>特点：</p><ul><li>指令复杂</li><li>指令数量多</li><li>一条指令可能完成复杂操作</li><li>硬件译码复杂</li></ul><p>例子：</p><ul><li>x86</li></ul><hr/><h2 id="68-parallelism-vs-concurrency">6.8 Parallelism vs Concurrency</h2><p>Revision 课件专门提示：</p><blockquote><p>Parallelism (L5) vs concurrency (L7)</p></blockquote>
<h3 id="parallelism">Parallelism</h3><p>真的同时执行。</p><p>需要多个 core 或多个 processing units。</p><p>可以写：</p><blockquote><p>Parallelism means multiple tasks are executed at the same time, usually on multiple processors or cores.</p></blockquote>
<hr/><h3 id="concurrency">Concurrency</h3><p>多个任务逻辑上同时进行，但不一定真的同时执行。</p><p>单核 CPU 也可以通过 scheduling 实现 concurrency。</p><p>可以写：</p><blockquote><p>Concurrency means multiple tasks make progress over overlapping time periods, but they may be interleaved on a single processor.</p></blockquote>
<hr/><h1 id="7-l6-memory-architecture">7. L6 Memory Architecture</h1><h2 id="71-volatile-vs-non-volatile-memory">7.1 Volatile vs Non-volatile Memory</h2><p>Revision sample question 明确提到：</p><blockquote><p>Explain the difference between volatile and non-volatile memory.</p></blockquote>
<h3 id="volatile-memory">Volatile memory</h3><p>断电后数据丢失。</p><p>例子：</p><ul><li>RAM</li><li>SRAM</li><li>DRAM</li><li>cache</li></ul><p>可以写：</p><blockquote><p>Volatile memory loses its stored data when power is removed.</p></blockquote>
<hr/><h3 id="non-volatile-memory">Non-volatile memory</h3><p>断电后数据仍保留。</p><p>例子：</p><ul><li>ROM</li><li>Flash</li><li>EEPROM</li><li>SSD</li></ul><p>可以写：</p><blockquote><p>Non-volatile memory retains stored data even when power is removed.</p></blockquote>
<hr/><h2 id="72-ram-vs-rom">7.2 RAM vs ROM</h2><table><thead><tr><th> 类型          </th><th> 用途                           </th></tr></thead><tbody><tr><td> RAM         </td><td> 存运行时变量、stack、heap            </td></tr><tr><td> ROM / Flash </td><td> 存程序代码、firmware、constant data </td></tr></tbody></table><hr/><h2 id="73-sram-vs-dram">7.3 SRAM vs DRAM</h2><table><thead><tr><th>项目</th><th>SRAM</th><th>DRAM</th></tr></thead><tbody><tr><td>存储方式</td><td>flip-flop</td><td>capacitor</td></tr><tr><td>是否需要 refresh</td><td>不需要</td><td>需要</td></tr><tr><td>速度</td><td>快</td><td>较慢</td></tr><tr><td>成本</td><td>高</td><td>低</td></tr><tr><td>密度</td><td>低</td><td>高</td></tr><tr><td>用途</td><td>cache</td><td>main memory</td></tr></tbody></table><hr/><h2 id="74-memory-hierarchy">7.4 Memory Hierarchy</h2><p>一般从快到慢：</p><pre class="language-text lang-text"><code class="language-text lang-text">Registers
Cache
SRAM
DRAM
Flash / ROM
External storage
</code></pre>
<p>越靠近 CPU：</p><ul><li>faster</li><li>smaller</li><li>more expensive per bit</li></ul><p>越远离 CPU：</p><ul><li>slower</li><li>larger</li><li>cheaper per bit</li></ul><hr/><h2 id="75-cache">7.5 Cache</h2><p>Cache 是 CPU 和 main memory 之间的高速小容量存储器。</p><p>作用：</p><blockquote><p>Cache stores recently or frequently used data to reduce average memory access time.</p></blockquote>
<p>核心思想：</p><ul><li>temporal locality：最近访问过的数据可能很快再次访问</li><li>spatial locality：访问某地址后，附近地址也可能被访问</li></ul><hr/><h2 id="76-memory-mapped-io">7.6 Memory-mapped I/O</h2><p>在 embedded system 中，peripheral register 可以映射到 memory address。</p><p>CPU 通过读写特定地址来控制外设。</p><p>可以写：</p><blockquote><p>In memory-mapped I/O, peripheral registers are assigned addresses in the same address space as memory, so the CPU can access peripherals using normal load/store instructions.</p></blockquote>
<hr/><h1 id="8-l7-multitasking-and-scheduling">8. L7 Multitasking and Scheduling</h1><p>Revision 课件强调：</p><blockquote><p>How to schedule concurrent systems on a single core?</p></blockquote>
<hr/><h2 id="81-task--thread--process">8.1 Task / Thread / Process</h2><h3 id="task">Task</h3><p>一个需要执行的工作单元。</p><p>例如：</p><ul><li>read sensor</li><li>update controller</li><li>send communication packet</li><li>log data</li></ul><hr/><h3 id="thread">Thread</h3><p>同一 process 内的轻量执行流，共享 memory。</p><p>优点：</p><ul><li>communication easy</li><li>lower overhead</li></ul><p>缺点：</p><ul><li>shared memory causes race condition</li><li>synchronization needed</li></ul><hr/><h3 id="process">Process</h3><p>独立地址空间的执行实体。</p><p>优点：</p><ul><li>isolation</li><li>safer</li></ul><p>缺点：</p><ul><li>communication more expensive</li><li>context switch overhead larger</li></ul><hr/><h2 id="82-threads-">8.2 Threads 的挑战</h2><p>线程共享 memory，所以可能出现：</p><ul><li>race condition</li><li>deadlock</li><li>priority inversion</li><li>inconsistent data</li><li>synchronization overhead</li></ul><h3 id="race-condition">Race condition</h3><p>多个线程同时访问共享数据，结果取决于执行顺序。</p><p>解决：</p><ul><li>mutex</li><li>semaphore</li><li>lock</li><li>critical section</li></ul><hr/><h2 id="83-message-passing">8.3 Message Passing</h2><p>进程之间可以通过 message passing 通信。</p><p>优点：</p><ul><li>no shared memory race condition</li><li>modular</li><li>safer</li></ul><p>缺点：</p><ul><li>overhead higher</li><li>message delay</li><li>communication design more complex</li></ul><hr/><h2 id="84-scheduler">8.4 Scheduler</h2><p>scheduler 决定哪个 task 在什么时候运行。</p><p>常见目标：</p><ul><li>meet deadlines</li><li>minimize response time</li><li>maximize CPU utilization</li><li>ensure fairness</li><li>avoid starvation</li></ul><hr/><h2 id="85-static-vs-dynamic-scheduling">8.5 Static vs Dynamic Scheduling</h2><h3 id="fully-static-scheduler">Fully-static scheduler</h3><p>所有任务执行顺序在设计时确定。</p><p>优点：</p><ul><li>predictable</li><li>simple</li><li>suitable for periodic tasks</li></ul><p>缺点：</p><ul><li>inflexible</li><li>difficult to handle unexpected events</li></ul><hr/><h3 id="dynamic-scheduler">Dynamic scheduler</h3><p>运行时根据优先级、deadline 等决定任务顺序。</p><p>优点：</p><ul><li>flexible</li><li>can respond to events</li></ul><p>缺点：</p><ul><li>runtime overhead</li><li>harder to verify</li></ul><hr/><h2 id="86-preemptive-vs-non-preemptive">8.6 Preemptive vs Non-preemptive</h2><h3 id="non-preemptive">Non-preemptive</h3><p>一个 task 一旦开始执行，就运行到完成或主动让出 CPU。</p><p>优点：</p><ul><li>simple</li><li>lower context-switch overhead</li><li>no mid-task interruption</li></ul><p>缺点：</p><ul><li>high-priority task 可能需要等待</li><li>response time 可能较长</li></ul><hr/><h3 id="preemptive">Preemptive</h3><p>高优先级 task 到来时，可以打断低优先级 task。</p><p>优点：</p><ul><li>better response for urgent tasks</li><li>suitable for real-time systems</li></ul><p>缺点：</p><ul><li>context switch overhead</li><li>synchronization more difficult</li><li>possible race condition</li></ul><hr/><h2 id="87-priority-based-scheduling">8.7 Priority-based Scheduling</h2><p>每个 task 有 priority。</p><p>scheduler 选择最高优先级 ready task 执行。</p><p>如果是 non-preemptive priority scheduler：</p><ul><li>当前 task 不会被打断</li><li>当前 task 完成后，选择 ready queue 里 priority 最高的 task</li></ul><p>如果是 preemptive priority scheduler：</p><ul><li>高优先级 task 一 ready，就可以打断低优先级 task</li></ul><hr/><h2 id="88--scheduling-">8.8 画 Scheduling 图的方法</h2><p>如果题目给几个 task：</p><table><thead><tr><th>Task</th><th style="text-align:right">Arrival time</th><th style="text-align:right">Execution time</th><th style="text-align:right">Priority</th></tr></thead><tbody><tr><td>T1</td><td style="text-align:right">0</td><td style="text-align:right">3</td><td style="text-align:right">2</td></tr><tr><td>T2</td><td style="text-align:right">1</td><td style="text-align:right">2</td><td style="text-align:right">1</td></tr><tr><td>T3</td><td style="text-align:right">2</td><td style="text-align:right">1</td><td style="text-align:right">3</td></tr></tbody></table><p>假设 priority 数字越小优先级越高。</p><h3 id="non-preemptive">Non-preemptive</h3><pre class="language-text lang-text"><code class="language-text lang-text">t=0: T1 arrives, run T1
t=1: T2 arrives, but T1 continues
t=2: T3 arrives, but T1 continues
t=3: T1 finishes, choose highest priority ready task T2
t=5: T2 finishes, run T3
t=6: finish
</code></pre>
<p>Gantt chart：</p><pre class="language-text lang-text"><code class="language-text lang-text">0     3     5   6
| T1  | T2  |T3 |
</code></pre>
<h3 id="preemptive">Preemptive</h3><pre class="language-text lang-text"><code class="language-text lang-text">t=0: T1 runs
t=1: T2 arrives, higher priority, preempts T1
t=3: T2 finishes, T1 resumes
t=5: T1 finishes
t=5: T3 runs
</code></pre>
<p>Gantt chart：</p><pre class="language-text lang-text"><code class="language-text lang-text">0  1     3     5   6
|T1| T2  | T1  |T3 |
</code></pre>
<hr/><h1 id="9-l9-interfacing">9. L9 Interfacing</h1><p>Revision 课件明确说 MCU basic interface 有四个 fundamental elements：</p><ol start="1"><li>Power source</li><li>Clock generator</li><li>Power-on reset circuit</li><li>Booting function</li></ol><hr/><h2 id="91-basic-mcu-interface">9.1 Basic MCU Interface</h2><p>可以写：</p><blockquote><p>A microcontroller requires a basic interface to become usable, including a power source, a clock generator, a reset circuit, and a booting function.</p></blockquote>
<hr/><h2 id="92-power-source">9.2 Power Source</h2><p>常见 power source：</p><ul><li>battery</li><li>wall-connected AC power</li><li>capacitor</li><li>energy harvesting</li></ul><p>Energy harvesting 例子：</p><ul><li>photovoltaic</li><li>piezoelectric</li><li>thermoelectric generator</li><li>kinetic energy</li><li>electromagnetic radiation</li></ul><hr/><h2 id="93-voltage-and-power">9.3 Voltage and Power</h2><p>课件提到：</p><p>$P \sim V^2$
即 digital circuit 的 power consumption 和 supply voltage 平方相关。</p><p>所以降低 voltage 可以降低功耗，但也会降低最高工作频率。</p><p>可以写：</p><blockquote><p>Lower supply voltage reduces power consumption, but it also reduces the maximum operating frequency of the circuit.</p></blockquote>
<hr/><h2 id="94-battery-model">9.4 Battery Model</h2><p>Revision sample question 提到：</p><blockquote><p>Draw the water bucket model for rechargeable batteries.</p></blockquote>
<p>核心概念：</p><ul><li>available charge：可以立即使用的电荷</li><li>bound charge：暂时不容易直接使用的电荷</li><li>internal resistance：内部电阻导致损耗</li><li>leakage/self-discharge：电池慢慢损失电量</li></ul><p>可以把电池想成两个水桶：</p><pre class="language-text lang-text"><code class="language-text lang-text">Bound charge bucket  → slowly transfers → Available charge bucket
                                           ↓
                                      load consumes
</code></pre>
<p>解释：</p><blockquote><p>The available-charge well represents charge that can be immediately extracted by the load. The bound-charge well represents charge that is stored chemically but not immediately available. Charge can flow from bound to available over time, and losses occur due to internal resistance and irreversible reactions.</p></blockquote>
<hr/><h2 id="95-peukerts-law">9.5 Peukert’s Law</h2><p>课件提到 lead-acid battery lifetime：</p><p>$t=\frac{C}{I^\alpha}$
其中：</p><ul><li>(t)：discharge time</li><li>(C)：capacity</li><li>(I)：discharge current</li><li>($\alpha$)：Peukert coefficient</li></ul><p>意义：</p><blockquote><p>Higher discharge current reduces effective battery capacity and shortens battery lifetime.</p></blockquote>
<hr/><h2 id="96-clock">9.6 Clock</h2><p>Clock 用于同步系统操作。</p><p>Clock requirements：</p><ul><li><p>fixed amplitude swing</p><p>$V<em>{SW}=V</em>{OH}-V_{OL}$</p></li><li><p>fixed frequency</p><p>$f<em>{clk}=\frac{1}{T</em>{clk}}$</p></li><li><p>constant duty cycle</p><p>$DC=\frac{T<em>{high}}{T</em>{clk}}$</p></li><li><p>small rising/falling time</p><h2>$t<em>r,\ t</em>f$</h2></li></ul><h2 id="97-clock-jitter-and-drift">9.7 Clock Jitter and Drift</h2><h3 id="jitter">Jitter</h3><blockquote><p>Clock jitter is uncertainty in the periodicity of a clock signal.</p></blockquote>
<p>也就是每个周期不完全一致。</p><h3 id="drift">Drift</h3><blockquote><p>Frequency drift is a systematic change in clock frequency over time.</p></blockquote>
<p>原因可能包括：</p><ul><li>thermal noise</li><li>power supply variation</li><li>loading condition</li><li>temperature change</li></ul><hr/><h2 id="98-internal-vs-external-clock">9.8 Internal vs External Clock</h2><h3 id="internal-clock">Internal clock</h3><ul><li>RC oscillator / ring oscillator</li><li>cheaper</li><li>simpler</li><li>less stable</li><li>limited frequency choices</li></ul><h3 id="external-clock">External clock</h3><ul><li>quartz crystal / ceramic resonator</li><li>more stable</li><li>more accurate</li><li>extra components needed</li></ul><hr/><h2 id="99-reset-and-por">9.9 Reset and POR</h2><p>RESET signal 的作用：</p><blockquote><p>RESET takes the system to its initial state.</p></blockquote>
<p>RESET 可能导致：</p><ul><li>load PC with first boot instruction address</li><li>clear status register</li><li>initialize peripherals</li><li>reset FSM state</li></ul><p>Power-on reset circuit, POR：</p><blockquote><p>A POR circuit generates a reset signal when power is first applied, ensuring that the MCU starts from a known initial state.</p></blockquote>
<hr/><h2 id="910-booting">9.10 Booting</h2><p>Booting 是 CPU reset 后执行的一系列初始化指令。</p><p>包括：</p><ol start="1"><li>identifying and initializing peripherals</li><li>setting up the stack</li><li>initializing global variables</li><li>performing diagnostics</li><li>loading OS or main program</li></ol><p>可以写：</p><blockquote><p>Booting is the sequence of instructions executed after reset to initialize the system and start software execution.</p></blockquote>
<hr/><h2 id="911-polling-vs-interrupts">9.11 Polling vs Interrupts</h2><h3 id="polling">Polling</h3><p>CPU 不断检查外设状态。</p><p>优点：</p><ul><li>simple</li><li>predictable</li><li>easy to implement</li></ul><p>缺点：</p><ul><li>wastes CPU cycles</li><li>poor response if polling interval too long</li><li>higher energy consumption</li></ul><hr/><h3 id="interrupt">Interrupt</h3><p>外设需要服务时发送 IRQ。</p><p>CPU 暂停当前任务，执行 ISR。</p><p>优点：</p><ul><li>faster response</li><li>lower CPU waste</li><li>lower energy consumption</li><li>better for asynchronous events</li></ul><p>缺点：</p><ul><li>more complex</li><li>ISR must be short</li><li>possible priority issues</li><li>concurrency problems</li></ul><hr/><h1 id="10-l10-serial-communication">10. L10 Serial Communication</h1><h2 id="101-serial-vs-parallel-communication">10.1 Serial vs Parallel Communication</h2><h3 id="serial-communication">Serial communication</h3><p>一条信号链路逐 bit 传输。</p><blockquote><p>Serial communication transmits an n-bit character over one signal link sequentially.</p></blockquote>
<p>优点：</p><ul><li>fewer wires</li><li>lower cost</li><li>better for long distance</li><li>less crosstalk</li><li>suitable for high-speed communication</li></ul><hr/><h3 id="parallel-communication">Parallel communication</h3><p>n 条线同时传 n bits。</p><blockquote><p>Parallel communication transmits multiple bits simultaneously using multiple signal links.</p></blockquote>
<p>理论上快，但高速或长距离时会有：</p><ul><li>skew</li><li>crosstalk</li><li>transmission line effects</li><li>synchronization difficulty</li></ul><p>所以现代高速通信很多使用 serial link。</p><hr/><h2 id="102-bit-rate-vs-baud-rate">10.2 Bit Rate vs Baud Rate</h2><h3 id="bit-rate">Bit rate</h3><p>每秒传输 bit 数：</p><p>$bit\ rate=\frac{1}{t_{bit}}$
单位：bps。</p><h3 id="baud-rate">Baud rate</h3><p>每秒传输 symbol 数。</p><p>关系：</p><p>$bit\ rate = baud\ rate \times bits\ per\ symbol$
例子：</p><h3 id="bpsk">BPSK</h3><p>一个 symbol 表示 1 bit：</p><p>$bit\ rate=baud\ rate$</p><h3 id="qpsk">QPSK</h3><p>一个 symbol 表示 2 bits：</p><p>$bit\ rate=2 \times baud\ rate$
所以：</p><p>$baud\ rate=0.5 \times bit\ rate$</p><h2 id="103-single-ended-vs-differential-link">10.3 Single-ended vs Differential Link</h2><h3 id="single-ended">Single-ended</h3><p>信号相对于 ground reference 测量。</p><p>例子：</p><ul><li>RS-232</li></ul><p>缺点：</p><ul><li>容易受 ground noise 影响</li><li>noise immunity 较差</li></ul><hr/><h3 id="differential">Differential</h3><p>用两条线的电压差表示信号：</p><p>$V<em>{diff}=V</em>+ - V<em>-$
如果两条线同时受到相同 noise (V</em>n)：</p><p>$V<em>{diff}=(V</em>+ + V<em>n)-(V</em>- + V<em>n)=V</em>+-V_-$
noise 被抵消。</p><p>可以写：</p><blockquote><p>Differential links reject common-mode noise because the receiver measures the voltage difference between two lines, and noise common to both lines is cancelled.</p></blockquote>
<hr/><h2 id="104-simplex--half-duplex--full-duplex">10.4 Simplex / Half-duplex / Full-duplex</h2><h3 id="simplex">Simplex</h3><p>单方向通信。</p><p>例子：</p><ul><li>TV broadcast</li><li>radio broadcast</li></ul><pre class="language-text lang-text"><code class="language-text lang-text">A → B
</code></pre>
<hr/><h3 id="half-duplex">Half-duplex</h3><p>双向通信，但同一时间只能一个方向。</p><p>例子：</p><ul><li>walkie-talkie</li></ul><pre class="language-text lang-text"><code class="language-text lang-text">A ⇄ B
but not simultaneously
</code></pre>
<hr/><h3 id="full-duplex">Full-duplex</h3><p>两个方向可以同时通信。</p><p>例子：</p><ul><li>phone call</li></ul><pre class="language-text lang-text"><code class="language-text lang-text">A ↔ B
simultaneously
</code></pre>
<hr/><h2 id="105-synchronous-vs-asynchronous-serial-communication">10.5 Synchronous vs Asynchronous Serial Communication</h2><h3 id="synchronous">Synchronous</h3><p>发送端和接收端共享 clock，或者 clock embedded in signal。</p><p>优点：</p><ul><li>high speed</li><li>no start/stop bit overhead</li></ul><p>缺点：</p><ul><li>clock synchronization needed</li><li>more complex</li></ul><p>例子：</p><ul><li>SPI</li><li>I2C</li></ul><hr/><h3 id="asynchronous">Asynchronous</h3><p>没有共享 clock，通过 start bit / stop bit 同步每个 frame。</p><p>优点：</p><ul><li>simple</li><li>fewer clock wires</li></ul><p>缺点：</p><ul><li>overhead higher</li><li>clock mismatch can accumulate</li></ul><p>例子：</p><ul><li>UART</li></ul><hr/><h2 id="106-packet--datagram">10.6 Packet / Datagram</h2><p>Message 可以分成 packet。</p><p>每个 packet 包括：</p><pre class="language-text lang-text"><code class="language-text lang-text">Header + Body + Footer
</code></pre>
<h3 id="header">Header</h3><ul><li>start of packet</li><li>address</li><li>type</li><li>length</li></ul><h3 id="body">Body</h3><p>实际数据。</p><h3 id="footer">Footer</h3><ul><li>end of packet</li><li>error checking</li><li>checksum / CRC</li></ul><hr/><h1 id="11-l11-evaluation-and-validation">11. L11 Evaluation and Validation</h1><h2 id="111-evaluation-vs-validation">11.1 Evaluation vs Validation</h2><h3 id="evaluation">Evaluation</h3><blockquote><p>Evaluation is the process of computing quantitative information about key system characteristics.</p></blockquote>
<p>也就是测量性能指标，例如：</p><ul><li>execution time</li><li>energy consumption</li><li>memory usage</li><li>temperature</li><li>quality of result</li></ul><hr/><h3 id="validation">Validation</h3><blockquote><p>Validation checks whether the design satisfies requirements and design objectives.</p></blockquote>
<p>也就是判断系统是否达到需求。</p><hr/><h3 id="formal-verification">Formal Verification</h3><blockquote><p>Formal verification validates a system with mathematical rigor.</p></blockquote>
<p>例如 model checking。</p><hr/><h2 id="112-multi-objective-optimization">11.2 Multi-objective Optimization</h2><p>Revision sample question：</p><blockquote><p>Formulate a multi-objective optimization.</p></blockquote>
<p>embedded system 设计中，目标通常冲突。</p><p>Product criteria (f(x))：</p><ul><li>cost</li><li>execution time</li><li>energy consumption</li><li>durability</li><li>reliability</li><li>memory usage</li><li>accuracy</li></ul><p>Product characteristics (x)：</p><ul><li>number of processors</li><li>size of memory</li><li>bus type</li><li>clock frequency</li><li>battery capacity</li><li>sensor type</li><li>algorithm parameters</li></ul><p>可以写成：</p><p>$$
\begin{aligned}
\min<em>x \quad 
f(x) &amp;=
\begin{bmatrix}
\mathrm{cost}(x) \
\mathrm{execution\ time}(x) \
\mathrm{energy}(x)
\end{bmatrix} \
\text{subject to} \quad
\mathrm{memory}(x) &amp;\le M</em>{\max} \
\mathrm{accuracy}(x) &amp;\ge A<em>{\min} \
\mathrm{power}(x) &amp;\le P</em>{\max}
\end{aligned}
$$
---</p><h2 id="113-pareto-optimality">11.3 Pareto Optimality</h2><p>如果一个 design 在某个目标上更好，但不会让其他目标变差，则它 dominates 另一个 design。</p><p>Pareto optimal point：</p><blockquote><p>A design is Pareto optimal if no other design can improve one objective without worsening at least one other objective.</p></blockquote>
<p>考试可以写：</p><blockquote><p>In multi-objective optimization, there may not be a single best solution. Instead, designers select from Pareto-optimal solutions according to design priorities.</p></blockquote>
<hr/><h2 id="114-wcet-and-bcet">11.4 WCET and BCET</h2><h3 id="wcet">WCET</h3><p>Worst-case execution time。</p><blockquote><p>WCET is the largest execution time of a program for any feasible input and initial state.</p></blockquote>
<p>估计值：</p><p>$WCET_{EST}\ge WCET$
并且要 tight：</p><p>$WCET_{EST}-WCET \ll WCET$
意思是：估计值要安全，但不能过于保守。</p><hr/><h3 id="bcet">BCET</h3><p>Best-case execution time。</p><blockquote><p>BCET is the smallest execution time of a program for feasible inputs and initial states.</p></blockquote>
<p>估计值：</p><p>$BCET_{EST}\le BCET$
并且要 tight：</p><p>$BCET-BCET_{EST} \ll BCET$</p><h2 id="115-approximate-computing">11.5 Approximate Computing</h2><p>课件定义：</p><blockquote><p>Approximate computing allows tolerated deviation from the best possible output in exchange for reduced resource consumption.</p></blockquote>
<p>例子：</p><ul><li>mp3</li><li>jpeg</li><li>mp4</li><li>approximate image processing</li><li>approximate machine learning inference</li></ul><p>权衡：</p><pre class="language-text lang-text"><code class="language-text lang-text">less accuracy
→ lower execution time
→ lower energy
→ lower memory
</code></pre>
<hr/><h2 id="116-error-metrics">11.6 Error Metrics</h2><h3 id="mse">MSE</h3><p>$MSE=\frac{1}{N}\sum<em>{i=1}^{N}(x</em>i-y_i)^2$
特点：</p><ul><li>penalizes large errors more</li><li>unit is squared</li></ul><hr/><h3 id="rmse">RMSE</h3><p>$RMSE=\sqrt{MSE}$
特点：</p><ul><li>same unit as original data</li><li>easier to interpret than MSE</li><li>still penalizes large error</li></ul><hr/><h3 id="mae">MAE</h3><p>$MAE=\frac{1}{N}\sum<em>{i=1}^{N}|x</em>i-y_i|$
特点：</p><ul><li>average absolute distance</li><li>same unit as original data</li><li>less sensitive to outliers than MSE</li></ul><p>注意：PPT 里可能写了 “emphasizes large deviations/outliers”，但从数学上看，MSE 比 MAE 更强调大误差。考试如果按课件写，保守写法是：</p><blockquote><p>MAE measures the average absolute deviation in the same unit as the data, while MSE/RMSE penalize larger errors more strongly due to squaring.</p></blockquote>
<hr/><h3 id="psnr">PSNR</h3><p>Peak signal-to-noise ratio 常用于图像质量。</p><p>通常：</p><p>$PSNR=10\log<em>{10}\frac{MAX</em>I^2}{MSE}$
MSE 越小，PSNR 越大，图像质量越好。</p><hr/><h2 id="117-dependability">11.7 Dependability</h2><p>Dependability 包括：</p><ul><li>reliability</li><li>safety</li><li>security</li><li>availability</li><li>maintainability</li></ul><h3 id="reliability">Reliability</h3><p>系统在一定时间内正确工作的能力。</p><h3 id="safety">Safety</h3><p>系统不会造成危险或伤害。</p><h3 id="security">Security</h3><p>系统抵抗攻击和未授权访问的能力。</p><h3 id="availability">Availability</h3><p>系统在需要时可用。</p><hr/><h1 id="12-">12. 高频题型模板</h1><h2 id="121-address-bus-">12.1 Address Bus 计算题</h2><p>题目：</p><blockquote><p>Determine how many memory locations can be accessed and the address range in hex notation with an address bus of 12 bits.</p></blockquote>
<p>答：</p><p>$2^{12}=4096$
所以可以访问 4096 个 memory locations。</p><p>12 bits = 3 hex digits。</p><p>地址范围：</p><p>$0x000 \text{ to } 0xFFF$
模板：</p><pre class="language-text lang-text"><code class="language-text lang-text">An m-bit address bus can address 2^m different memory locations.
The address range is from 0 to 2^m - 1.
Then convert both limits to hexadecimal.
</code></pre>
<hr/><h2 id="122-volatile-vs-non-volatile-memory">12.2 Volatile vs Non-volatile Memory</h2><p>可直接背：</p><blockquote><p>Volatile memory loses its stored data when power is removed, such as RAM, SRAM, DRAM, and cache. Non-volatile memory retains data without power, such as ROM, Flash, and EEPROM. In embedded systems, volatile memory is usually used for runtime data, while non-volatile memory is used to store firmware and permanent data.</p></blockquote>
<hr/><h2 id="123-sensor-selection">12.3 Sensor Selection</h2><p>可直接背：</p><blockquote><p>To select sensors for a dynamical system, we first identify the physical quantities that need to be measured, such as position, velocity, acceleration, temperature, or pressure. Then suitable sensors are chosen according to range, sensitivity, accuracy, precision, resolution, response time, noise level, power consumption, cost, size, and interface compatibility. For example, a mobile robot may use encoders for wheel position, IMU for acceleration and angular velocity, LIDAR or camera for obstacle detection, and GPS for outdoor localization.</p></blockquote>
<hr/><h2 id="124-non-preemptive-priority-scheduler">12.4 Non-preemptive Priority Scheduler</h2><p>答题步骤：</p><pre class="language-text lang-text"><code class="language-text lang-text">1. Sort tasks by arrival time.
2. At each decision point, list ready tasks.
3. Select the ready task with highest priority.
4. Since it is non-preemptive, once a task starts, it runs until completion.
5. Draw Gantt chart.
</code></pre>
<p>关键句：</p><blockquote><p>In a non-preemptive priority scheduler, a running task cannot be interrupted. The scheduler only chooses the highest-priority ready task when the CPU becomes free.</p></blockquote>
<hr/><h2 id="125-differential-link-common-noise-cancellation">12.5 Differential Link Common Noise Cancellation</h2><p>可直接背：</p><blockquote><p>In a differential link, the receiver measures the voltage difference between two signal wires. If the same noise is added to both wires, it is common-mode noise. Since the receiver subtracts the two voltages, the common noise is cancelled.</p></blockquote>
<p>公式：</p><p>$V<em>{diff}=(V</em>+ + V<em>n)-(V</em>- + V<em>n)=V</em>+-V_-$</p><h2 id="126-ltl-factory-inspection-task">12.6 LTL Factory Inspection Task</h2><p>题目可能是：</p><blockquote><p>Formally specify a factory inspection task by a robot as an LTL formula.</p></blockquote>
<p>可以自己定义 propositions：</p><ul><li>(p)：robot receives inspection request</li><li>(q)：robot reaches inspection station</li><li>(r)：robot completes inspection</li><li>(s)：unsafe area is entered</li><li>(c)：collision occurs</li></ul><p>可以写：</p><ol start="1"><li>收到任务后最终到达检查点：</li></ol><p>$G(p \Rightarrow Fq)$</p><ol start="2"><li>到达检查点后最终完成检查：</li></ol><p>$G(q \Rightarrow Fr)$</p><ol start="3"><li>永远不能碰撞：</li></ol><p>$G(\neg c)$</p><ol start="4"><li>不能进入危险区域：</li></ol><p>$G(\neg s)$
综合：</p><p>$G(p \Rightarrow Fq)\land G(q \Rightarrow Fr)\land G(\neg c)\land G(\neg s)$
文字解释：</p><blockquote><p>This formula specifies that whenever an inspection request is issued, the robot must eventually reach the inspection station and complete the inspection, while always avoiding collision and unsafe areas.</p></blockquote>
<hr/><h2 id="127-multi-objective-optimization">12.7 Multi-objective Optimization</h2><p>题目：</p><blockquote><p>Formulate a multi-objective optimization.</p></blockquote>
<p>可以写：</p><p>Let (x) represent design choices, such as CPU frequency, memory size, battery capacity, and sensor type.</p><p>Objectives:</p><p>$
\min<em>x<br/>\begin{bmatrix}<br/>cost(x)\<br/>energy(x)\<br/>execution\ time(x)<br/>\end{bmatrix}<br/>$Constraints:$
accuracy(x)\ge A</em>{min}<br/>$</p><p>$memory(x)\le M<em>{max}$
$response\ time(x)\le T</em>{max}$
解释：</p><blockquote><p>The objectives conflict with each other. For example, increasing CPU frequency may reduce execution time but increase energy consumption. Therefore, the designer should consider Pareto-optimal solutions.</p></blockquote>
<hr/><h1 id="13-">13. 最后冲刺背诵版</h1><h2 id="">必背定义</h2><h3 id="embedded-system">Embedded System</h3><blockquote><p>A dedicated computer system integrated into a larger device to perform specific functions.</p></blockquote>
<h3 id="cps">CPS</h3><blockquote><p>A system integrating computation, communication, and physical processes.</p></blockquote>
<h3 id="continuous-dynamics">Continuous Dynamics</h3><blockquote><p>Describes time-continuous system behavior using differential equations.</p></blockquote>
<h3 id="discrete-dynamics">Discrete Dynamics</h3><blockquote><p>Describes systems whose states change through discrete transitions.</p></blockquote>
<h3 id="fsm">FSM</h3><blockquote><p>A mathematical model consisting of states, inputs, outputs, transitions, and output functions.</p></blockquote>
<h3 id="sensor">Sensor</h3><blockquote><p>A device that measures a physical quantity and converts it into a signal.</p></blockquote>
<h3 id="actuator">Actuator</h3><blockquote><p>A device that changes or controls a physical quantity in the environment.</p></blockquote>
<h3 id="mcu">MCU</h3><blockquote><p>A computer-on-a-chip integrating CPU, memory, and I/O peripherals.</p></blockquote>
<h3 id="volatile-memory">Volatile Memory</h3><blockquote><p>Memory that loses data when power is removed.</p></blockquote>
<h3 id="non-volatile-memory">Non-volatile Memory</h3><blockquote><p>Memory that retains data without power.</p></blockquote>
<h3 id="interrupt">Interrupt</h3><blockquote><p>A signal from a peripheral that causes the CPU to suspend its current task and execute an ISR.</p></blockquote>
<h3 id="bit-rate">Bit Rate</h3><blockquote><p>Number of bits transmitted per second.</p></blockquote>
<h3 id="baud-rate">Baud Rate</h3><blockquote><p>Number of symbols transmitted per second.</p></blockquote>
<h3 id="evaluation">Evaluation</h3><blockquote><p>Computing quantitative information about system characteristics.</p></blockquote>
<h3 id="validation">Validation</h3><blockquote><p>Checking whether a design satisfies requirements.</p></blockquote>
<hr/><h1 id="14-">14. 考试答题建议</h1><ol start="1"><li><strong>看到 “draw” 就一定画图</strong><br/> 例如 FSM、scheduler、FBD、battery bucket model、differential link。</li><li><strong>看到 “explain difference” 就写对比表</strong><br/> 例如 volatile vs non-volatile，Mealy vs Moore，polling vs interrupt，serial vs parallel。</li><li><strong>看到公式题，先写 general rule</strong><br/> 例如 address bus：<br/>$2^m$
 然后再代入。</li><li><strong>LTL 题先定义 proposition</strong><br/> 不要直接写公式。先写：
 
 <code>text
 p: request
 q: inspection completed
 c: collision
 </code>
 
 然后再写：<br/>$G(p\Rightarrow Fq)\land G(\neg c)$</li><li><strong>scheduler 题一定说明 preemptive / non-preemptive</strong><br/> 很多人错在没有说明任务能不能被打断。</li><li><strong>传感器选择题不要只写 sensor 名字</strong><br/> 还要写为什么：
 
 <code>text
 selected because it measures acceleration, has fast response, and is suitable for robot motion estimation
 </code></li><li><strong>Evaluation 题要体现 trade-off</strong><br/> embedded system 设计几乎都是 trade-off：
 
 <code>text
 speed vs energy
 cost vs performance
 accuracy vs resource consumption
 memory vs capability
 </code></li></ol><hr/><p>这份资料里最值得优先背的是：</p><pre class="language-text lang-text"><code class="language-text lang-text">L2 系统性质 + 线性化
L3 FSM / Mealy / Moore
L4 sensor metrics
L5 address bus 计算
L6 volatile vs non-volatile
L7 scheduling
L8 LTL 公式模板
L9 power / clock / reset / boot / interrupt
L10 bit rate / baud rate / differential link
L11 evaluation / validation / multi-objective optimization
</code></pre></div><p style="text-align:right"><a href="https://blog.chiran.top/posts/大学生涯/mec302#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.chiran.top/posts/大学生涯/mec302</link><guid isPermaLink="true">https://blog.chiran.top/posts/大学生涯/mec302</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Mon, 08 Jun 2026 11:27:09 GMT</pubDate></item><item><title><![CDATA[CPT204]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.chiran.top/posts/大学生涯/CPT204">https://blog.chiran.top/posts/大学生涯/CPT204</a></blockquote><div><h1 id="lecture-11---arrays">Lecture 1.1 - Arrays</h1><h3 id=""><strong>一、数组基础：为什么需要数组？</strong></h3><h4 id=""><strong>核心问题引入</strong></h4><p>假设要读取 100 个数字并计算平均值，还要找出大于平均值的数字个数。如果为每个数字都创建一个变量，显然不现实。这时，<strong>数组</strong>就能派上用场，它可以用一个变量存储多个同类型的数据。</p><h4 id=""><strong>数组的定义与特点</strong></h4><ul><li><strong>定义</strong>：数组是相同数据类型元素的集合，有固定的长度，通过<strong>索引（从 0 开始）</strong>访问元素。</li><li><strong>内存存储</strong>：数组在内存的堆区分配空间，变量存储的是数组的<strong>引用（内存地址）</strong>。</li></ul><h3 id=""><strong>二、数组的声明与创建</strong></h3><h4 id="1-"><strong>1. 声明数组变量</strong></h4><ul><li><p><strong>语法</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">数据类型[] 数组名; // 推荐写法，如 double[] myList;
数据类型 数组名[]; // 允许但不推荐，如 double myList[];

</code></pre>
</li></ul><h4 id="2-"><strong>2. 创建数组（分配内存）</strong></h4><ul><li><p><strong>语法</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">数组名 = new 数据类型[长度]; // 如 myList = new double[10];

</code></pre>
</li><li><p><strong>示例</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">double[] myList; // 声明
myList = new double[10]; // 创建，长度为 10

</code></pre>
</li></ul><h4 id="3-"><strong>3. 声明与创建合并</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">double[] myList = new double[10]; // 一步完成
</code></pre>
<h3 id=""><strong>三、数组初始化</strong></h3><h4 id="1-"><strong>1. 默认值</strong></h4><ul><li>数值型（如 <code>int</code>、<code>double</code>）：默认值为 <code>0</code>。</li><li>布尔型：默认值为 <code>false</code>。</li><li>引用类型（如 <code>String</code>）：默认值为 <code>null</code>。</li></ul><h4 id="2-"><strong>2. 手动赋值</strong></h4><ul><li><p><strong>通过索引赋值</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">myList[0] = 5.6; // 给第一个元素赋值
myList[1] = 4.5; // 给第二个元素赋值

</code></pre>
</li></ul><h4 id="3-"><strong>3. 快捷初始化（数组初始化器）</strong></h4><ul><li><p><strong>语法</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">数据类型[] 数组名 = {值1, 值2, 值3, ...}; // 声明、创建、赋值一步完成

</code></pre>
</li><li><p><strong>示例</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">int[] scores = {85, 90, 78}; // 长度为 3

</code></pre>
</li></ul><h3 id=""><strong>四、数组常用操作</strong></h3><h4 id="1-"><strong>1. 遍历数组</strong></h4><ul><li><p><strong>普通 for 循环（带索引）</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">for (int i = 0; i &lt; myList.length; i++) {
    System.out.println(myList[i]); // length 是数组属性，获取长度
}

</code></pre>
</li><li><p><strong>增强 for 循环（for-each，无索引）</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">for (double value : myList) { // 依次取出每个元素赋值给 value
    System.out.println(value);
}

</code></pre>
</li></ul><h4 id="2-"><strong>2. 常见算法</strong></h4><ul><li><p><strong>求和</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">double sum = 0;
for (double num : myList) {
    sum += num;
}

</code></pre>
</li><li><p><strong>找最大值</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">double max = myList[0]; // 假设第一个元素是最大值
for (int i = 1; i &lt; myList.length; i++) {
    if (myList[i] &gt; max) {
        max = myList[i]; // 发现更大值，更新 max
    }
}

</code></pre>
</li><li><p><strong>数组复制</strong>：</p><ul><li><p><strong>错误做法（引用复制）</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">int[] arr1 = {1, 2, 3};
int[] arr2 = arr1; // arr2 和 arr1 指向同一个数组，修改 arr2 会影响 arr1

</code></pre>
</li><li><p><strong>正确做法（元素复制）</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">int[] arr1 = {1, 2, 3};
int[] arr2 = new int[arr1.length];
for (int i = 0; i &lt; arr1.length; i++) {
    arr2[i] = arr1[i]; // 逐个元素复制
}

</code></pre>
</li></ul></li></ul><h3 id=""><strong>五、数组作为方法参数</strong></h3><h4 id="1-"><strong>1. 传递数组（按值传递引用）</strong></h4><ul><li>Java 中传递数组时，实际传递的是数组的<strong>引用（内存地址）</strong>，因此方法内对数组元素的修改会影响原始数组。</li><li><p><strong>示例</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static void changeArray(int[] arr) {
    arr[0] = 100; // 修改数组第一个元素
}

public static void main(String[] args) {
    int[] numbers = {1, 2, 3};
    changeArray(numbers); // 调用方法
    System.out.println(numbers[0]); // 输出 100（原始数组被修改）
}

</code></pre>
</li></ul><h4 id="2-"><strong>2. 返回数组</strong></h4><ul><li><p>方法可以返回一个数组，例如反转数组：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static int[] reverse(int[] list) {
    int[] result = new int[list.length];
    for (int i = 0, j = result.length - 1; i &lt; list.length; i++, j--) {
        result[j] = list[i]; // 首尾元素交换
    }
    return result; // 返回新数组
}

</code></pre>
</li></ul><h3 id=""><strong>六、搜索与排序算法</strong></h3><h4 id="1-"><strong>1. 线性搜索（适用于未排序数组）</strong></h4><ul><li><strong>原理</strong>：从数组第一个元素开始，逐个与目标值比较，直到找到或遍历完数组。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static int linearSearch(int[] list, int key) {
    for (int i = 0; i &lt; list.length; i++) {
        if (list[i] == key) {
            return i; // 找到，返回索引
        }
    }
    return -1; // 未找到，返回 -1
}

</code></pre>
</li></ul><h4 id="2-"><strong>2. 二分搜索（适用于已排序数组，效率更高）</strong></h4><ul><li><strong>原理</strong>：每次将数组分成两半，根据中间元素与目标值的大小关系，缩小搜索范围。</li><li><strong>时间复杂度</strong>：O(log n)（最坏情况）。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static int binarySearch(int[] list, int key) {
    int low = 0, high = list.length - 1;
    while (low &lt;= high) {
        int mid = (low + high) / 2;
        if (key == list[mid]) return mid; // 找到
        else if (key &lt; list[mid]) high = mid - 1; // 目标值在左半部分
        else low = mid + 1; // 目标值在右半部分
    }
    return -1; // 未找到
}

</code></pre>
</li></ul><h4 id="3-"><strong>3. 选择排序（简单直观，效率较低）</strong></h4><ul><li><strong>原理</strong>：每次从剩余元素中找到最小值，与当前位置元素交换。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static void selectionSort(int[] list) {
    for (int i = 0; i &lt; list.length - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j &lt; list.length; j++) {
            if (list[j] &lt; list[minIndex]) {
                minIndex = j; // 更新最小值索引
            }
        }
        // 交换当前元素与最小值
        int temp = list[i];
        list[i] = list[minIndex];
        list[minIndex] = temp;
    }
}

</code></pre>
</li></ul><h3 id="-quiz"><strong>七、练习与 Quiz</strong></h3><h4 id="-1"><strong>练习 1：数组初始化判断</strong></h4><p>下面哪个是正确的数组初始化方式？</p><pre class="language-java lang-java"><code class="language-java lang-java">A. int[] arr = new int {1, 2, 3};
B. int[] arr = {1, 2, 3};
C. int arr[3] = {1, 2, 3};
</code></pre><p><strong>答案</strong>：B（A 缺少长度；C 是 C/C++ 语法，Java 不支持）。</p><h4 id="-2-for-"><strong>练习 2：增强 for 循环</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">int[] numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
    num *= 2; // 修改的是副本，不影响原数组
}
System.out.println(numbers[2]); // 输出什么？
</code></pre><p><strong>答案</strong>：3（增强 for 循环中无法通过 <code>num</code> 修改原数组元素）。</p><h4 id="-3"><strong>练习 3：二分搜索时间复杂度</strong></h4><p>二分搜索的最坏时间复杂度是？</p><pre class="language-java lang-java"><code class="language-java lang-java">A. O(1)  B. O(n)  C. O(log n)  D. O(n²)
</code></pre><p><strong>答案</strong>：C（每次折半，时间复杂度为对数级）。</p><h3 id=""><strong>八、总结</strong></h3><ul><li><strong>重点知识</strong>：数组的声明与创建、初始化、遍历、作为方法参数传递、搜索与排序算法。</li><li><strong>常见误区</strong>：
<ul><li>数组长度不可变，创建后无法修改。</li><li>直接赋值 <code>arr2 = arr1</code> 是引用复制，而非元素复制。</li><li>二分搜索必须在<strong>已排序数组</strong>上使用。</li></ul></li></ul><h1 id="lecture-12---objects-and-classes">Lecture 1.2 - Objects and Classes</h1><h3 id=""><strong>一、核心概念：对象与类</strong></h3><h4 id="1-"><strong>1. 什么是对象？</strong></h4><ul><li><strong>定义</strong>：对象是现实世界中可唯一标识的实体，例如“一个圆形”“一个学生”。</li><li><strong>特征</strong>：
<ul><li><strong>状态（State）</strong>：用数据字段（属性）表示，如圆形的半径<code>radius</code>。</li><li><strong>行为（Behavior）</strong>：用方法（函数）表示，如计算面积的<code>getArea()</code>。</li></ul></li></ul><h4 id="2-"><strong>2. 什么是类？</strong></h4><ul><li><strong>定义</strong>：类是对象的模板/蓝图，用于创建具有相同属性和方法的对象。</li><li><strong>组成</strong>：
<ul><li><strong>数据字段（Fields）</strong>：如<code>private double radius;</code>（非静态，属于每个对象）。</li><li><strong>方法（Methods）</strong>：如<code>public double getArea()</code>（非静态方法需通过对象调用）。</li><li><strong>构造函数（Constructors）</strong>：用于创建对象，名称必须与类名相同，无返回类型。</li></ul></li></ul><p><strong>示例：Circle类</strong></p><pre class="language-java lang-java"><code class="language-java lang-java">public class Circle {
    private double radius = 1.0; // 数据字段（默认值1.0）

    // 无参构造函数
    public Circle() { }

    // 有参构造函数
    public Circle(double newRadius) {
        radius = newRadius;
    }

    // 实例方法：计算面积
    public double getArea() {
        return radius * radius * 3.14159;
    }
}
</code></pre>
<h3 id=""><strong>二、创建对象与内存分析</strong></h3><h4 id="1-"><strong>1. 对象创建步骤</strong></h4><ol start="1"><li><strong>声明引用变量</strong>：<code>Circle myCircle;</code>（此时变量值为<code>null</code>，未指向任何对象）。</li><li><strong>实例化对象</strong>：<code>myCircle = new Circle(5.0);</code>（通过<code>new</code>关键字调用构造函数，分配内存）。</li></ol><h4 id="2-"><strong>2. 内存示意图</strong></h4><pre class=""><code class="">myCircle  →  { radius: 5.0 }  // 对象在堆内存中，引用变量指向对象
</code></pre>
<h4 id="3-"><strong>3. 访问对象成员</strong></h4><ul><li><strong>访问属性</strong>：<code>对象名.属性</code>（需注意权限，如<code>private</code>属性不可直接访问）。</li><li><strong>调用方法</strong>：<code>对象名.方法名()</code>。</li></ul><p><strong>示例</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">Circle c1 = new Circle();       // 调用无参构造，radius=1.0
Circle c2 = new Circle(5.0);    // 调用有参构造，radius=5.0
System.out.println(c2.getArea()); // 输出：78.53975（5²×π）
</code></pre>
<h3 id=""><strong>三、关键知识点解析</strong></h3><h4 id="1-static-vs-non-static"><strong>1. 静态（Static） vs 非静态（Non-static）</strong></h4><table><thead><tr><th> <strong>对比项</strong>  </th><th> <strong>静态成员（类成员）</strong>             </th><th> <strong>非静态成员（实例成员）</strong>           </th></tr></thead><tbody><tr><td> <strong>归属</strong>   </td><td> 属于类本身，所有对象共享              </td><td> 属于单个对象                    </td></tr><tr><td> <strong>访问方式</strong> </td><td> 直接通过类名访问（如<code>ClassName.x</code>）  </td><td> 必须通过对象访问（如<code>obj.x</code>）        </td></tr><tr><td> <strong>常见用途</strong> </td><td> 工具方法（如<code>Math.pow()</code>）、全局计数器 </td><td> 对象的状态数据（如每个Circle的radius） </td></tr></tbody></table><p><strong>示例：静态变量<code>numberOfObjects</code></strong></p><pre class="language-java lang-java"><code class="language-java lang-java">public class Circle {
    private static int numberOfObjects = 0; // 静态变量，记录创建的对象总数

    public Circle() {
        numberOfObjects++; // 每次创建对象时计数加1
    }

    public static int getNumberOfObjects() { // 静态方法，返回计数
        return numberOfObjects;
    }
}

// 使用方式：
System.out.println(Circle.getNumberOfObjects()); // 直接通过类名调用
</code></pre>
<h4 id="2-visibility-modifiers"><strong>2. 访问修饰符（Visibility Modifiers）</strong></h4><table><thead><tr><th> <strong>修饰符</strong> </th><th> <strong>作用范围</strong>                     </th><th> <strong>UML符号</strong> </th></tr></thead><tbody><tr><td> <code>public</code>   </td><td> 任何类均可访问                   </td><td> <code>+</code>         </td></tr><tr><td> <code>private</code>  </td><td> 仅当前类内部可访问               </td><td> <code>-</code>         </td></tr><tr><td> <strong>默认</strong>   </td><td> 同一包（package）内可访问         </td><td> 无符号      </td></tr></tbody></table><p><strong>封装最佳实践</strong>：</p><ul><li><p>将数据字段设为<code>private</code>，通过<strong>访问器（getter）</strong>和<strong>修改器（setter）</strong>方法间接操作。</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Circle {
  private double radius;

  // 访问器（getter）
  public double getRadius() {
      return radius;
  }

  // 修改器（setter）
  public void setRadius(double radius) {
      this.radius = radius; // `this`指代当前对象
  }
}
</code></pre>
</li></ul><h4 id="3--vs-"><strong>3. 基本类型 vs 引用类型</strong></h4><table><thead><tr><th> <strong>类型</strong>       </th><th> <strong>示例</strong>       </th><th> <strong>赋值行为</strong>                          </th><th> <strong>内存存储</strong>       </th></tr></thead><tbody><tr><td> <strong>基本类型</strong>   </td><td> <code>int i = 5;</code>   </td><td> 复制值（如<code>j = i</code>后，<code>j</code>独立存储5）   </td><td> 栈内存             </td></tr><tr><td> <strong>引用类型</strong>   </td><td> <code>Circle c = new Circle();</code> </td><td> 复制引用（如<code>c2 = c1</code>后，两者指向同一对象） </td><td> 引用在栈，对象在堆 </td></tr></tbody></table><p><strong>注意</strong>：修改引用类型的属性会影响所有指向它的变量！</p><pre class="language-java lang-java"><code class="language-java lang-java">Circle c1 = new Circle(5);
Circle c2 = c1;          // c2引用c1的对象
c2.setRadius(10);        // c1和c2的radius都会变为10
</code></pre>
<h3 id="uml"><strong>四、UML类图快速入门</strong></h3><p>UML类图用于可视化类的结构，常见符号：</p><pre class=""><code class="">+-----------------+
|     Circle      |          // 类名
+-----------------+
| - radius: double|          // 私有属性（-）
| + numberOfObjects: static int | // 静态属性（下划线）
+-----------------+
| + Circle()      |          // 构造函数
| + Circle(radius: double) | 
| + getArea(): double       | // 公共方法（+）
+-----------------+
</code></pre>
<h3 id=""><strong>五、实战练习</strong></h3><h4 id="1"><strong>练习1：定义学生类</strong></h4><p><strong>需求</strong>：</p><ul><li>定义<code>Student</code>类，包含私有字段<code>name</code>（String）、<code>age</code>（int）、<code>isScienceMajor</code>（boolean）。</li><li>提供构造函数初始化<code>name</code>和<code>age</code>，<code>isScienceMajor</code>默认<code>false</code>。</li><li>提供<code>getter</code>和<code>setter</code>方法。</li><li>在<code>main</code>方法中创建学生对象，设置专业为<code>true</code>，并输出信息。</li></ul><p><strong>参考答案</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Student {
    private String name;
    private int age;
    private boolean isScienceMajor = false; // 默认值false

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter和setter
    public String getName() { return name; }
    public int getAge() { return age; }
    public boolean isScienceMajor() { return isScienceMajor; }
    public void setScienceMajor(boolean isScienceMajor) {
        this.isScienceMajor = isScienceMajor;
    }

    public static void main(String[] args) {
        Student stu = new Student(&quot;Alice&quot;, 20);
        stu.setScienceMajor(true);
        System.out.println(stu.getName() + &quot;是科学专业吗？&quot; + stu.isScienceMajor()); // 输出：Alice是科学专业吗？true
    }
}
</code></pre>
<h4 id="2"><strong>练习2：静态方法应用</strong></h4><p><strong>需求</strong>：在<code>Circle</code>类中添加静态方法<code>calculateArea(double radius)</code>，无需创建对象即可计算面积。</p><p><strong>参考答案</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Circle {
    // ...原有代码...

    // 静态方法：直接通过类名调用，计算任意半径的面积
    public static double calculateArea(double radius) {
        return radius * radius * 3.14159;
    }

    // 使用示例：
    double area = Circle.calculateArea(5); // 直接调用，无需创建对象
}
</code></pre>
<h3 id=""><strong>六、常见问题解答</strong></h3><ol start="1"><li><p><strong>为什么构造函数没有返回类型？</strong><br/>构造函数的作用是初始化对象，不是“返回”对象，因此无需声明返回类型（包括<code>void</code>）。</p></li><li><p><strong>静态变量什么时候初始化？</strong><br/>静态变量在类加载时初始化，早于对象创建，且仅初始化一次。</p></li><li><p><strong><code>this</code>关键字的作用是什么？</strong><br/><code>this</code>指代当前对象，用于区分方法参数与类的字段（如<code>this.radius = radius;</code>）。</p></li></ol><h1 id="lecture-21---thinking-in-objects">Lecture 2.1 - Thinking in Objects</h1><h3 id=""><strong>第一部分：核心概念</strong></h3><h4 id="1-immutable-objects-and-classes"><strong>1. 不可变对象与类（Immutable Objects and Classes）</strong></h4><p><strong>定义</strong>：对象一旦创建，内容不可修改，其类称为不可变类。<br/><strong>条件</strong>（缺一不可）：</p><ol start="1"><li>所有数据字段为 <code>private</code></li><li>无 <code>set</code> 方法（mutator）</li><li>不返回可变对象的引用（如数组、自定义对象）</li></ol><p><strong>示例：不可变的 Circle 类</strong></p><pre class="language-java lang-java"><code class="language-java lang-java">public class Circle {
    private double radius;
    public Circle(double radius) { this.radius = radius; }
    public double getRadius() { return radius; } // 仅有 getter，无 setter
}
</code></pre>
<p><strong>反例：可变的 Student 类</strong></p><pre class="language-java lang-java"><code class="language-java lang-java">public class Student {
    private int id;
    private BirthDate birthDate; // BirthDate 是可变类
    public BirthDate getBirthDate() { 
        return birthDate; // 返回可变对象引用，外部可通过该引用修改内部状态
    }
}
public class BirthDate {
    private int year;
    public void setYear(int newYear) { year = newYear; } // 有 setter，可变
}
</code></pre>
<p><strong>为什么可变？</strong></p><pre class="language-java lang-java"><code class="language-java lang-java">Student student = new Student(...);
student.getBirthDate().setYear(2050); // 通过引用修改内部对象状态，导致 Student 实际可变
</code></pre>
<p><strong>练习题1</strong>：<br/>判断以下类是否为不可变类？为什么？</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Book {
    private String title;
    private Author author; // Author 类有 public 的 name 字段
    public Book(String title, Author author) {
        this.title = title;
        this.author = author;
    }
    public Author getAuthor() { return author; }
}
public class Author {
    public String name; // 字段非 private
}
</code></pre>
<h4 id="2-scope-of-variables"><strong>2. 变量作用域（Scope of Variables）</strong></h4><table><thead><tr><th> <strong>类型</strong>       </th><th> <strong>作用域</strong>                </th><th> <strong>初始化规则</strong>                </th></tr></thead><tbody><tr><td> <strong>局部变量</strong>   </td><td> 从声明处到所在代码块结束  </td><td> <strong>必须显式初始化</strong>后才能使用  </td></tr><tr><td> <strong>实例变量</strong>   </td><td> 整个类范围                </td><td> 自动赋默认值（如 <code>int=0</code>）    </td></tr><tr><td> <strong>静态变量</strong>   </td><td> 整个类范围                </td><td> 自动赋默认值                  </td></tr></tbody></table><p><strong>示例</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public class ScopeDemo {
    int instanceVar; // 实例变量，默认值 0
    static int staticVar; // 静态变量，默认值 0
    
    public void method() {
        int localVar; // 局部变量，未初始化不能使用！
        // System.out.println(localVar); // 编译错误！
        localVar = 10; // 必须先赋值
    }
}
</code></pre>
<p><strong>练习题2</strong>：<br/>指出以下代码的错误：</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Test {
    public void printSum() {
        int a = 10;
        int b;
        System.out.println(a + b); // 错误？为什么？
    }
}
</code></pre>
<h4 id="3-this-the-this-keyword"><strong>3. this 关键字（The this Keyword）</strong></h4><p><strong>作用</strong>：</p><ol start="1"><li><p><strong>引用当前对象的成员</strong>：当局部变量与成员变量同名时，用 <code>this</code> 区分。</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Person {
    private String name;
    public Person(String name) {
        this.name = name; // this.name 指成员变量，name 指参数
    }
}

</code></pre>
</li><li><p><strong>在构造方法中调用重载的构造方法</strong>：必须作为构造方法的第一条语句。</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Circle {
    private double radius;
    public Circle() { this(1.0); } // 调用 Circle(double radius)
    public Circle(double radius) { this.radius = radius; }
}

</code></pre>
</li></ol><p><strong>注意</strong>：静态方法中不能使用 <code>this</code>（静态方法属于类，不属于对象）。</p><h4 id="4-class-abstraction-and-encapsulation"><strong>4. 类的抽象与封装（Class Abstraction and Encapsulation）</strong></h4><p><strong>核心思想</strong>：</p><ul><li><strong>抽象</strong>：对外暴露功能（API），隐藏实现细节（如方法内部逻辑、数据存储方式）。</li><li><strong>封装</strong>：通过 <code>private</code> 修饰符隐藏数据字段，仅通过公共方法（getter/setter）访问。</li></ul><p><strong>示例：Loan 类的封装</strong></p><pre class="language-java lang-java"><code class="language-java lang-java">public class Loan {
    private double annualInterestRate;
    private int numberOfYears;
    
    // 构造方法与公共方法（API）
    public Loan(double rate, int years) {
        this.annualInterestRate = rate;
        this.numberOfYears = years;
    }
    public double getMonthlyPayment() { 
        // 隐藏计算逻辑，仅暴露结果
        double monthlyRate = annualInterestRate / 1200;
        return ...; 
    }
}
</code></pre>
<p><strong>优点</strong>：</p><ul><li>数据安全（防止非法修改）</li><li>易维护（修改内部实现不影响外部调用）</li></ul><h3 id=""><strong>第二部分：具体类设计实践</strong></h3><h4 id="1--loan-"><strong>1. 设计 Loan 类</strong></h4><p><strong>需求</strong>：计算贷款的月还款额和总还款额。<br/><strong>属性</strong>：年利率、贷款年限、贷款金额、创建日期<br/><strong>方法</strong>：计算月还款（<code>getMonthlyPayment</code>）、总还款（<code>getTotalPayment</code>）</p><p><strong>关键代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public double getMonthlyPayment() {
    double monthlyRate = annualInterestRate / 1200;
    double numerator = loanAmount * monthlyRate;
    double denominator = 1 - Math.pow(1 / (1 + monthlyRate), numberOfYears * 12);
    return numerator / denominator;
}
</code></pre>
<p><strong>练习题3</strong>：<br/>修改 <code>Loan</code> 类，添加一个 <code>getDailyPayment()</code> 方法，计算每日还款额（假设一年 365 天）。</p><h4 id="2--bmi-"><strong>2. 设计 BMI 类</strong></h4><p><strong>需求</strong>：计算身体质量指数（BMI）并判断健康状态。<br/><strong>公式</strong>：</p><pre class=""><code class="">BMI = 体重（磅） × 0.45359237 / (身高（英寸） × 0.0254)^2
</code></pre><p><strong>状态判断</strong>：</p><ul><li>&lt;16：严重偏瘦</li><li>16~18：偏瘦</li><li>18~24：正常</li><li>24~29：超重</li><li>≥29：严重超重</li></ul><p><strong>关键代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public String getStatus() {
    double bmi = getBMI();
    if (bmi &lt; 16) return &quot;seriously underweight&quot;;
    // ... 其他条件
}
</code></pre>
<p><strong>练习题4</strong>：<br/>修改 <code>BMI</code> 类，增加一个构造方法，允许传入体重（公斤）和身高（米），并重载 <code>getBMI()</code> 方法。</p><h3 id=""><strong>第三部分：常用类详解</strong></h3><h4 id="1-string-"><strong>1. String 类（不可变性与常用方法）</strong></h4><p><strong>不可变性</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">String s = &quot;Java&quot;;
s = s + &quot; HTML&quot;; // 原 &quot;Java&quot; 对象不变，新建 &quot;Java HTML&quot; 对象
</code></pre>
<p><strong>字符串比较</strong>：</p><table><thead><tr><th> <strong>方法</strong>               </th><th> <strong>用途</strong>          </th></tr></thead><tbody><tr><td> <code>equals()</code>           </td><td> 比较内容是否相等（区分大小写） </td></tr><tr><td> <code>==</code>                 </td><td> 比较引用是否指向同一对象    </td></tr><tr><td> <code>equalsIgnoreCase()</code> </td><td> 忽略大小写比较内容       </td></tr></tbody></table><p><strong>示例</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">String s1 = &quot;Hello&quot;;
String s2 = new String(&quot;Hello&quot;);
System.out.println(s1 == s2); // false（引用不同）
System.out.println(s1.equals(s2)); // true（内容相同）
</code></pre>
<p><strong>常用方法</strong>：</p><ul><li><p><strong>截取子串</strong>：<code>substring(start, end)</code>（左闭右开）</p><pre class="language-java lang-java"><code class="language-java lang-java">&quot;HelloWorld&quot;.substring(3, 7); // &quot;loWo&quot;

</code></pre>
</li><li><strong>查找字符/子串</strong>：<code>indexOf(&quot;sub&quot;)</code>（返回首次出现的索引，未找到返回 -1）</li><li><strong>替换</strong>：<code>replace(&quot;old&quot;, &quot;new&quot;)</code>（返回新字符串，原字符串不变）</li></ul><h4 id="2-stringbuilder--stringbuffer"><strong>2. StringBuilder 与 StringBuffer</strong></h4><p><strong>区别</strong>：</p><ul><li><code>StringBuilder</code>：非线程安全，效率高（推荐单线程使用）</li><li><code>StringBuffer</code>：线程安全，效率低（适合多线程环境）</li></ul><p><strong>核心操作</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">StringBuilder sb = new StringBuilder(&quot;Java&quot;);
sb.append(&quot; HTML&quot;); // 追加 → &quot;Java HTML&quot;
sb.insert(4, &quot; and &quot;); // 插入 → &quot;Java and HTML&quot;
sb.delete(0, 4); // 删除前 4 个字符 → &quot; and HTML&quot;
sb.reverse(); // 反转 → &quot;MLTH and &quot;
</code></pre>
<p><strong>何时使用？</strong>：需要频繁修改字符串时（如循环拼接），用 <code>StringBuilder</code> 代替 <code>String</code>，避免生成大量临时对象。</p><h4 id="3-regular-expressions"><strong>3. 正则表达式（Regular Expressions）</strong></h4><p><strong>作用</strong>：用于字符串匹配、验证、替换、分割。<br/><strong>示例</strong>：</p><ul><li><strong>验证邮箱</strong>：<code>^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$</code></li><li><strong>提取数字</strong>：<code>String[] nums = &quot;a1b2c3&quot;.split(&quot;\D&quot;); // [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;]</code></li><li><strong>替换特殊字符</strong>：<code>&quot;a$b#c&quot;.replaceAll(&quot;[\$#]&quot;, &quot;X&quot;); // &quot;aXbXc&quot;</code></li></ul><p><strong>练习题5</strong>：<br/>编写正则表达式，验证手机号（以 1开头，第二位 3-9，共11位数字）。</p><h3 id=""><strong>第四部分：综合练习</strong></h3><h4 id="-course-"><strong>设计 Course 类</strong></h4><p><strong>需求</strong>：管理课程的学生列表，支持添加学生、获取学生列表、动态扩容。<br/><strong>属性</strong>：课程名、学生数组、学生人数<br/><strong>关键代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Course {
    private String courseName;
    private String[] students = new String[10]; // 初始容量 10
    private int numberOfStudents;
    
    public void addStudent(String student) {
        if (numberOfStudents &gt;= students.length) {
            String[] newStudents = new String[students.length * 2];
            System.arraycopy(students, 0, newStudents, 0, students.length);
            students = newStudents; // 扩容为原来的 2 倍
        }
        students[numberOfStudents++] = student;
    }
}
</code></pre>
<h1 id="lecture-22---inheritance-and-polymorphism">Lecture 2.2 - Inheritance and Polymorphism</h1><h3 id=""><strong>一、继承的基本概念</strong></h3><h4 id="1-"><strong>1. 为什么需要继承？</strong></h4><ul><li><strong>动机</strong>：当多个类（如Circle、Rectangle）有共同属性（color、filled）和方法（getArea、getPerimeter）时，通过继承可以避免代码冗余。
<ul><li><strong>父类（超类）</strong>：提取公共属性和方法，如<code>GeometricObject</code>。</li><li><strong>子类</strong>：继承父类，并添加特有的属性和方法，如<code>Circle</code>添加<code>radius</code>和<code>getDiameter</code>。</li></ul></li></ul><h4 id="2-"><strong>2. 如何声明子类？</strong></h4><ul><li><p><strong>语法</strong>：使用<code>extends</code>关键字。</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Circle extends GeometricObject {
    private double radius; // 子类特有的属性
    // 子类特有的方法
    public double getDiameter() { return 2 * radius; }
}

</code></pre>
</li><li><p><strong>子类继承父类的哪些成员？</strong></p><ul><li><strong>继承</strong>：非<code>private</code>的属性和方法（如<code>color</code>是<code>private</code>，需通过<code>getColor()</code>访问）。</li><li><strong>不继承</strong>：构造方法（需显式或隐式调用父类构造方法）。</li></ul></li></ul><h3 id="super"><strong>二、构造函数与<code>super</code>关键字</strong></h3><h4 id="1-constructor-chaining"><strong>1. 构造函数链（Constructor Chaining）</strong></h4><ul><li><p><strong>规则</strong>：子类构造函数必须先调用父类构造函数（通过<code>super()</code>或<code>this()</code>），默认调用父类无参构造函数。</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Rectangle extends GeometricObject {
    public Rectangle(double width, double height) {
        super(); // 隐式调用父类无参构造函数（可省略）
        this.width = width;
        this.height = height;
    }
    public Rectangle(double width, double height, String color, boolean filled) {
        super(color, filled); // 显式调用父类有参构造函数
        this.width = width;
        this.height = height;
    }
}

</code></pre>
</li><li><p><strong>示例分析</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">class Person { public Person() { System.out.println(&quot;Person&quot;); } }
class Employee extends Person { public Employee() { super(); System.out.println(&quot;Employee&quot;); } }
class Faculty extends Employee { public Faculty() { super(); System.out.println(&quot;Faculty&quot;); } }
// 执行 new Faculty() 时输出：Person → Employee → Faculty

</code></pre>
</li></ul><h4 id="2-super"><strong>2. <code>super</code>关键字的作用</strong></h4><ul><li><strong>调用父类构造函数</strong>：必须作为子类构造函数的第一行。</li><li><p><strong>调用父类被重写的方法</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Circle {
    @Override
    public String toString() {
        return &quot;Circle radius: &quot; + radius + &quot;, &quot; + super.toString(); // 调用父类的toString()
    }
}

</code></pre>
</li></ul><h3 id="overrideoverload"><strong>三、方法重写（Override）与重载（Overload）</strong></h3><h4 id="1-override"><strong>1. 方法重写（Override）</strong></h4><ul><li><p><strong>定义</strong>：子类重新实现父类的非<code>private</code>、非<code>static</code>、非<code>final</code>方法，<strong>方法签名必须完全一致</strong>。</p><pre class="language-java lang-java"><code class="language-java lang-java">public class GeometricObject {
    public abstract double getArea(); // 抽象方法，子类必须重写
}
public class Circle {
    @Override // 注解确保正确重写
    public double getArea() { return Math.PI * radius * radius; }
}

</code></pre>
</li><li><p><strong>应用场景</strong>：多态的基础（后文会讲）。</p></li></ul><h4 id="2-overload"><strong>2. 方法重载（Overload）</strong></h4><ul><li><p><strong>定义</strong>：同一类中，方法名相同但<strong>参数列表不同</strong>（类型、数量、顺序），与返回值无关。</p><pre class="language-java lang-java"><code class="language-java lang-java">public class Calculator {
    public int add(int a, int b) { return a + b; } // 重载1
    public double add(double a, double b) { return a + b; } // 重载2
}

</code></pre>
</li></ul><h4 id="3-"><strong>3. 对比表格</strong></h4><table><thead><tr><th> <strong>特征</strong>         </th><th> <strong>重写（Override）</strong>                </th><th> <strong>重载（Overload）</strong>                </th></tr></thead><tbody><tr><td> <strong>发生范围</strong>     </td><td> 子类与父类之间                      </td><td> 同一类中                            </td></tr><tr><td> <strong>方法签名</strong>     </td><td> 必须相同                            </td><td> 必须不同（参数列表）                </td></tr><tr><td> <strong>访问权限</strong>     </td><td> 子类方法不能比父类更严格（如父类<code>public</code>，子类不能是<code>protected</code>） </td><td> 无限制                              </td></tr><tr><td> <strong>多态性</strong>       </td><td> 支持（动态绑定）                    </td><td> 不支持（静态绑定）                  </td></tr></tbody></table><p><strong>练习1</strong>：判断以下代码是重写还是重载？</p><pre class="language-java lang-java"><code class="language-java lang-java">class A { public void m(int x) { } }
class B extends A { public void m(double x) { } } // 重载（参数类型不同）
class C extends A { public void m(int x) { } } // 重写（方法签名相同）
</code></pre>
<h3 id="polymorphismdynamic-binding"><strong>四、多态（Polymorphism）与动态绑定（Dynamic Binding）</strong></h3><h4 id="1-"><strong>1. 多态的本质</strong></h4><ul><li><p><strong>定义</strong>：父类引用可以指向子类对象，运行时根据实际对象类型调用方法。</p><pre class="language-java lang-java"><code class="language-java lang-java">GeometricObject obj1 = new Circle(5); // 父类引用指向子类对象
GeometricObject obj2 = new Rectangle(5, 3);

</code></pre>
</li><li><p><strong>优势</strong>：代码通用性强，如<code>displayGeometricObject</code>方法可接收任何<code>GeometricObject</code>子类对象。</p><pre class="language-java lang-java"><code class="language-java lang-java">public static void displayGeometricObject(GeometricObject obj) {
    System.out.println(obj.getArea()); // 动态绑定：根据obj实际类型调用Circle或Rectangle的getArea()
}

</code></pre>
</li></ul><h4 id="2-"><strong>2. 动态绑定机制</strong></h4><ul><li><strong>过程</strong>：JVM在运行时根据对象的实际类型（而非引用类型）确定调用哪个方法。
<ul><li>若对象是<code>Circle</code>，调用<code>Circle</code>的<code>getArea()</code>；若是<code>Rectangle</code>，调用<code>Rectangle</code>的<code>getArea()</code>。</li></ul></li><li><p><strong>示例</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">class Person { public String toString() { return &quot;Person&quot;; } }
class Student extends Person { @Override public String toString() { return &quot;Student&quot;; } }
public static void main(String[] args) {
    Person p = new Student(); // 父类引用指向子类对象
    System.out.println(p.toString()); // 输出：Student（动态绑定）
}

</code></pre>
</li></ul><h3 id="instanceof"><strong>五、类型转换与<code>instanceof</code>操作符</strong></h3><h4 id="1-upcasting"><strong>1. 向上转型（Upcasting）</strong></h4><ul><li><p><strong>自动转换</strong>：子类对象 → 父类引用（安全，因为子类是父类的一种）。</p><pre class="language-java lang-java"><code class="language-java lang-java">Circle circle = new Circle(5);
GeometricObject geo = circle; // 向上转型（自动）

</code></pre>
</li></ul><h4 id="2-downcasting"><strong>2. 向下转型（Downcasting）</strong></h4><ul><li><p><strong>强制转换</strong>：父类引用 → 子类对象（需确保引用实际指向子类对象，否则抛出<code>ClassCastException</code>）。</p><pre class="language-java lang-java"><code class="language-java lang-java">GeometricObject geo = new Circle(5);
Circle circle = (Circle) geo; // 向下转型（安全，因为实际是Circle对象）

</code></pre>
</li></ul><h4 id="3-instanceof"><strong>3. <code>instanceof</code>操作符</strong></h4><ul><li><p><strong>作用</strong>：在向下转型前检查对象类型，避免异常。</p><pre class="language-java lang-java"><code class="language-java lang-java">if (geo instanceof Circle) { // 先判断是否是Circle类型
    Circle circle = (Circle) geo;
    System.out.println(circle.getRadius());
}

</code></pre>
</li></ul><h3 id="object"><strong>六、<code>Object</code>类的常用方法</strong></h3><h4 id="1-tostring"><strong>1. <code>toString()</code>方法</strong></h4><ul><li><strong>默认实现</strong>：返回<code>类名@哈希码</code>（如<code>Circle@123456</code>），无实际意义。</li><li><p><strong>重写建议</strong>：返回对象属性的字符串表示。</p><pre class="language-java lang-java"><code class="language-java lang-java">@Override
public String toString() {
    return &quot;Circle[radius=&quot; + radius + &quot;, color=&quot; + getColor() + &quot;]&quot;;
}

</code></pre>
</li></ul><h4 id="2-equals"><strong>2. <code>equals()</code>方法</strong></h4><ul><li><strong>默认实现</strong>：比较对象引用地址（<code>this == obj</code>），而非内容。</li><li><p><strong>重写逻辑</strong>：</p><ol start="1"><li>判断是否为同一对象（<code>this == obj</code>）。</li><li>判断<code>obj</code>是否为<code>null</code>或类型是否匹配（用<code>instanceof</code>）。</li><li>比较关键属性是否相等。</li></ol><pre class="language-java lang-java"><code class="language-java lang-java">@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return false;
    Circle other = (Circle) obj;
    return Double.compare(other.radius, radius) == 0;
}

</code></pre>
</li></ul><h3 id="arraylistmystack"><strong>七、容器类：<code>ArrayList</code>与自定义栈（MyStack）</strong></h3><h4 id="1-arraylist"><strong>1. <code>ArrayList</code>的优势</strong></h4><ul><li><strong>动态扩容</strong>：无需指定初始大小，自动扩展。</li><li><p><strong>泛型支持</strong>：避免类型转换警告，如<code>ArrayList&amp;lt;Circle&amp;gt;</code>只能存储<code>Circle</code>对象。</p><pre class="language-java lang-java"><code class="language-java lang-java">ArrayList&amp;lt;Circle&amp;gt; circles = new ArrayList&lt;&gt;();
circles.add(new Circle(2));
Circle c = circles.get(0); // 无需强制转换

</code></pre>
</li></ul><h4 id="2-mystack"><strong>2. 自定义栈（MyStack）</strong></h4><ul><li><p><strong>底层实现</strong>：用<code>ArrayList</code>存储元素，实现栈的<code>push</code>（入栈）、<code>pop</code>（出栈）等操作。</p><pre class="language-java lang-java"><code class="language-java lang-java">public class MyStack {
    private ArrayList&amp;lt;Object&amp;gt; list = new ArrayList&lt;&gt;();
    public void push(Object o) { list.add(o); } // 入栈：添加到末尾
    public Object pop() { return list.remove(list.size() - 1); } // 出栈：移除末尾元素
}

</code></pre>
</li></ul><h3 id="protectedfinal"><strong>八、访问修饰符：<code>protected</code>与<code>final</code></strong></h3><h4 id="1-protected"><strong>1. <code>protected</code>修饰符</strong></h4><ul><li><strong>作用</strong>：允许同一包内的类和子类（即使不同包）访问。</li><li><strong>对比表格</strong>：</li></ul><table><thead><tr><th> 修饰符         </th><th> 同一类 </th><th> 同一包 </th><th> 子类（不同包） </th><th> 其他包 </th></tr></thead><tbody><tr><td> <code>public</code>    </td><td> ✅   </td><td> ✅   </td><td> ✅       </td><td> ✅   </td></tr><tr><td> <code>protected</code> </td><td> ✅   </td><td> ✅   </td><td> ✅       </td><td> ❌   </td></tr><tr><td> <code>默认</code>        </td><td> ✅   </td><td> ✅   </td><td> ❌       </td><td> ❌   </td></tr><tr><td> <code>private</code>   </td><td> ✅   </td><td> ❌   </td><td> ❌       </td><td> ❌   </td></tr></tbody></table><h4 id="2-final"><strong>2. <code>final</code>修饰符</strong></h4><ul><li><strong><code>final</code>变量</strong>：常量，不可修改（如<code>final static double PI = 3.14;</code>）。</li><li><strong><code>final</code>方法</strong>：不可被重写（防止子类修改核心逻辑）。</li><li><strong><code>final</code>类</strong>：不可被继承（如<code>String</code>类）。</li></ul><h3 id=""><strong>九、课堂测验与巩固</strong></h3><h4 id="1"><strong>测验1：继承与构造函数</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">class A { public A() { System.out.print(&quot;A&quot;); } public A(int x) { System.out.print(&quot;B&quot;); } }
class B extends A { public B() { super(1); } }
new B(); // 输出？
</code></pre><p><strong>答案</strong>：<code>B</code>（子类构造函数调用<code>super(1)</code>，执行父类<code>A(int x)</code>构造函数）。</p><h4 id="2"><strong>测验2：重写与多态</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">class Animal { public void speak() { System.out.println(&quot;Animal&quot;); } }
class Dog extends Animal { @Override public void speak() { System.out.println(&quot;Woof&quot;); } }
public static void main(String[] args) {
    Animal a = new Dog();
    a.speak(); // 输出？
}
</code></pre><p><strong>答案</strong>：<code>Woof</code>（动态绑定，调用<code>Dog</code>的<code>speak()</code>）。</p>
<h1 id="lecture-3---abstract-classes-and-interfaces">Lecture 3 - Abstract Classes and Interfaces</h1><h3 id=""><strong>一、核心知识点总结</strong></h3><h4 id="1-abstract-classes"><strong>1. 抽象类（Abstract Classes）</strong></h4><ul><li><strong>定义</strong>：用 <code>abstract</code> 修饰的类，包含抽象方法（<code>abstract</code> 方法，无方法体）和具体方法。
<ul><li>例：<code>GeometricObject</code> 是抽象类，包含 <code>getArea()</code>、<code>getPerimeter()</code> 抽象方法。</li></ul></li><li><strong>特点</strong>：
<ul><li>不能直接实例化（不能用 <code>new</code> 创建对象），但可作为父类被继承。</li><li>非抽象子类必须实现父类的所有抽象方法。</li><li>抽象类可包含构造方法，供子类通过 <code>super()</code> 调用。</li></ul></li><li><strong>用途</strong>：定义通用模板，强制子类实现特定方法（如几何图形的面积计算）。</li></ul><h4 id="2-interfaces"><strong>2. 接口（Interfaces）</strong></h4><ul><li><strong>定义</strong>：用 <code>interface</code> 声明，仅包含 <strong>抽象方法</strong> 和 <strong>常量</strong>（默认 <code>public static final</code>）。
<ul><li>例：<code>Edible</code> 接口定义 <code>howToEat()</code> 方法，<code>Comparable</code> 接口定义 <code>compareTo()</code> 方法。</li></ul></li><li><strong>特点</strong>：
<ul><li>类通过 <code>implements</code> 实现接口，需重写所有接口方法（默认 <code>public abstract</code>）。</li><li>支持多继承（一个类可实现多个接口），弥补Java单继承限制。</li><li>接口可继承其他接口（<code>interface A extends B, C</code>）。</li></ul></li><li><strong>用途</strong>：定义行为规范（如“可比较”“可克隆”），解耦实现与接口。</li></ul><h4 id="3-"><strong>3. 关键接口示例</strong></h4><ul><li><strong><code>Comparable</code> 接口</strong>：
<ul><li>用于对象排序（如 <code>Arrays.sort()</code>），需实现 <code>compareTo(Object o)</code> 方法。</li><li>例：自定义 <code>ComparableRectangle</code> 类，通过面积比较大小。</li></ul></li><li><strong><code>Cloneable</code> 接口</strong>：
<ul><li>标记接口（无方法），允许对象通过 <code>clone()</code> 方法克隆。</li><li>注意：<code>Object.clone()</code> 是浅拷贝，深拷贝需手动重写 <code>clone()</code>。</li></ul></li></ul><h4 id="4--vs-"><strong>4. 抽象类 vs. 接口</strong></h4><table><thead><tr><th> <strong>对比维度</strong>       </th><th> <strong>抽象类</strong>                     </th><th> <strong>接口</strong>                         </th></tr></thead><tbody><tr><td> <strong>成员</strong>           </td><td> 可包含抽象方法、具体方法、变量 </td><td> 只能包含抽象方法和常量（<code>public static final</code>） </td></tr><tr><td> <strong>继承/实现</strong>      </td><td> 单继承（<code>extends</code>）            </td><td> 多实现（<code>implements</code>）           </td></tr><tr><td> <strong>构造方法</strong>       </td><td> 有（供子类调用）               </td><td> 无                               </td></tr><tr><td> <strong>实例化</strong>         </td><td> 不能直接实例化                 </td><td> 不能直接实例化                   </td></tr><tr><td> <strong>设计目的</strong>       </td><td> 定义类的模板或部分实现         </td><td> 定义行为规范（“能做什么”）       </td></tr></tbody></table>
<h4 id="5-wrapper-classes"><strong>5. 包装类（Wrapper Classes）</strong></h4><ul><li><strong>作用</strong>：将基本数据类型（如 <code>int</code>、<code>double</code>）封装为对象，便于集合类使用。</li><li><strong>常见类</strong>：<code>Integer</code>、<code>Double</code>、<code>Boolean</code> 等，均继承自 <code>Number</code> 类。</li><li><p><strong>特性</strong>：</p><ul><li>不可变（对象创建后值不可改）。</li><li><p>支持自动装箱/拆箱（JDK 1.5+）：</p><pre class="language-java lang-java"><code class="language-java lang-java">Integer num = 10; // 自动装箱（int → Integer）
int n = num;      // 自动拆箱（Integer → int）

</code></pre>
</li></ul></li><li><p><strong>常用方法</strong>：</p><ul><li>转换：<code>parseInt(String s)</code>、<code>doubleValue()</code>。</li><li>比较：<code>compareTo()</code>（实现 <code>Comparable</code> 接口）。</li></ul></li></ul><h4 id="6-biginteger--bigdecimal"><strong>6. 大数类（BigInteger &amp; BigDecimal）</strong></h4><ul><li><strong>用途</strong>：处理超出基本类型范围的大整数（<code>BigInteger</code>）或高精度小数（<code>BigDecimal</code>）。</li><li><p><strong>特点</strong>：</p><ul><li>不可变，方法返回新对象（如 <code>add()</code>、<code>multiply()</code>）。</li><li><p><code>BigDecimal</code> 需指定精度和舍入模式：</p><pre class="language-java lang-java"><code class="language-java lang-java">BigDecimal result = a.divide(b, 20, BigDecimal.ROUND_HALF_UP);

</code></pre>
</li></ul></li></ul><h4 id="7-rational"><strong>7. 案例：Rational类（有理数）</strong></h4><ul><li><strong>功能</strong>：实现有理数的加减乘除、比较和类型转换（继承 <code>Number</code> 接口）。</li><li><strong>关键点</strong>：
<ul><li>重写 <code>compareTo()</code> 实现比较逻辑。</li><li>使用 <code>gcd()</code> 方法约分，确保分母为正。</li></ul></li></ul><h3 id=""><strong>二、常见问题与解答</strong></h3><ol start="1"><li><strong>Q：抽象类可以没有抽象方法吗？</strong><br/>A：可以。抽象类的存在意义是禁止实例化，即使没有抽象方法，也可作为基类强制子类继承。</li><li><strong>Q：接口能继承类吗？</strong><br/>A：不能。接口只能继承其他接口（<code>interface A extends B</code>），类通过 <code>implements</code> 实现接口。</li><li><strong>Q：为什么包装类是不可变的？</strong><br/>A：为保证线程安全和哈希值稳定（如作为 <code>HashMap</code> 键时），包装类的内部状态不可修改。</li><li><strong>Q：深拷贝和浅拷贝的实现区别？</strong><br/>A：浅拷贝直接复制引用（<code>super.clone()</code>），深拷贝需手动创建新对象并复制引用字段（如 <code>h.whenBuilt = (Date)whenBuilt.clone();</code>）。</li></ol><h1 id="lecture-4---generics">Lecture 4 - Generics</h1><h3 id="">一、泛型基础：为何需要泛型？</h3><h4 id="">核心作用</h4><p>泛型能够将<strong>类型检查的阶段提前到编译期</strong>，避免在运行时出现类型错误，同时还能实现代码的复用。下面通过一个示例来对比说明：</p><ul><li><p><strong>无泛型（存在风险）</strong></p><pre class="language-java lang-java"><code class="language-java lang-java">ArrayList list = new ArrayList();
list.add(&quot;1&quot;); // 向列表中添加字符串
Integer i = (Integer) list.get(0); // 运行时会抛出 ClassCastException 异常

</code></pre><p><strong>存在的问题</strong>：在运行时才能发现类型不匹配的问题。</p></li><li><p><strong>有泛型（更安全）</strong></p><pre class="language-java lang-java"><code class="language-java lang-java">ArrayList&amp;lt;Integer&amp;gt; list = new ArrayList&lt;&gt;(); // JDK 1.7 及之后版本支持钻石语法
list.add(&quot;1&quot;); // 编译时就会报错，提示类型不匹配
list.add(1); // 正确操作，自动装箱
Integer i = list.get(0); // 无需进行强制类型转换

</code></pre><p><strong>优势体现</strong>：在编译阶段就能捕获错误，代码的可读性也更强。</p></li></ul><h4 id="">关键概念</h4><p>泛型的本质是对类型进行参数化。就像定义一个“模板”，在使用的时候再指定具体的类型。例如：</p><ul><li><strong>泛型类模板</strong>：<code>class GenericStack&amp;lt;E&amp;gt;</code> 
<ul><li><code>E</code>是类型参数（也被称为类型变量），在创建实例时，需要用具体的类型（如<code>Integer</code>、<code>String</code>）来替换它。</li></ul></li><li><strong>实例化方式</strong>：<code>GenericStack&amp;lt;String&amp;gt; stack = new GenericStack&lt;&gt;();</code></li></ul><h3 id="">二、动手实践：定义泛型类与接口</h3><h4 id="genericstack">案例：自定义泛型栈（GenericStack）</h4><pre class="language-java lang-java"><code class="language-java lang-java">public class GenericStack&amp;lt;E&amp;gt; {
    private ArrayList&amp;lt;E&amp;gt; list = new ArrayList&lt;&gt;(); // 使用 E 作为元素类型

    public void push(E o) { list.add(o); } // 只能添加 E 类型的元素
    public E pop() { return list.remove(list.size() - 1); } // 返回 E 类型的元素
    // 其他方法如 peek()、isEmpty() 等的返回类型或参数类型均为 E
}
</code></pre><p><strong>使用方式</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">GenericStack&amp;lt;Integer&amp;gt; intStack = new GenericStack&lt;&gt;();
intStack.push(10); // 自动装箱为 Integer
intStack.push(20);
System.out.println(intStack.pop()); // 输出：20（类型为 Integer）
</code></pre>
<h4 id="-1">练习 1</h4><p>尝试定义一个泛型队列<code>GenericQueue&amp;lt;E&amp;gt;</code>，要求包含<code>enqueue(E element)</code>（入队）和<code>dequeue()</code>（出队）方法。</p><h3 id="">三、深入探究：泛型方法与有界类型</h3><h4 id="1-">1. 泛型方法</h4><p>泛型方法可以独立于类而存在，在方法名前使用<code>&amp;lt;T&amp;gt;</code>来声明类型参数。</p><pre class="language-java lang-java"><code class="language-java lang-java">public static &amp;lt;E&amp;gt; void printArray(E[] array) { // 适用于任何类型的数组
    for (E element : array) {
        System.out.print(element + &quot; &quot;);
    }
}
// 使用示例
Integer[] intArray = {1, 2, 3};
printArray(intArray); // 输出：1 2 3
</code></pre>
<h4 id="2-bounded-type-parameters">2. 有界类型参数（Bounded Type Parameters）</h4><p>通过<code>extends</code>关键字来限制类型参数的范围，例如<code>&amp;lt;E extends Comparable&lt;E&amp;gt;&gt;</code>表示<code>E</code>必须实现<code>Comparable</code>接口。
<strong>案例：排序方法</strong></p><pre class="language-java lang-java"><code class="language-java lang-java">public static &amp;lt;E extends Comparable&lt;E&amp;gt;&gt; void sort(E[] array) {
    // 使用 compareTo() 方法进行比较排序
    for (int i = 0; i &lt; array.length - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j &lt; array.length; j++) {
            if (array[j].compareTo(array[minIndex]) &lt; 0) {
                minIndex = j;
            }
        }
        // 交换元素
        E temp = array[i];
        array[i] = array[minIndex];
        array[minIndex] = temp;
    }
}
// 适用类型：Integer、String（因为它们都实现了 Comparable 接口）
</code></pre>
<h4 id="-2">练习 2</h4><p>定义一个泛型方法<code>max(E a, E b)</code>，用于返回两个可比较对象中的最大值，要求使用有界类型参数。</p><h3 id="wildcards">四、通配符（Wildcards）：解决类型兼容性问题</h3><p>通配符用于处理泛型类型之间的继承关系，常见的有以下三种：</p><table><thead><tr><th> 通配符          </th><th> 含义                                                                 </th></tr></thead><tbody><tr><td> <code>?</code>             </td><td> 无界通配符，表示任意类型                                             </td></tr><tr><td> <code>? extends T</code>    </td><td> 上界通配符，表示类型必须是<code>T</code>的子类（包括<code>T</code>本身）                    </td></tr><tr><td> <code>? super T</code>      </td><td> 下界通配符，表示类型必须是<code>T</code>的父类（包括<code>T</code>本身）                    </td></tr></tbody></table><h4 id="">案例：计算栈中数字的最大值</h4><pre class="language-java lang-java"><code class="language-java lang-java">public static double max(GenericStack&lt;? extends Number&gt; stack) { // 接受 Number 及其子类（如 Integer、Double）
    double max = stack.pop().doubleValue();
    while (!stack.isEmpty()) {
        double value = stack.pop().doubleValue();
        if (value &gt; max) max = value;
    }
    return max;
}
// 使用示例
GenericStack&amp;lt;Integer&amp;gt; intStack = new GenericStack&lt;&gt;();
intStack.push(10);
intStack.push(20);
System.out.println(max(intStack)); // 输出：20.0
</code></pre>
<h4 id="">注意事项</h4><ul><li><strong>父子类型关系</strong>：<code>GenericStack&amp;lt;Integer&amp;gt;</code><strong>不是</strong><code>GenericStack&amp;lt;Number&amp;gt;</code>的子类，但<code>GenericStack&amp;lt;Integer&amp;gt;</code>是<code>GenericStack&lt;? extends Number&gt;</code>的子类。</li><li><strong>写入限制</strong>：使用<code>? extends T</code>时，无法向集合中写入元素（除了<code>null</code>），因为类型不确定；使用<code>? super T</code>时，允许写入<code>T</code>及其子类元素。</li></ul><h4 id="-3">练习 3</h4><p>思考以下代码是否能编译通过，并解释原因：</p><pre class="language-java lang-java"><code class="language-java lang-java">GenericStack&lt;? extends Number&gt; stack = new GenericStack&amp;lt;Integer&amp;gt;();
stack.push(new Integer(1)); // 能否编译通过？NO
stack.push(new Number(1) {}); // 能否编译通过？NO
</code></pre>
<h3 id="type-erasurejava-">五、类型擦除（Type Erasure）：Java 泛型的实现原理</h3><h4 id="">核心机制</h4><ul><li>编译器会在编译阶段移除泛型类型信息，将其替换为原始类型（如<code>E</code>替换为<code>Object</code>）。</li><li><p>因此，<strong>泛型类型在运行时并不存在</strong>，例如：</p><pre class="language-java lang-java"><code class="language-java lang-java">System.out.println(new GenericStack&amp;lt;Integer&amp;gt;().getClass() == new GenericStack&amp;lt;String&amp;gt;().getClass()); // 输出：true

</code></pre><p>这是因为擦除后两者的类型都是<code>GenericStack</code>（原始类型）。</p></li></ul><h4 id="">限制条件</h4><p>由于类型擦除的存在，泛型有以下限制：</p><ol start="1"><li><strong>不能实例化泛型类型</strong>：<code>new E()</code>是不允许的，因为运行时<code>E</code>会被擦除为<code>Object</code>，无法确定具体类型。</li><li><strong>静态成员不能使用类型参数</strong>：静态变量或方法属于类级别，而类型参数是实例级别的。</li><li><strong>数组操作受限</strong>：不能创建泛型数组，如<code>E[] array = new E[10];</code>，但可以使用<code>ArrayList&amp;lt;E&amp;gt;</code>。</li></ol><h3 id="">六、实战应用：设计泛型矩阵类</h3><p>文档中提供了<code>GenericMatrix&amp;lt;E&amp;gt;</code>的示例，它是一个抽象类，通过泛型实现了矩阵的加法和乘法运算，具体的元素操作由子类（如<code>IntegerMatrix</code>、<code>RationalMatrix</code>）来实现。</p><pre class="language-java lang-java"><code class="language-java lang-java">// 抽象泛型矩阵类
public abstract class GenericMatrix&amp;lt;E extends Number&amp;gt; {
    protected abstract E add(E a, E b); // 元素加法
    protected abstract E multiply(E a, E b); // 元素乘法
    protected abstract E zero(); // 返回零值

    public E[][] addMatrix(E[][] m1, E[][] m2) {
        // 实现矩阵加法，调用 add() 方法
    }
}

// 整数矩阵子类
public class IntegerMatrix extends GenericMatrix&amp;lt;Integer&amp;gt; {
    @Override
    protected Integer add(Integer a, Integer b) { return a + b; }
    @Override
    protected Integer multiply(Integer a, Integer b) { return a * b; }
    @Override
    protected Integer zero() { return 0; }
}
</code></pre>
<h4 id="-4">练习 4</h4><p>尝试为<code>GenericMatrix</code>添加一个<code>RationalMatrix</code>子类，用于处理有理数矩阵的运算（有理数类<code>Rational</code>已在文档中定义）。</p><h3 id="">七、常见问题与最佳实践</h3><ol start="1"><li><strong>原始类型（Raw Types）</strong>：为了兼容旧代码，可以使用原始类型（如<code>ArrayList</code>），但这会失去泛型的类型安全保障，不建议在新代码中使用。</li><li><strong>优先使用泛型集合</strong>：避免使用<code>ArrayList</code>，而应使用<code>ArrayList&amp;lt;String&amp;gt;</code>等参数化类型。</li><li><strong>合理使用通配符</strong>：在需要处理类型层次结构时，使用通配符来提高代码的灵活性。</li></ol><h3 id="">总结：知识图谱</h3><pre class=""><code class="">泛型
├─ 核心优势：编译时类型检查、代码复用
├─ 基础语法：泛型类/接口（GenericStack&amp;lt;E&amp;gt;）、泛型方法（&amp;lt;T&amp;gt; void print(T[] arr)）
├─ 类型参数：无界类型（E）、有界类型（E extends Comparable&amp;lt;E&amp;gt;）
├─ 通配符：?、? extends T、? super T
├─ 实现原理：类型擦除、原始类型
└─ 实战：自定义泛型数据结构、泛型算法（排序、矩阵运算）
</code></pre>
<h1 id="lecture-5---lists-stacks-queues-and-priority-queues">Lecture 5 - Lists, Stacks, Queues, and Priority Queues</h1><h3 id="java"><strong>第一部分：Java集合框架概述</strong></h3><h4 id="1-"><strong>1. 集合框架的核心结构</strong></h4><p>Java集合框架（Java Collections Framework, JCF）是一组用于存储和操作数据的API，包含<strong>接口</strong>、<strong>抽象类</strong>和<strong>具体类</strong>。</p><ul><li><strong>接口</strong>：定义操作规范（如<code>Collection</code>、<code>List</code>、<code>Queue</code>）。</li><li><strong>抽象类</strong>：提供部分实现（如<code>AbstractCollection</code>、<code>AbstractList</code>）。</li><li><strong>具体类</strong>：实现具体数据结构（如<code>ArrayList</code>、<code>LinkedList</code>、<code>PriorityQueue</code>）。</li></ul><h4 id="2-"><strong>2. 核心接口分类</strong></h4><ul><li><strong>单元素集合</strong>（<code>Collection</code>）：存储独立元素，分为：<br/><ul><li><strong>有序列表</strong>（<code>List</code>）：允许重复，有序（如<code>ArrayList</code>、<code>LinkedList</code>）。</li><li><strong>集合</strong>（<code>Set</code>）：不允许重复（如<code>HashSet</code>、<code>TreeSet</code>）。</li><li><strong>队列</strong>（<code>Queue</code>）：先进先出（FIFO）或优先级排序（如<code>PriorityQueue</code>）。</li></ul></li><li><strong>键值对集合</strong>（<code>Map</code>）：存储键值对（如<code>HashMap</code>、<code>TreeMap</code>）。</li></ul><h3 id="list"><strong>第二部分：列表（List）</strong></h3><h4 id="1-list"><strong>1. List接口特点</strong></h4><ul><li><strong>有序性</strong>：元素按插入顺序存储，可通过索引访问。</li><li><strong>允许重复</strong>：可存储相同元素。</li><li><strong>核心方法</strong>：<br/><ul><li><code>add(index, element)</code>：在指定位置插入元素。</li><li><code>get(index)</code>：获取指定索引的元素。</li><li><code>remove(index)</code>：删除指定索引的元素。</li><li><code>size()</code>：获取元素个数。</li></ul></li></ul><h4 id="2-arraylist-vs-linkedlist"><strong>2. 实现类：ArrayList vs. LinkedList</strong></h4><table><thead><tr><th> <strong>特性</strong>         </th><th> <strong>ArrayList</strong>                          </th><th> <strong>LinkedList</strong>                         </th></tr></thead><tbody><tr><td> <strong>数据结构</strong>     </td><td> 动态数组                               </td><td> 双向链表                               </td></tr><tr><td> <strong>随机访问</strong>     </td><td> 快（O(1)）                             </td><td> 慢（O(n)，需遍历链表）                 </td></tr><tr><td> <strong>插入/删除</strong>    </td><td> 尾部快，中间/头部慢（需移动元素）      </td><td> 快（O(1)，仅需修改指针）               </td></tr><tr><td> <strong>适用场景</strong>     </td><td> 频繁查询、较少增删                     </td><td> 频繁增删、首尾操作                     </td></tr></tbody></table>
<h4 id="3-list"><strong>3. 示例代码：List基本操作</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">import java.util.ArrayList;
import java.util.List;

public class ListDemo {
    public static void main(String[] args) {
        // 创建ArrayList
        List&amp;lt;String&amp;gt; list = new ArrayList&lt;&gt;();
        
        // 添加元素
        list.add(&quot;Apple&quot;);
        list.add(&quot;Banana&quot;);
        list.add(1, &quot;Cherry&quot;); // 在索引1处插入元素
        
        // 遍历列表
        System.out.println(&quot;遍历列表（for-each）:&quot;);
        for (String item : list) {
            System.out.print(item + &quot; &quot;); // 输出：Apple Cherry Banana
        }
        
        // 删除元素
        list.remove(&quot;Banana&quot;);
        System.out.println(&quot;\n删除后的列表: &quot; + list); // 输出：[Apple, Cherry]
        
        // 获取元素
        String firstItem = list.get(0);
        System.out.println(&quot;第一个元素: &quot; + firstItem); // 输出：Apple
    }
}
</code></pre>
<h3 id="stack"><strong>第三部分：栈（Stack）</strong></h3><h4 id="1-"><strong>1. 栈的特点</strong></h4><ul><li><strong>后进先出（LIFO）</strong>：最后插入的元素最先取出。</li><li><strong>核心方法</strong>：<br/><ul><li><code>push(element)</code>：压入元素（栈顶）。</li><li><code>pop()</code>：弹出栈顶元素（并删除）。</li><li><code>peek()</code>：查看栈顶元素（不删除）。</li><li><code>empty()</code>：判断栈是否为空。</li></ul></li></ul><h4 id="2-"><strong>2. 示例代码：栈的基本操作</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">import java.util.Stack;

public class StackDemo {
    public static void main(String[] args) {
        Stack&amp;lt;String&amp;gt; stack = new Stack&lt;&gt;();
        
        // 压入元素
        stack.push(&quot;Java&quot;);
        stack.push(&quot;Python&quot;);
        stack.push(&quot;C++&quot;);
        
        // 弹出元素
        System.out.println(&quot;弹出元素: &quot; + stack.pop()); // 输出：C++
        System.out.println(&quot;栈顶元素: &quot; + stack.peek()); // 输出：Python
        
        // 判断栈是否为空
        System.out.println(&quot;栈是否为空: &quot; + stack.empty()); // 输出：false
    }
}
</code></pre>
<h3 id="queuepriorityqueue"><strong>第四部分：队列（Queue）和优先队列（PriorityQueue）</strong></h3><h4 id="1-queue"><strong>1. 队列（Queue）</strong></h4><ul><li><strong>先进先出（FIFO）</strong>：元素从队尾插入，队头取出。</li><li><strong>核心方法</strong>：<br/><ul><li><code>offer(element)</code>：添加元素到队尾（推荐用此方法，避免异常）。</li><li><code>poll()</code>：取出队头元素（队列为空时返回<code>null</code>）。</li><li><code>peek()</code>：查看队头元素（队列为空时返回<code>null</code>）。</li></ul></li></ul><h4 id="2-priorityqueue"><strong>2. 优先队列（PriorityQueue）</strong></h4><ul><li><strong>按优先级排序</strong>：元素按自然顺序（<code>Comparable</code>）或自定义顺序（<code>Comparator</code>）排列，优先级高的元素先取出。</li><li><strong>示例代码：优先队列排序</strong>
```java
import java.util.PriorityQueue;</li></ul><p>public class PriorityQueueDemo {</p><pre class=""><code class="">public static void main(String[] args) {
    // 默认按自然顺序（字符串字典序）
    PriorityQueue&amp;lt;String&amp;gt; queue = new PriorityQueue&lt;&gt;();
    queue.offer(&quot;Oklahoma&quot;);
    queue.offer(&quot;Indiana&quot;);
    queue.offer(&quot;Georgia&quot;);
    
    System.out.println(&quot;默认排序（字典序）:&quot;);
    while (!queue.isEmpty()) {
        System.out.print(queue.poll() + &quot; &quot;); // 输出：Georgia Indiana Oklahoma
    }
    
    // 自定义排序（逆序）
    PriorityQueue&amp;lt;String&amp;gt; reverseQueue = new PriorityQueue&lt;&gt;(
        (a, b) -&gt; b.compareTo(a) // 匿名内部类实现Comparator
    );
    reverseQueue.offer(&quot;Oklahoma&quot;);
    reverseQueue.offer(&quot;Indiana&quot;);
    reverseQueue.offer(&quot;Georgia&quot;);
    
    System.out.println(&quot;\n逆序排序:&quot;);
    while (!reverseQueue.isEmpty()) {
        System.out.print(reverseQueue.poll() + &quot; &quot;); // 输出：Oklahoma Indiana Georgia
    }
}</code></pre><p>}</p><pre class=""><code class="">
### **第五部分：集合工具类（Collections）**
`Collections`类提供大量静态方法，用于操作集合：  
- **排序**：`sort(list)`（自然顺序）、`sort(list, comparator)`（自定义顺序）。  
- **搜索**：`binarySearch(list, key)`（需先排序）。  
- **其他**：`reverse(list)`（反转列表）、`shuffle(list)`（打乱顺序）、`frequency(collection, element)`（统计元素出现次数）。  

#### **示例代码：排序与搜索**
</code></pre><p>java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;</p><p>public class CollectionsDemo {</p><pre class=""><code class="">public static void main(String[] args) {
    List&amp;lt;Integer&amp;gt; list = new ArrayList&lt;&gt;(Arrays.asList(3, 1, 4, 1, 5, 9));
    
    // 排序（自然顺序）
    Collections.sort(list);
    System.out.println(&quot;排序后: &quot; + list); // 输出：[1, 1, 3, 4, 5, 9]
    
    // 搜索元素
    int index = Collections.binarySearch(list, 4);
    System.out.println(&quot;元素4的索引: &quot; + index); // 输出：3
    
    // 反转列表
    Collections.reverse(list);
    System.out.println(&quot;反转后: &quot; + list); // 输出：[9, 5, 4, 3, 1, 1]
}</code></pre><p>}</p><pre class=""><code class="">
### **第六部分：练习与思考**

#### **练习1：List性能对比**
编写代码对比`ArrayList`和`LinkedList`在**中间插入元素**的性能差异，思考为什么会有这样的结果。

#### **练习2：栈的应用**
使用`Stack`实现一个简单的计算器，计算后缀表达式（如`&quot;3 4 + 5 *&quot;`）。
![](/pictures/Pasted image 20250604174439.png)
等价于中缀表达式：(3 + 4) * 5 = 35
</code></pre><p>Java
import java.util.Stack;</p><p>public class PostfixCalculator {</p><pre class=""><code class="">public static int evaluatePostfix(String expression) {
    Stack&amp;lt;Integer&amp;gt; stack = new Stack&lt;&gt;();
    String[] tokens = expression.split(&quot; &quot;);

    for (String token : tokens) {
        if (isOperator(token)) {
            int b = stack.pop(); // 注意：先出栈的是右操作数
            int a = stack.pop();
            int result = applyOperator(a, b, token);
            stack.push(result);
        } else {
            stack.push(Integer.parseInt(token));
        }
    }

    return stack.pop();
}

private static boolean isOperator(String token) {
    return &quot;+-*/&quot;.contains(token);
}

private static int applyOperator(int a, int b, String operator) {
    switch (operator) {
        case &quot;+&quot;: return a + b;
        case &quot;-&quot;: return a - b;
        case &quot;*&quot;: return a * b;
        case &quot;/&quot;: return a / b; // 假设输入合法，b ≠ 0
        default: throw new IllegalArgumentException(&quot;未知操作符: &quot; + operator);
    }
}

public static void main(String[] args) {
    String expr = &quot;3 4 + 5 *&quot;;
    int result = evaluatePostfix(expr);
    System.out.println(&quot;结果: &quot; + result); // 输出: 35
}</code></pre><p>}</p><pre class=""><code class="">
#### **练习3：优先队列自定义排序**
定义一个`Student`类（包含姓名和成绩），使用`PriorityQueue`按成绩从高到低排序学生。


# Lecture 6 - Sets and Maps

### **第一部分：Set（集合）**

#### **1. Set 基础概念**
- **定义**：`Set` 是一种不允许存储重复元素的集合，继承自 `Collection` 接口。
- **核心特性**：
  - **无序性**：元素存储顺序不确定（除了 `LinkedHashSet` 和 `TreeSet`）。
  - **唯一性**：通过 `equals()` 方法判断元素是否重复，确保无重复元素。
- **实现类**：
  - **`HashSet`**：基于哈希表实现，无序，查询速度快。
  - **`LinkedHashSet`**：继承自 `HashSet`，通过链表维护插入顺序，遍历时按插入顺序输出。
  - **`TreeSet`**：基于红黑树实现，元素有序（自然排序或自定义排序）。

#### **2. HashSet 详解**

##### **2.1 创建 HashSet**
</code></pre><p>java
// 空 HashSet
Set&lt;String&gt; set1 = new HashSet&lt;&gt;();</p><p>// 从现有集合创建（如 List）
List&lt;String&gt; list = Arrays.asList(&quot;Apple&quot;, &quot;Banana&quot;);
Set&lt;String&gt; set2 = new HashSet&lt;&gt;(list);</p><p>// 指定初始容量和负载因子（默认容量16，负载因子0.75）
Set&lt;Integer&gt; set3 = new HashSet&lt;&gt;(20, 0.8f);</p><pre class=""><code class="">
##### **2.2 添加元素（`add()`）**
- **唯一性验证**：通过 `hashCode()` 和 `equals()` 判断重复。
  - **示例**：向 `HashSet` 中添加重复元素（字符串会自动去重）。

  </code></pre><p>java
  Set&lt;String&gt; cities = new HashSet&lt;&gt;();
  cities.add(&quot;London&quot;);
  cities.add(&quot;Paris&quot;);
  cities.add(&quot;New York&quot;);
  cities.add(&quot;New York&quot;); // 重复，不会被添加
  System.out.println(cities); // 输出：[London, Paris, New York]（顺序不确定）</p><pre class=""><code class="">
##### **2.3 遍历 HashSet**
- **增强 for 循环**：

  </code></pre><p>java
  for (String city : cities) {</p><pre class=""><code class="">  System.out.print(city + &quot; &quot;);</code></pre><p>}</p><pre class=""><code class="">- **`forEach()` 方法（Lambda 表达式）**：

  </code></pre><p>java
  cities.forEach(city -&gt; System.out.print(city.toLowerCase() + &quot; &quot;));</p><pre class=""><code class="">
##### **2.4 常用方法**
| 方法          | 说明                          |
|---------------|-------------------------------|
| `remove(obj)` | 删除指定元素                  |
| `contains(obj)` | 检查是否包含元素              |
| `size()`      | 获取元素个数                  |
| `clear()`     | 清空集合                      |


#### **3. LinkedHashSet 详解**
- **特点**：保持元素的**插入顺序**，遍历时按插入顺序输出。
- **示例**：

  </code></pre><p>java
  Set&lt;String&gt; linkedSet = new LinkedHashSet&lt;&gt;();
  linkedSet.add(&quot;A&quot;);
  linkedSet.add(&quot;B&quot;);
  linkedSet.add(&quot;C&quot;);
  System.out.println(linkedSet); // 输出：[A, B, C]（顺序与插入一致）</p><pre class=""><code class="">
#### **4. TreeSet 详解**
- **特点**：元素**自动排序**，支持自然排序（`Comparable`）或自定义排序（`Comparator`）。
- **示例：自然排序（字符串按字母顺序）**

  </code></pre><p>java
  Set&lt;String&gt; treeSet = new TreeSet&lt;&gt;();
  treeSet.add(&quot;Z&quot;);
  treeSet.add(&quot;A&quot;);
  treeSet.add(&quot;B&quot;);
  System.out.println(treeSet); // 输出：[A, B, Z]（升序排列）</p><pre class=""><code class="">- **自定义排序（Comparator）**：

  </code></pre><p>java
  // 按字符串长度降序排列
  Set&lt;String&gt; treeSet = new TreeSet&lt;&gt;(Comparator.comparingInt(String::length).reversed());
  treeSet.add(&quot;Apple&quot;); // 5字母
  treeSet.add(&quot;Banana&quot;); // 6字母
  treeSet.add(&quot;Cat&quot;); // 3字母
  System.out.println(treeSet); // 输出：[Banana, Apple, Cat]</p><pre class=""><code class="">
#### **5. Set vs List 性能对比**
- **场景总结**：
  - **Set**：适合存储唯一元素，查询效率高（尤其是 `HashSet`）。
  - **List**：适合需要索引访问的场景（如 `ArrayList`、`LinkedList`）。
- **性能测试示例**（判断元素是否存在）：

  </code></pre><p>java
  // HashSet 耗时约 10-50ms
  long time = System.currentTimeMillis();
  for (int i = 0; i &lt; 100000; i++) {</p><pre class=""><code class="">  set.contains(i);</code></pre><p>}
  System.out.println(&quot;HashSet耗时：&quot; + (System.currentTimeMillis() - time) + &quot;ms&quot;);</p><pre class=""><code class="">
### **第二部分：Map（映射）**

#### **1. Map 基础概念**
- **定义**：存储键值对（`key-value`）的集合，通过 `key` 快速查找 `value`。
- **核心特性**：
  - **键唯一性**：`key` 不能重复（通过 `equals()` 判断）。
  - **值可重复**：`value` 可以重复。
- **实现类**：
  - **`HashMap`**：无序，基于哈希表，查询速度快。
  - **`LinkedHashMap`**：保持插入顺序或访问顺序。
  - **`TreeMap`**：按键排序（自然排序或自定义排序）。

#### **2. HashMap 详解**

##### **2.1 创建 HashMap**
</code></pre><p>java
// 空 HashMap
Map&lt;String, Integer&gt; map1 = new HashMap&lt;&gt;();</p><p>// 初始化键值对
Map&lt;String, Integer&gt; map2 = new HashMap&lt;&gt;() {{</p><pre class=""><code class="">put(&quot;Apple&quot;, 10);
put(&quot;Banana&quot;, 20);</code></pre><p>}};</p><p>// 从现有 Map 创建
Map&lt;String, Integer&gt; map3 = new HashMap&lt;&gt;(map2);</p><pre class=""><code class="">
##### **2.2 操作方法**
- **添加/更新键值对**：`put(key, value)`（若 `key` 已存在，覆盖原有 `value`）。

  </code></pre><p>java
  map1.put(&quot;Orange&quot;, 30); // 添加
  map1.put(&quot;Apple&quot;, 15); // 更新 Apple 的值为 15</p><pre class=""><code class="">- **获取值**：`get(key)`（若 `key` 不存在，返回 `null`）。

  </code></pre><p>java
  int value = map1.get(&quot;Apple&quot;); // value = 15</p><pre class=""><code class="">- **遍历键值对**：

  </code></pre><p>java
  // 遍历所有键
  for (String key : map1.keySet()) {</p><pre class=""><code class="">  System.out.println(&quot;Key: &quot; + key + &quot;, Value: &quot; + map1.get(key));</code></pre><p>}</p><p>  // 遍历所有键值对（推荐）
  for (Map.Entry&lt;String, Integer&gt; entry : map1.entrySet()) {</p><pre class=""><code class="">  System.out.println(entry.getKey() + &quot; -&gt; &quot; + entry.getValue());</code></pre><p>}</p><p>  // 使用 forEach()
  map1.forEach((key, value) -&gt; System.out.println(key + &quot;: &quot; + value));</p><pre class=""><code class="">
##### **2.3 常用方法**
| 方法              | 说明                          |
|-------------------|-------------------------------|
| `remove(key)`     | 删除指定键的键值对            |
| `containsKey(key)` | 检查是否存在指定键            |
| `size()`          | 获取键值对个数                |
| `keySet()`        | 获取所有键的 `Set`            |
| `values()`        | 获取所有值的 `Collection`     |


#### **3. LinkedHashMap 详解**
- **特点**：
  - **插入顺序**：默认按插入顺序存储和遍历。
  - **访问顺序**：设置 `accessOrder = true` 时，按最后访问顺序排序（最近最少访问 → 最近最多访问）。
- **示例：访问顺序**

  </code></pre><p>java
  Map&lt;String, Integer&gt; linkedMap = new LinkedHashMap&lt;&gt;(16, 0.75f, true);
  linkedMap.put(&quot;A&quot;, 1);
  linkedMap.put(&quot;B&quot;, 2);
  linkedMap.get(&quot;A&quot;); // 访问键 &quot;A&quot;
  System.out.println(linkedMap); // 输出：[B, A]（&quot;A&quot; 被移动到最后）</p><pre class=""><code class="">
#### **4. TreeMap 详解**
- **特点**：按键的**自然排序**或**自定义排序**（`Comparator`）排列。
- **示例：自然排序（字符串按字母顺序）**

  </code></pre><p>java
  Map&lt;String, Integer&gt; treeMap = new TreeMap&lt;&gt;();
  treeMap.put(&quot;Z&quot;, 26);
  treeMap.put(&quot;A&quot;, 1);
  treeMap.put(&quot;B&quot;, 2);
  System.out.println(treeMap); // 输出：{A=1, B=2, Z=26}（键升序）</p><pre class=""><code class="">- **自定义排序（按值降序）**：

  </code></pre><p>java
  Map&lt;String, Integer&gt; treeMap = new TreeMap&lt;&gt;(Comparator.comparingInt(Map.Entry::getValue).reversed());
  treeMap.put(&quot;Apple&quot;, 15);
  treeMap.put(&quot;Banana&quot;, 20);
  treeMap.put(&quot;Cherry&quot;, 10);
  System.out.println(treeMap); // 输出：{Banana=20, Apple=15, Cherry=10}（值降序）</p><pre class=""><code class="">
### **第三部分：关键练习与注意事项**

#### **1. 练习建议**
1. **Set 练习**：
   - 创建一个 `HashSet`，存储自定义对象 `Person`（包含姓名和年龄），观察去重效果。若去重失败，重写 `equals()` 和 `hashCode()` 方法。
   - 使用 `TreeSet` 对整数列表进行排序，尝试自定义排序规则（如降序）。
2. **Map 练习**：
   - 创建一个 `HashMap`，统计字符串中每个字符的出现次数（例如：&quot;abracadabra&quot; → a:5, b:2, r:2, c:1, d:1）。
   - 使用 `LinkedHashMap` 实现一个简单的LRU缓存（最近最少使用），设置 `accessOrder = true`，当容量超过限制时删除最旧的元素。

#### **2. 注意事项**
- **Set 去重**：自定义对象必须重写 `equals()` 和 `hashCode()`，否则 `HashSet` 无法正确去重。
- **Map 键的选择**：建议使用不可变对象（如 `String`、`Integer`）作为键，避免键值改变导致哈希冲突。
- **性能优化**：
  - `HashSet`/`HashMap` 适合高频查询和插入。
  - `TreeSet`/`TreeMap` 适合需要排序的场景，但性能略低于哈希结构。

### **总结：核心知识点速查表**
| 类型       | 实现类       | 顺序性         | 去重机制               | 典型场景                  |
|------------|--------------|----------------|------------------------|---------------------------|
| **Set**    | `HashSet`    | 无序           | `hashCode()` + `equals()` | 快速去重、唯一性校验      |
|            | `LinkedHashSet` | 插入顺序       | 同上                   | 需要保持插入顺序的场景    |
|            | `TreeSet`    | 自然/自定义排序 | `compareTo()`/`Comparator` | 排序集合、范围查询        |
| **Map**    | `HashMap`    | 无序           | `hashCode()` + `equals()` | 键值对快速查找            |
|            | `LinkedHashMap` | 插入/访问顺序  | 同上                   | 日志记录、LRU缓存         |
|            | `TreeMap`    | 自然/自定义排序 | `compareTo()`/`Comparator` | 按键排序的统计、范围查询  |


# Lecture 8 - Developing Efficient Algorithms

### **第一讲：算法效率与大O表示法**

#### **1. 为什么需要分析算法效率？**
- **问题**：同一任务可能有不同算法（如线性搜索 vs 二分搜索），如何比较它们的效率？
- **解决方案**：用**大O表示法**（Big O Notation）衡量算法的**时间复杂度**，关注输入规模增长时的性能变化趋势。

#### **2. 大O表示法的核心思想**
- **忽略常数和低阶项**：例如，$100n$和$n/2$的时间复杂度均为$O(n)$。
- **关注最坏情况**：分析算法在最坏输入下的表现（因为平均情况通常与最坏情况同阶）。

#### **3. 常见时间复杂度排序**
$O(1) &lt; O(\log n) &lt; O(n) &lt; O(n \log n) &lt; O(n^2) &lt; O(n^3) &lt; O(2^n)$
- **示例**：
  - **常数时间**$O(1)$：数组索引访问。
  - **线性时间**$O(n)$：线性搜索。
  - **对数时间**$O(\log n)$：二分搜索。
  - **平方时间**$O(n^2)$：选择排序、插入排序。

### **第二讲：常见算法的时间复杂度分析**

#### **1. 线性搜索 vs 二分搜索**
- **线性搜索**：
  - **代码**：

    </code></pre><p>java</p><pre class=""><code class="">public static int linearSearch(int[] list, int key) {
    for (int i = 0; i &lt; list.length; i++) {
        if (list[i] == key) return i;
    }
    return -1;
}

```</code></pre><ul><li><strong>复杂度</strong>：$O(n)$（最坏情况需遍历所有元素）。</li></ul><p><strong>二分搜索</strong>：</p><ul><li><strong>条件</strong>：数组必须有序。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static int binarySearch(int[] list, int key) {
    int low = 0, high = list.length - 1;
    while (low &lt;= high) {
        int mid = (low + high) / 2;
        if (key &lt; list[mid]) high = mid - 1;
        else if (key == list[mid]) return mid;
        else low = mid + 1;
    }
    return -1;
}

</code></pre>
</li><li><p><strong>复杂度</strong>：$O(\log n)$（每次将问题规模减半）。</p></li></ul><h4 id="2-"><strong>2. 选择排序</strong></h4><ul><li><strong>思想</strong>：每次从未排序部分选择最小元素，与未排序部分的第一个元素交换。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static void selectionSort(double[] list) {
    for (int i = 0; i &lt; list.length; i++) {
        int minIndex = i;
        for (int j = i + 1; j &lt; list.length; j++) {
            if (list[j] &lt; list[minIndex]) minIndex = j;
        }
        double temp = list[i];
        list[i] = list[minIndex];
        list[minIndex] = temp;
    }
}

</code></pre>
</li><li><p><strong>复杂度</strong>：$O(n^2)$（双重循环）。</p></li></ul><h4 id="3-"><strong>3. 插入排序</strong></h4><ul><li><strong>思想</strong>：将未排序元素逐个插入已排序部分的正确位置。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static void insertionSort(int[] list) {
    for (int i = 1; i &lt; list.length; i++) {
        int current = list[i];
        int j = i - 1;
        while (j &gt;= 0 &amp;&amp; list[j] &gt; current) {
            list[j + 1] = list[j];
            j--;
        }
        list[j + 1] = current;
    }
}

</code></pre>
</li><li><p><strong>复杂度</strong>：$O(n^2)$（最坏情况下需移动所有元素）。</p></li></ul><h3 id=""><strong>第三讲：递归与动态规划</strong></h3><h4 id="1-"><strong>1. 斐波那契数列的递归实现</strong></h4><ul><li><strong>递归公式</strong>：$fib(n) = fib(n-1) + fib(n-2)$。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static int fib(int n) {
    if (n &lt;= 1) return n;
    return fib(n-1) + fib(n-2);
}

</code></pre>
</li><li><p><strong>复杂度</strong>：$O(2^n)$（重复计算大量子问题）。</p></li></ul><h4 id="2-"><strong>2. 动态规划优化斐波那契数列</strong></h4><ul><li><strong>思想</strong>：用数组存储已计算的子问题结果，避免重复计算。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static int fib(int n) {
    if (n &lt;= 1) return n;
    int[] dp = new int[n + 1];
    dp[0] = 0;
    dp[1] = 1;
    for (int i = 2; i &lt;= n; i++) {
        dp[i] = dp[i-1] + dp[i-2];
    }
    return dp[n];
}

</code></pre>
</li><li><p><strong>复杂度</strong>：$O(n)$（线性时间）。</p></li></ul><h3 id=""><strong>第四讲：分治法与回溯法</strong></h3><h4 id="1-"><strong>1. 最近点对问题（分治法）</strong></h4><ul><li><strong>步骤</strong>：
<ol start="1"><li>将点按x坐标排序。</li><li>递归求解左右两半的最近点对。</li><li>合并时检查中间带内的点对。</li></ol></li><li><strong>复杂度</strong>：$O(n \log n)$（排序和递归合并）。</li></ul><h4 id="2-"><strong>2. 八皇后问题（回溯法）</strong></h4><ul><li><strong>思想</strong>：逐行放置皇后，若当前位置冲突则回溯到上一行。</li><li><p><strong>代码关键逻辑</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">private boolean isValid(int row, int col) {
    for (int i = 0; i &lt; row; i++) {
        if (queens[i] == col || Math.abs(row - i) == Math.abs(col - queens[i])) {
            return false;
        }
    }
    return true;
}

</code></pre>
</li><li><p><strong>复杂度</strong>：$O(n!)$（理论上最坏情况，但通过剪枝优化后实际效率更高）。</p></li></ul><h3 id=""><strong>第五讲：其他高效算法</strong></h3><h4 id="1-gcd"><strong>1. 欧几里得算法（求GCD）</strong></h4><ul><li><strong>思想</strong>：利用$gcd(m, n) = gcd(n, m % n)$。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static int gcd(int m, int n) {
    if (n == 0) return m;
    return gcd(n, m % n);
}

</code></pre>
</li><li><p><strong>复杂度</strong>：$O(\log n)$。</p></li></ul><h4 id="2-sieve-of-eratosthenes"><strong>2. 筛法求素数（Sieve of Eratosthenes）</strong></h4><ul><li><strong>思想</strong>：标记非素数，从2开始筛去所有倍数。</li><li><p><strong>代码</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static boolean[] sieve(int n) {
    boolean[] isPrime = new boolean[n + 1];
    Arrays.fill(isPrime, true);
    isPrime[0] = isPrime[1] = false;
    for (int i = 2; i * i &lt;= n; i++) {
        if (isPrime[i]) {
            for (int j = i * i; j &lt;= n; j += i) {
                isPrime[j] = false;
            }
        }
    }
    return isPrime;
}

</code></pre>
</li><li><p><strong>复杂度</strong>：$O(n \log \log n)$。</p></li></ul><h3 id=""><strong>总结与练习</strong></h3><ol start="1"><li><strong>大O表示法</strong>：分析算法复杂度的核心工具，关注主导项。</li><li><strong>排序算法</strong>：选择排序和插入排序均为$O(n^2)$，适合小规模数据。</li><li><strong>递归与动态规划</strong>：动态规划通过存储子问题结果优化递归的指数复杂度。</li><li><strong>分治与回溯</strong>：分治法将问题分解为独立子问题，回溯法通过剪枝避免无效搜索。</li></ol><p><strong>练习题</strong>：</p><ol start="1"><li><p>分析以下代码的时间复杂度：</p><pre class="language-java lang-java"><code class="language-java lang-java">public static void printPairs(int[] arr) {
    for (int i = 0; i &lt; arr.length; i++) {
        for (int j = i + 1; j &lt; arr.length; j++) {
            System.out.println(arr[i] + &quot;, &quot; + arr[j]);
        }
    }
}

</code></pre>
</li></ol><h4 id="">答案：</h4><p>这段代码的时间复杂度为$O(n^2)$（$n$为数组长度）。</p><ul><li>分析过程：
  代码的核心是两层嵌套循环，用于打印数组中所有不重复的元素对（即每个元素对$(arr[i], arr[j])$满足$i &lt; j$）。</li></ul><h5 id="1-">1. 外层循环的迭代次数</h5><p>外层循环的变量$i$从$0$遍历到$arr.length - 1$（共$n$次迭代，$n = arr.length$）。</p><h5 id="2-">2. 内层循环的迭代次数</h5><p>对于外层循环的每一次迭代$i$，内层循环的变量$j$从$i + 1$遍历到$arr.length - 1$。因此：</p><ul><li>当$i = 0$时，内层循环执行$n - 1$次（$j = 1, 2, ..., n-1$）；</li><li>当$i = 1$时，内层循环执行$n - 2$次（$j = 2, 3, ..., n-1$）；</li><li>...</li><li>当$i = n - 2$时，内层循环执行$1$次（$j = n - 1$）；</li><li>当$i = n - 1$时，内层循环不执行（$j = n$超出数组范围）。</li></ul><h5 id="3-">3. 总操作次数</h5><p>总操作次数为内层循环所有迭代次数的和：<br/>$\text{总次数} = (n-1) + (n-2) + ... + 2 + 1 + 0 = \frac{n(n-1)}{2}$</p><h5 id="4-">4. 时间复杂度结论</h5><p>根据大$O$表示法的规则（忽略低阶项和常数系数），$\frac{n(n-1)}{2}$可简化为$O(n^2)$。
<strong>结论</strong>：该代码的时间复杂度为$O(n^2)$（平方阶）。</p><ol start="2"><li>用动态规划实现斐波那契数列，尝试优化空间复杂度。</li></ol><h4 id="">答案：</h4><p>动态规划实现斐波那契数列（空间优化版）
斐波那契数列的定义为：<br/>$F(0) = 0,\ F(1) = 1,\ F(n) = F(n-1) + F(n-2)$（$n \geq 2$）<br/>传统动态规划方法需要使用数组存储中间结果（空间复杂度$O(n)$），但通过观察可以发现，计算$F(n)$仅需前两项$F(n-1)$和$F(n-2)$，因此可以用两个变量代替数组，将空间复杂度优化至$O(1)$。</p><h5 id="java">实现代码（Java）</h5><pre class="language-java lang-java"><code class="language-java lang-java">public class Fibonacci {
    // 动态规划实现，空间复杂度优化为 O(1)
    public static long fibonacci(int n) {
        if (n == 0) return 0;    // 边界条件：F(0) = 0
        if (n == 1) return 1;    // 边界条件：F(1) = 1
        
        long prev1 = 0;  // 保存 F(n-2) 的值（初始为 F(0)）
        long prev2 = 1;  // 保存 F(n-1) 的值（初始为 F(1)）
        
        for (int i = 2; i &lt;= n; i++) {
            long current = prev1 + prev2;  // 计算 F(i) = F(i-1) + F(i-2)
            prev1 = prev2;                 // 迭代更新 F(n-2) 为原 F(n-1)
            prev2 = current;               // 迭代更新 F(n-1) 为当前 F(i)
        }
        return prev2;  // 最终 prev2 即为 F(n)
    }

    public static void main(String[] args) {
        for (int i = 0; i &lt;= 10; i++) {
            System.out.printf(&quot;F(%d) = %d%n&quot;, i, fibonacci(i));
        }
    }
}
</code></pre>
<h5 id="">代码说明</h5><ol start="1"><li><strong>边界条件处理</strong>：<br/>当$n = 0$或$n = 1$时，直接返回$0$或$1$，无需计算。</li><li><strong>变量初始化</strong>：<br/><ul><li><code>prev1</code> 初始化为$F(0) = 0$（对应$F(n-2)$）；</li><li><code>prev2</code> 初始化为$F(1) = 1$（对应$F(n-1)$）。</li></ul></li><li><strong>迭代计算</strong>：<br/>从$i = 2$开始循环至$n$，每次计算当前项$F(i) = prev1 + prev2$，然后更新 <code>prev1</code> 和 <code>prev2</code> 为下一次迭代的前驱值。</li><li><strong>空间优化</strong>：<br/>仅用两个变量保存前驱状态，避免了数组存储，空间复杂度从$O(n)$优化至$O(1)$。</li></ol><h5 id="">复杂度分析</h5><ul><li><strong>时间复杂度</strong>：$O(n)$（仅需一次循环遍历到$n$）。</li><li><strong>空间复杂度</strong>：$O(1)$（仅用两个变量保存前驱状态）。</li></ul><h5 id="">测试结果</h5><p>运行 <code>main</code> 方法输出前10项斐波那契数：</p><pre class=""><code class="">F(0) = 0
F(1) = 1
F(2) = 1
F(3) = 2
F(4) = 3
F(5) = 5
F(6) = 8
F(7) = 13
F(8) = 21
F(9) = 34
F(10) = 55
</code></pre>
<h1 id="lecture-9---sorting">Lecture 9 - Sorting</h1><h3 id=""><strong>第一部分：排序算法基础</strong></h3><h4 id="1-"><strong>1. 什么是排序算法？</strong></h4><p>排序算法是将一组数据按特定顺序（如升序、降序）排列的方法。评价标准包括：</p><ul><li><strong>时间复杂度</strong>：算法运行所需时间（如 O(n²)、O(n log n)）。</li><li><strong>空间复杂度</strong>：算法占用的额外内存空间（如原地排序 O(1)）。</li><li><strong>稳定性</strong>：相同元素的相对顺序在排序后是否保持不变。</li></ul><h4 id="2-"><strong>2. 常见排序算法分类</strong></h4><table><thead><tr><th> 算法名称   </th><th> 时间复杂度       </th><th> 空间复杂度 </th><th> 稳定性 </th></tr></thead><tbody><tr><td> 冒泡排序   </td><td> O(n²)            </td><td> O(1)       </td><td> 稳定   </td></tr><tr><td> 归并排序   </td><td> O(n log n)       </td><td> O(n)       </td><td> 稳定   </td></tr><tr><td> 快速排序   </td><td> 平均 O(n log n)  </td><td> O(log n)   </td><td> 不稳定 </td></tr><tr><td> 堆排序     </td><td> O(n log n)       </td><td> O(1)       </td><td> 不稳定 </td></tr></tbody></table>
<h3 id="bubble-sort"><strong>第二部分：冒泡排序（Bubble Sort）</strong></h3><h4 id="1-"><strong>1. 核心思想</strong></h4><p>通过反复比较相邻元素，将较大的元素逐步“冒泡”到数组末尾。</p><ul><li><strong>示例</strong>：排序 <code>[4, 3, 2, 1]</code><br/><ul><li>第1轮：比较 <code>4↔3</code>、<code>4↔2</code>、<code>4↔1</code>，最大元素4到位，数组变为 <code>[3, 2, 1, 4]</code>。</li><li>第2轮：比较 <code>3↔2</code>、<code>3↔1</code>，次大元素3到位，数组变为 <code>[2, 1, 3, 4]</code>。</li><li>第3轮：比较 <code>2↔1</code>，数组有序 <code>[1, 2, 3, 4]</code>。</li></ul></li></ul><h4 id="2-java"><strong>2. 代码实现（Java）</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">public static void bubbleSort(int[] list) {
    boolean needNextPass = true; // 优化：提前终止排序
    for (int k = 1; k &lt; list.length &amp;&amp; needNextPass; k++) {
        needNextPass = false;
        for (int i = 0; i &lt; list.length - k; i++) {
            if (list[i] &gt; list[i + 1]) {
                // 交换相邻元素
                int temp = list[i];
                list[i] = list[i + 1];
                list[i + 1] = temp;
                needNextPass = true; // 有交换，需继续下一轮
            }
        }
    }
}
</code></pre>
<h4 id="3-"><strong>3. 时间复杂度</strong></h4><ul><li><strong>最好情况</strong>（数组已排序）：O(n)（仅需1轮遍历）。</li><li><strong>最坏情况</strong>（完全逆序）：O(n²)（需 n(n-1)/2 次比较）。</li></ul><h4 id="-1"><strong>🎯 练习1</strong></h4><p>用冒泡排序对 <code>[5, 1, 4, 2, 8]</code> 排序，手动模拟每一轮过程。</p>
<h3 id="merge-sort"><strong>第三部分：归并排序（Merge Sort）</strong></h3><h4 id="1-"><strong>1. 核心思想（分治算法）</strong></h4><ol start="1"><li><strong>分解</strong>：将数组分成两半，递归排序每一半。</li><li><strong>合并</strong>：将两个已排序的子数组合并成一个有序数组。<br/><ul><li><strong>示例</strong>：排序 <code>[5, 2, 9, 1]</code></li><li>分解：<code>[5,2]</code> 和 <code>[9,1]</code> → 继续分解为 <code>[5]</code>, <code>[2]</code>, <code>[9]</code>, <code>[1]</code>。</li><li>合并：<code>[2,5]</code> 和 <code>[1,9]</code> → 最终合并为 <code>[1, 2, 5, 9]</code>。</li></ul></li></ol><h4 id="2-"><strong>2. 合并操作（双指针法）</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">private static void merge(int[] left, int[] right, int[] list) {
    int i = 0, j = 0, k = 0;
    while (i &lt; left.length &amp;&amp; j &lt; right.length) {
        if (left[i] &lt;= right[j]) {
            list[k++] = left[i++]; // 取较小元素放入结果数组
        } else {
            list[k++] = right[j++];
        }
    }
    // 复制剩余元素
    while (i &lt; left.length) list[k++] = left[i++];
    while (j &lt; right.length) list[k++] = right[j++];
}
</code></pre>
<h4 id="3-"><strong>3. 时间复杂度</strong></h4><ul><li>每一层合并时间为 O(n)，递归深度为 log n，总时间 <strong>O(n log n)</strong>。</li><li>空间复杂度 <strong>O(n)</strong>（需临时数组存储合并结果）。</li></ul><h4 id="-2"><strong>🎯 练习2</strong></h4><p>归并排序的稳定性如何？为什么？</p>
<h3 id="quick-sort"><strong>第四部分：快速排序（Quick Sort）</strong></h3><h4 id="1-"><strong>1. 核心思想（分治算法）</strong></h4><ol start="1"><li><strong>选枢轴</strong>：选择一个元素（如第一个元素）作为枢轴。</li><li><strong>分区</strong>：将数组分为两部分，左半部分 ≤ 枢轴，右半部分 &gt; 枢轴。</li><li><strong>递归排序</strong>：对左右两部分递归应用快速排序。<br/><ul><li><strong>示例</strong>：排序 <code>[5, 2, 9, 3, 8]</code>，选5为枢轴</li><li>分区后：<code>[2, 3]</code> 5 <code>[9, 8]</code> → 递归排序左右部分，最终得到 <code>[2, 3, 5, 8, 9]</code>。</li></ul></li></ol><h4 id="2-"><strong>2. 分区操作（双指针法）</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">private static int partition(int[] list, int first, int last) {
    int pivot = list[first]; // 枢轴
    int low = first + 1, high = last;
    while (high &gt; low) {
        // 找左半部分第一个&gt;枢轴的元素
        while (low &lt;= high &amp;&amp; list[low] &lt;= pivot) low++;
        // 找右半部分第一个≤枢轴的元素
        while (low &lt;= high &amp;&amp; list[high] &gt; pivot) high--;
        // 交换元素
        if (high &gt; low) swap(list, low, high);
    }
    // 将枢轴放到正确位置
    while (high &gt; first &amp;&amp; list[high] &gt;= pivot) high--;
    if (pivot &gt; list[high]) swap(list, first, high);
    return high; // 返回枢轴索引
}
</code></pre>
<h4 id="3-"><strong>3. 时间复杂度</strong></h4><ul><li><strong>平均情况</strong>：O(n log n)（枢轴平衡分区）。</li><li><strong>最坏情况</strong>：O(n²)（枢轴每次为最小/最大值，如已排序数组）。</li></ul><h4 id="-3"><strong>🎯 练习3</strong></h4><p>快速排序的空间复杂度为什么是 O(log n)？最坏情况如何优化？</p>
<h3 id="heap-sort"><strong>第五部分：二叉堆与堆排序（Heap Sort）</strong></h3><h4 id="1-"><strong>1. 二叉堆（最大堆）</strong></h4><ul><li><strong>定义</strong>：完全二叉树，每个节点 ≥ 子节点（最大堆）。</li><li><strong>数组表示</strong>：根节点下标0，左子节点2i+1，右子节点2i+2，父节点(i-1)/2。</li><li><strong>操作</strong>：<br/><ul><li><strong>上浮</strong>：插入元素后，与父节点比较并交换，直到堆性质满足。</li><li><strong>下沉</strong>：删除根节点后，与子节点比较并交换，重建堆。</li></ul></li></ul><h4 id="2-"><strong>2. 堆排序步骤</strong></h4><ol start="1"><li><strong>建堆</strong>：将数组转换为最大堆（自底向上调整）。</li><li><strong>排序</strong>：重复删除根节点（最大值），放到数组末尾，重建堆。<br/><ul><li><strong>示例</strong>：数组 <code>[10, 5, 3, 4, 1]</code></li><li>建堆后：<code>[10, 5, 3, 4, 1]</code>（根节点最大）。</li><li>第1次删除10，数组变为 <code>[5,4,3,1,10]</code>，重建堆后根节点5。</li><li>最终排序为 <code>[1, 3, 4, 5, 10]</code>。</li></ul></li></ol><h4 id="3-"><strong>3. 代码片段（建堆）</strong></h4><pre class="language-java lang-java"><code class="language-java lang-java">public static void heapSort(int[] array) {
    int n = array.length;
    // 建堆：从最后一个非叶子节点开始下沉
    for (int i = n/2 - 1; i &gt;= 0; i--) {
        heapify(array, n, i);
    }
    // 排序：删除根节点，重建堆
    for (int i = n-1; i &gt; 0; i--) {
        swap(array, 0, i); // 根节点（最大值）放到末尾
        heapify(array, i, 0); // 对前i个元素重建堆
    }
}

private static void heapify(int[] array, int n, int i) {
    int largest = i;
    int left = 2*i + 1, right = 2*i + 2;
    if (left &lt; n &amp;&amp; array[left] &gt; array[largest]) largest = left;
    if (right &lt; n &amp;&amp; array[right] &gt; array[largest]) largest = right;
    if (largest != i) {
        swap(array, i, largest);
        heapify(array, n, largest); // 递归下沉
    }
}
</code></pre>
<h4 id="4-"><strong>4. 时间复杂度</strong></h4><ul><li>建堆时间 O(n)，排序时间 O(n log n)，总时间 <strong>O(n log n)</strong>。</li></ul><h3 id=""><strong>第六部分：总结与对比</strong></h3><table><thead><tr><th> 算法       </th><th> 核心思想       </th><th> 最佳场景                  </th></tr></thead><tbody><tr><td> 冒泡排序   </td><td> 相邻交换       </td><td> 小规模数据，教育场景       </td></tr><tr><td> 归并排序   </td><td> 分治+合并      </td><td> 大规模数据，需稳定性       </td></tr><tr><td> 快速排序   </td><td> 分治+分区      </td><td> 通用场景，效率最高         </td></tr><tr><td> 堆排序     </td><td> 二叉堆+删除根  </td><td> 原地排序，内存敏感场景     </td></tr></tbody></table>
<h3 id=""><strong>课后任务</strong></h3><ol start="1"><li><strong>编程练习</strong>：<br/><ul><li>实现冒泡排序，测试最好/最坏情况性能。</li><li>用归并排序对 <code>[3, 1, 4, 1, 5, 9, 2, 6]</code> 排序，输出每一步合并结果。</li></ul></li><li><strong>思考问题</strong>：<br/><ul><li>为什么快速排序平均效率高于归并排序？</li><li>堆排序如何实现原地排序？</li></ul></li></ol><h1 id="lecture-10---graphs-and-applications">Lecture 10 - Graphs and Applications</h1><h3 id=""><strong>第一讲：图的基本概念</strong></h3><h4 id="11-"><strong>1.1 什么是图？</strong></h4><ul><li><strong>定义</strong>：图是由 <strong>顶点（Vertices/Nodes）</strong> 和 <strong>边（Edges）</strong> 组成的结构，记作 $G=(V, E)$。
<ul><li><strong>顶点</strong>：表示对象（如城市、用户、节点）。</li><li><strong>边</strong>：表示对象之间的关系（如路线、连接、依赖）。</li></ul></li><li><strong>示例</strong>：
<ul><li>社交网络：顶点是用户，边是“关注”关系（有向边）。</li><li>城市地图：顶点是城市，边是道路（无向边，可能带距离权重）。</li></ul></li></ul><h4 id="12-"><strong>1.2 图的分类</strong></h4><table><thead><tr><th> <strong>分类维度</strong>       </th><th> <strong>类型</strong>                </th><th> <strong>特点</strong>                                                                 </th></tr></thead><tbody><tr><td> <strong>边的方向</strong>       </td><td> 无向图                  </td><td> 边无方向（如 $A-B$ 与 $B-A$ 是同一条边）                          </td></tr><tr><td>                    </td><td> 有向图                  </td><td> 边有方向（如 $A \to B$ 表示从 A 到 B 的单向关系）                     </td></tr><tr><td> <strong>边的权重</strong>       </td><td> 无权图                  </td><td> 边仅表示连接，无数值属性                                               </td></tr><tr><td>                    </td><td> 有权图                  </td><td> 边带有权重（如距离、成本、时间）                                         </td></tr><tr><td> <strong>特殊结构</strong>       </td><td> 简单图                  </td><td> 无自环（顶点到自身的边）和并行边（两顶点间多条边）                       </td></tr><tr><td>                    </td><td> 非简单图                </td><td> 允许自环或并行边                                                       </td></tr><tr><td> <strong>连通性</strong>         </td><td> 连通图                  </td><td> 任意两顶点之间存在路径                                                 </td></tr><tr><td>                    </td><td> 非连通图                </td><td> 存在至少两顶点无法通过路径连接                                         </td></tr></tbody></table><p><strong>练习1</strong>：判断以下场景属于哪种图：</p><ul><li>地铁线路图（站点连接，无方向）→ <strong>无向无权图</strong></li><li>航班路线图（城市间单向航线，带飞行时间）→ <strong>有向有权图</strong></li><li>化学分子结构（原子为顶点，化学键为边，无权重）→ <strong>无向简单图</strong></li></ul><h4 id="13-"><strong>1.3 关键术语</strong></h4><ul><li><strong>相邻顶点（Adjacent Vertices）</strong>：通过一条边直接连接的顶点（如 $A$ 和 $B$ 相邻）。</li><li><strong>顶点的度（Degree）</strong>：
<ul><li>无向图：顶点关联的边数（如顶点 $A$ 有3条边，度为3）。</li><li>有向图：分为 <strong>入度（In-Degree）</strong> 和 <strong>出度（Out-Degree）</strong>。</li></ul></li><li><strong>环（Cycle）</strong>：起点和终点相同的路径（如 $A \to B \to C \to A$）。</li><li><strong>树（Tree）</strong>：连通且无环的无向图，边数为 $|V| - 1$。</li><li><strong>生成树（Spanning Tree）</strong>：包含图中所有顶点的树（边数为 $|V| - 1$，连通无环）。</li></ul><h3 id=""><strong>第二讲：图的表示方法</strong></h3><p>图的表示方法影响算法的效率，常见方法有两种：<strong>邻接矩阵</strong> 和 <strong>邻接表</strong>。</p><h4 id="21-adjacency-matrix"><strong>2.1 邻接矩阵（Adjacency Matrix）</strong></h4><ul><li><strong>定义</strong>：用 $n \times n$ 矩阵（$n$ 为顶点数）表示顶点间的连接关系。
<ul><li><strong>无权图</strong>：矩阵元素 $matrix<span>[i][j]</span> = 1$ 表示存在边，$0$ 表示不存在。</li><li><strong>有权图</strong>：矩阵元素为边的权重，不存在的边用 $\infty$ 表示。</li></ul></li><li><strong>示例</strong>：无向无权图（顶点 $A(0), B(1), C(2)$，边 $A-B, A-C, B-C$）：
  $\begin{bmatrix}0 &amp; 1 &amp; 1 \1 &amp; 0 &amp; 1 \1 &amp; 1 &amp; 0 \\end{bmatrix}$</li><li><strong>优缺点</strong>：
<ul><li>✅ 优点：访问速度快（直接查矩阵）。</li><li>❌ 缺点：存储空间大（稀疏图浪费空间）。</li></ul></li></ul><h4 id="22-adjacency-list"><strong>2.2 邻接表（Adjacency List）</strong></h4><ul><li><strong>定义</strong>：每个顶点对应一个列表，存储其相邻顶点或边。
<ul><li><strong>无权图</strong>：列表存储相邻顶点索引（如 $0: [1, 2]$ 表示顶点0相邻顶点1和2）。</li><li><strong>有权图</strong>：列表存储边对象（包含目标顶点和权重）。</li></ul></li><li><p><strong>实现方式（Java示例）</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">// 无权图邻接表
List&amp;lt;List&lt;Integer&amp;gt;&gt; adjList = new ArrayList&lt;&gt;();
adjList.add(Arrays.asList(1, 2)); // 顶点0的相邻顶点
adjList.add(Arrays.asList(0, 2)); // 顶点1的相邻顶点
adjList.add(Arrays.asList(0, 1)); // 顶点2的相邻顶点

</code></pre>
</li><li><p><strong>优缺点</strong>：</p><ul><li>✅ 优点：节省空间（仅存储存在的边）。</li><li>❌ 缺点：访问相邻顶点需遍历列表。</li></ul></li></ul><p><strong>练习2</strong>：用邻接表表示有向有权图（顶点 $A(0) \to B(1)$ 权重5，$B(1) \to C(2)$ 权重3）：</p><pre class="language-java lang-java"><code class="language-java lang-java">// 有权图邻接表，使用自定义边类
class Edge {
    int to; // 目标顶点
    int weight; // 权重
    public Edge(int to, int weight) { this.to = to; this.weight = weight; }
}

List&amp;lt;List&lt;Edge&amp;gt;&gt; adjList = new ArrayList&lt;&gt;();
adjList.add(Arrays.asList(new Edge(1, 5))); // A(0) → B(1)
adjList.add(Arrays.asList(new Edge(2, 3))); // B(1) → C(2)
</code></pre>
<h3 id=""><strong>第三讲：图的遍历算法</strong></h3><p>遍历图的目的是访问每个顶点一次，常见算法有 <strong>深度优先搜索（DFS）</strong> 和 <strong>广度优先搜索（BFS）</strong>。</p><h4 id="31-dfs"><strong>3.1 深度优先搜索（DFS）</strong></h4><ul><li><strong>核心思想</strong>：从起点出发，尽可能深入访问相邻顶点，遇无法继续则回溯。</li><li><strong>实现方式</strong>：递归或栈（此处用递归）。</li><li><strong>算法步骤</strong>：
<ol start="1"><li>标记当前顶点为已访问。</li><li>递归访问所有未访问的相邻顶点。</li></ol></li><li><strong>示例（无向图 $0-1-2-3$）</strong>：
<ul><li>起点0 → 访问0 → 访问1 → 访问2 → 访问3（假设邻接顺序为0→1→2→3）。</li></ul></li><li><p><strong>代码框架（Java）</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">boolean[] visited; // 标记是否访问过
void dfs(int v) {
    visited[v] = true; // 标记当前顶点
    for (int neighbor : adjList.get(v)) { // 遍历相邻顶点
        if (!visited[neighbor]) {
            dfs(neighbor); // 递归访问
        }
    }
}

</code></pre>
</li></ul><h4 id="32-bfs"><strong>3.2 广度优先搜索（BFS）</strong></h4><ul><li><strong>核心思想</strong>：从起点出发，逐层访问相邻顶点（类似“水波扩散”）。</li><li><strong>实现方式</strong>：队列（FIFO）。</li><li><strong>算法步骤</strong>：
<ol start="1"><li>将起点入队，标记为已访问。</li><li>取出队首顶点，访问其所有未访问的相邻顶点，标记并加入队列。</li></ol></li><li><strong>示例（无向图 $0-1-2-3$）</strong>：
<ul><li>起点0 → 队列：[0] → 访问0，相邻顶点1入队 → 队列：[1] → 访问1，相邻顶点2入队 → 队列：[2] → 访问2，相邻顶点3入队 → 访问3。</li></ul></li><li><p><strong>代码框架（Java）</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">boolean[] visited;
void bfs(int start) {
    Queue&amp;lt;Integer&amp;gt; queue = new LinkedList&lt;&gt;();
    queue.offer(start);
    visited[start] = true;
    
    while (!queue.isEmpty()) {
        int v = queue.poll(); // 取出队首顶点
        for (int neighbor : adjList.get(v)) {
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                queue.offer(neighbor); // 相邻顶点入队
            }
        }
    }
}

</code></pre>
</li></ul><p><strong>练习3</strong>：画出以下图的DFS和BFS遍历顺序（起点0）：</p><pre class=""><code class="">邻接表：0: [1, 3], 1: [0, 2], 2: [1], 3: [0, 4], 4: [3]
</code></pre><ul><li><strong>DFS顺序</strong>：0 → 1 → 2 → 3 → 4</li><li><strong>BFS顺序</strong>：0 → 1 → 3 → 2 → 4</li></ul><h3 id=""><strong>第四讲：有权图与经典算法</strong></h3><h4 id="41-mst"><strong>4.1 最小生成树（MST）</strong></h4><ul><li><strong>定义</strong>：连通有权图中，边权和最小的生成树。</li><li><strong>应用场景</strong>：构建最低成本的网络（如城市间管道铺设）。</li><li><strong>算法</strong>：
<ul><li><strong>Prim算法</strong>：从顶点出发，每次选当前生成树与未加入顶点的最小边（适用于稠密图）。</li><li><strong>Kruskal算法</strong>：从边出发，按权重排序后选最小边，用并查集避免成环（适用于稀疏图）。</li></ul></li></ul><h4 id="42-dijkstra"><strong>4.2 最短路径算法（Dijkstra）</strong></h4><ul><li><strong>定义</strong>：求单源顶点到其他所有顶点的最短路径（边权非负）。</li><li><strong>应用场景</strong>：导航系统（如最短驾车路线）。</li><li><strong>核心思想</strong>：维护每个顶点到源点的当前最短距离，逐步更新（贪心策略）。</li><li><p><strong>示例</strong>：源点0到顶点3的最短路径：</p><pre class=""><code class="">图：0→1（权重2），0→2（权重5），1→3（权重3），2→3（权重1）
最短路径：0→2→3（总权重5+1=6）

</code></pre>
</li></ul><h1 id="lecture-11---binary-search-trees">Lecture 11 - Binary Search Trees</h1><h3 id=""><strong>一、二叉树基础</strong></h3><h4 id="1-">1. 二叉树定义</h4><ul><li><strong>结构</strong>：要么为空，要么由根节点、左子树、右子树组成。</li><li><strong>节点</strong>：每个节点包含一个元素（<code>element</code>），以及左右子节点指针（<code>left</code>/<code>right</code>）。</li><li><strong>术语</strong>：
<ul><li>叶子节点：没有子节点的节点（如示例中的45、57、67、107）。</li><li>子树：节点的左/右分支形成的树。</li></ul></li></ul><h4 id="2-java">2. 二叉树的表示（Java代码）</h4><pre class="language-java lang-java"><code class="language-java lang-java">class TreeNode&amp;lt;E&amp;gt; {
    E element;
    TreeNode&amp;lt;E&amp;gt; left;
    TreeNode&amp;lt;E&amp;gt; right;
    public TreeNode(E o) { element = o; }
}
</code></pre><ul><li>每个节点是一个泛型类，可存储任意类型数据。</li></ul><h3 id="bst"><strong>二、二叉搜索树（BST）核心性质</strong></h3><h4 id="1-bst">1. BST的特性</h4><ul><li><strong>无重复元素</strong>（默认）。</li><li><strong>有序性</strong>：
<ul><li>左子树所有节点的值 <strong>小于</strong> 根节点的值。</li><li>右子树所有节点的值 <strong>大于</strong> 根节点的值。</li></ul></li><li><strong>示例</strong>：
<code>
    60
   /  \
 55   100
/  \  /  \
45  57 67  107
</code>
<ul><li>55 &lt; 60 &lt; 100，45 &lt; 55 &lt; 57，67 &lt; 100 &lt; 107，符合BST规则。</li></ul></li></ul><h4 id="2-bst">2. BST的优势</h4><ul><li><strong>高效搜索、插入、删除</strong>（理想情况下时间复杂度为 $O(\log n)$）。</li><li><strong>中序遍历有序</strong>：遍历结果为升序序列（如上述示例中序遍历结果：45, 55, 57, 60, 67, 100, 107）。</li></ul><h3 id="bst"><strong>三、BST核心操作：插入元素</strong></h3><h4 id="1-">1. 插入逻辑</h4><ol start="1"><li><strong>树为空</strong>：直接创建根节点。</li><li><strong>树非空</strong>：
<ul><li>用<code>current</code>和<code>parent</code>指针找到插入位置：
<ul><li>若插入值 &lt; 当前节点值，移动到左子树；</li><li>若插入值 &gt; 当前节点值，移动到右子树；</li><li>若相等（重复），插入失败。</li></ul></li><li>找到<code>parent</code>后，根据值大小决定插入左或右子节点。</li></ul></li></ol><h4 id="2-java">2. 代码实现（Java）</h4><pre class="language-java lang-java"><code class="language-java lang-java">public boolean insert(E element) {
    if (root == null) {
        root = new TreeNode&lt;&gt;(element); // 空树时创建根节点
    } else {
        TreeNode&amp;lt;E&amp;gt; current = root, parent = null;
        while (current != null) {
            parent = current;
            if (element.compareTo(current.element) &lt; 0) {
                current = current.left;
            } else if (element.compareTo(current.element) &gt; 0) {
                current = current.right;
            } else {
                return false; // 重复元素，插入失败
            }
        }
        // 插入到parent的左或右子节点
        if (element.compareTo(parent.element) &lt; 0) {
            parent.left = new TreeNode&lt;&gt;(element);
        } else {
            parent.right = new TreeNode&lt;&gt;(element);
        }
    }
    return true;
}
</code></pre>
<h4 id="3-101bst">3. 示例：插入101到BST</h4><ul><li><strong>步骤跟踪</strong>：
<ol start="1"><li>从根节点60开始，101 &gt; 60 → 移动到右子节点100。</li><li>101 &gt; 100 → 移动到右子节点107。</li><li>101 &lt; 107 → <code>parent=107</code>，<code>current=null</code>，插入到107的左子节点。</li></ol></li><li><strong>结果</strong>：107的左子节点为101，树结构保持BST性质。</li></ul><h3 id="bst"><strong>四、BST核心操作：搜索元素</strong></h3><h4 id="1-">1. 搜索逻辑</h4><ul><li>从根节点开始，逐层比较：
<ul><li>若搜索值 &lt; 当前节点值，搜索左子树；</li><li>若搜索值 &gt; 当前节点值，搜索右子树；</li><li>相等则返回<code>true</code>，遍历完仍未找到则返回<code>false</code>。</li></ul></li></ul><h4 id="2-java">2. 代码实现（Java）</h4><pre class="language-java lang-java"><code class="language-java lang-java">public boolean search(E element) {
    TreeNode&amp;lt;E&amp;gt; current = root;
    while (current != null) {
        if (element.compareTo(current.element) &lt; 0) {
            current = current.left;
        } else if (element.compareTo(current.element) &gt; 0) {
            current = current.right;
        } else {
            return true; // 找到元素
        }
    }
    return false; // 未找到元素
}
</code></pre>
<h4 id="3-57">3. 示例：搜索57</h4><ul><li>60 → 55（左子节点）→ 57（右子节点），找到，返回<code>true</code>。</li></ul><h3 id=""><strong>五、树的遍历</strong></h3><h4 id="1-">1. 深度优先遍历（递归实现）</h4><ul><li><strong>前序遍历</strong>（根→左→右）：访问根节点，递归遍历左子树，递归遍历右子树。</li><li><strong>中序遍历</strong>（左→根→右）：BST中序遍历结果为升序序列（重点！）。</li><li><strong>后序遍历</strong>（左→右→根）：常用于删除树前释放资源。</li></ul><h4 id="2-">2. 代码示例（中序遍历）</h4><pre class="language-java lang-java"><code class="language-java lang-java">public void inorder() {
    inorder(root); // 从根节点开始
}

private void inorder(TreeNode&amp;lt;E&amp;gt; root) {
    if (root == null) return;
    inorder(root.left);   // 左子树
    System.out.print(root.element + &quot; &quot;); // 根节点
    inorder(root.right);  // 右子树
}
</code></pre>
<h4 id="3-">3. 广度优先遍历（层序遍历）</h4><ul><li>按层访问：根节点→第一层子节点→第二层子节点→…</li><li><strong>实现</strong>：使用队列，先入队根节点，每次出队一个节点，入队其左右子节点（若存在）。</li></ul><p>好的！我们继续学习剩余内容，包括 <strong>BST删除操作</strong>、<strong>迭代器实现</strong>、<strong>Huffman编码</strong> 和 <strong>时间复杂度分析</strong>。</p>
<h3 id="bst"><strong>六、BST核心操作：删除元素</strong></h3><p>删除操作是BST中最复杂的操作，需分情况处理，确保删除后仍满足BST性质。</p><h4 id="1-">1. 准备工作：定位节点</h4><ul><li>用<code>current</code>指向待删除节点，<code>parent</code>指向其父节点。</li><li>若<code>current</code>为<code>null</code>，说明元素不存在，删除失败。</li></ul><h4 id="2-1">2. 情况1：当前节点无左子树</h4><ul><li><strong>操作</strong>：直接用当前节点的右子树替代当前节点。</li><li><p><strong>示例</strong>：删除节点10（父节点20）：</p><pre class=""><code class="">原树结构：       删除后结构：
    20            20
   /  \          /  \
 10   40        40
  \    / \             
  16 30 80        

</code></pre>
</li><li><p><strong>代码逻辑</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">if (current.left == null) {
    if (parent == null) { // 删除根节点
        root = current.right;
    } else if (current == parent.left) { // 当前节点是左子节点
        parent.left = current.right;
    } else { // 当前节点是右子节点
        parent.right = current.right;
    }
}

</code></pre>
</li></ul><h4 id="3-2">3. 情况2：当前节点有左子树</h4><ul><li><strong>核心思想</strong>：找到当前节点左子树中的 <strong>最右节点（rightMost）</strong>，用其值替换当前节点的值，然后删除该最右节点（因其无右子树，转化为情况1）。</li><li><strong>原因</strong>：rightMost是左子树中最大的值，替换后不会破坏BST的有序性。</li><li><strong>步骤</strong>：
<ol start="1"><li>找到<code>current</code>左子树的最右节点<code>rightMost</code>及其父节点<code>parentOfRightMost</code>。</li><li>用<code>rightMost</code>的值覆盖<code>current</code>的值。</li><li>删除<code>rightMost</code>（若<code>rightMost</code>有左子树，连接到<code>parentOfRightMost</code>的右侧）。</li></ol></li><li><p><strong>示例</strong>：删除节点20（左子树最右节点为16）：</p><pre class=""><code class="">原树结构：       替换后结构：
    20            16
   /  \          /  \
 10   40        10   40
  \    / \             
  16 30 80        

</code></pre>
</li><li><p><strong>代码逻辑</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">else { // 当前节点有左子树
    TreeNode&amp;lt;E&amp;gt; parentOfRightMost = current;
    TreeNode&amp;lt;E&amp;gt; rightMost = current.left;
    // 找到左子树的最右节点
    while (rightMost.right != null) {
        parentOfRightMost = rightMost;
        rightMost = rightMost.right;
    }
    // 用rightMost的值替换current的值
    current.element = rightMost.element;
    // 删除rightMost节点（其无右子树，可能有左子树）
    if (parentOfRightMost == current) { // rightMost是current的左子节点
        parentOfRightMost.left = rightMost.left;
    } else {
        parentOfRightMost.right = rightMost.left;
    }
}

</code></pre>
</li></ul><h3 id=""><strong>七、使用迭代器遍历树</strong></h3><h4 id="1-">1. 为什么需要迭代器？</h4><ul><li>传统的<code>inorder()</code>、<code>preorder()</code>方法只能打印元素，而迭代器允许通过<code>foreach</code>循环灵活处理元素（如转换为大写、过滤等）。</li></ul><h4 id="2-">2. 实现步骤（以中序遍历为例）</h4><ol start="1"><li><strong>定义迭代器类</strong>：实现<code>java.util.Iterator</code>接口。</li><li><strong>中序遍历存储元素</strong>：在迭代器构造函数中执行中序遍历，将元素存入列表<code>list</code>。</li><li><strong>实现迭代器方法</strong>：
<ul><li><code>hasNext()</code>：检查列表是否有剩余元素。</li><li><code>next()</code>：返回当前元素并移动指针。</li><li><code>remove()</code>：删除当前元素（需重新构建列表）。</li></ul></li></ol><h4 id="3-">3. 代码示例</h4><pre class="language-java lang-java"><code class="language-java lang-java">public Iterator&amp;lt;E&amp;gt; iterator() {
    return new InorderIterator();
}

private class InorderIterator implements Iterator&amp;lt;E&amp;gt; {
    private ArrayList&amp;lt;E&amp;gt; list = new ArrayList&lt;&gt;();
    private int current = 0;

    public InorderIterator() {
        inorder(root); // 中序遍历填充list
    }

    private void inorder(TreeNode&amp;lt;E&amp;gt; root) {
        if (root == null) return;
        inorder(root.left);
        list.add(root.element);
        inorder(root.right);
    }

    @Override
    public boolean hasNext() {
        return current &lt; list.size();
    }

    @Override
    public E next() {
        return list.get(current++);
    }

    @Override
    public void remove() {
        BST.this.delete(list.get(current)); // 删除元素
        list.clear(); // 清空列表并重新遍历
        inorder(root);
        current = 0;
    }
}
</code></pre>
<h4 id="4-">4. 使用示例</h4><pre class="language-java lang-java"><code class="language-java lang-java">BST&amp;lt;String&amp;gt; tree = new BST&lt;&gt;();
tree.insert(&quot;A&quot;);
tree.insert(&quot;B&quot;);
tree.insert(&quot;C&quot;);
for (String s : tree) { // 自动调用迭代器
    System.out.print(s.toUpperCase() + &quot; &quot;); // 输出: A B C
}
</code></pre>
<h3 id="huffman"><strong>八、Huffman编码：二叉树在数据压缩中的应用</strong></h3><h4 id="1-">1. 核心思想</h4><ul><li>用 <strong>频率高的字符分配短编码，频率低的分配长编码</strong>，减少整体编码长度。</li><li>通过构建<strong>Huffman树</strong>（带权路径长度最短的二叉树）实现。</li></ul><h4 id="2-huffman">2. 构建Huffman树的步骤（贪心算法）</h4><ol start="1"><li><strong>统计字符频率</strong>：例如，字符串&quot;Mississippi&quot;中，&#x27;i&#x27;和&#x27;s&#x27;频率为4，&#x27;M&#x27;为1，&#x27;p&#x27;为2。</li><li><strong>创建初始森林</strong>：每个字符作为一棵单节点树，权重为频率。</li><li><strong>合并最小权重树</strong>：
<ul><li>每次选取权重最小的两棵树，创建父节点，权重为两者之和。</li><li>重复直到只剩一棵树（Huffman树）。</li></ul></li></ol><h4 id="3-">3. 生成字符编码</h4><ul><li>从根节点到叶子节点的路径中，左分支记为<code>0</code>，右分支记为<code>1</code>。</li><li><p><strong>示例</strong>：</p><pre class=""><code class="">    12（根）
   /  \
 5     7
/ \   / \
M p  s  i (频率：1,2,4,4)
</code></pre>
<p>编码：M=000, p=001, s=01, i=1</p></li></ul><h4 id="4-">4. 代码实现（简化版）</h4><pre class="language-java lang-java"><code class="language-java lang-java">// 构建Huffman树
public static HuffmanTree buildHuffmanTree(int[] frequencies) {
    PriorityQueue&amp;lt;HuffmanTree&amp;gt; queue = new PriorityQueue&lt;&gt;();
    // 初始化单节点树
    for (int i = 0; i &lt; 256; i++) {
        if (frequencies[i] &gt; 0) {
            queue.offer(new HuffmanTree(frequencies[i], (char) i));
        }
    }
    // 合并树
    while (queue.size() &gt; 1) {
        HuffmanTree t1 = queue.poll();
        HuffmanTree t2 = queue.poll();
        HuffmanTree newTree = new HuffmanTree(t1, t2);
        queue.offer(newTree);
    }
    return queue.poll();
}

// 生成编码
public static String[] getHuffmanCodes(HuffmanTree root) {
    String[] codes = new String[256];
    generateCodes(root.root, &quot;&quot;, codes);
    return codes;
}

private static void generateCodes(HuffmanNode node, String code, String[] codes) {
    if (node.left == null &amp;&amp; node.right == null) { // 叶子节点
        codes[(int) node.element] = code;
        return;
    }
    generateCodes(node.left, code + &quot;0&quot;, codes); // 左分支加0
    generateCodes(node.right, code + &quot;1&quot;, codes); // 右分支加1
}
</code></pre>
<h3 id=""><strong>九、时间复杂度分析</strong></h3><table><thead><tr><th> 操作         </th><th> 时间复杂度          </th><th> 说明                          </th></tr></thead><tbody><tr><td> 插入/搜索/删除 </td><td> $O(h)$（h为树高） </td><td> 最坏情况树退化为链表，$h=n$，$O(n)$；理想情况平衡树，$h=\log n$，$O(\log n)$ </td></tr><tr><td> 遍历（前/中/后序） </td><td> $O(n)$            </td><td> 每个节点访问一次              </td></tr></tbody></table>
<h3 id=""><strong>十、课程总结</strong></h3><h4 id="1-">1. 核心知识点</h4><ul><li><strong>BST性质</strong>：左&lt;根&lt;右，中序遍历有序。</li><li><strong>三大操作</strong>：
<ul><li>插入：找到合适位置，保持BST性质。</li><li>搜索：逐层比较，高效查找。</li><li>删除：分情况处理，维护BST结构。</li></ul></li><li><strong>遍历方式</strong>：前序/中序/后序（递归）、层序（队列）。</li><li><strong>迭代器</strong>：通过中序遍历实现，支持灵活遍历。</li><li><strong>Huffman编码</strong>：利用二叉树压缩数据，贪心算法构建最优树。</li></ul><h4 id="2-">2. 代码结构建议</h4><ul><li><strong>TreeNode</strong>：作为内部类，封装节点属性。</li><li><strong>BST类</strong>：实现插入、删除、搜索、遍历、迭代器等方法。</li><li><strong>Huffman相关类</strong>：独立实现树构建和编码生成。</li></ul><h4 id="3-">3. 练习建议</h4><ol start="1"><li><strong>手动绘制</strong>：删除BST中的节点，如删除示例树中的60，观察结构变化。</li><li><strong>编码实践</strong>：实现Huffman编码的完整流程（编码+解码）。</li><li><strong>扩展思考</strong>：如何优化BST的性能？（提示：平衡树如AVL、红黑树）</li></ol><h3 id=""><strong>课后问题</strong></h3><ol start="1"><li>删除BST节点时，为什么选择左子树的最右节点替换，而不是右子树的最左节点？</li><li>Huffman树中，为什么合并最小权重的两棵树能得到最优编码？</li><li>尝试用迭代方式实现前序遍历（非递归）。</li></ol><h1 id="lecture-12---avl-trees">Lecture 12 - AVL Trees</h1><h3 id="avl"><strong>一、AVL树核心知识</strong></h3><h4 id="1-avl">1. <strong>为什么需要AVL树？</strong></h4><ul><li><strong>问题</strong>：普通二叉搜索树（BST）在最坏情况下高度为O(n)，导致搜索、插入、删除的时间复杂度退化为O(n)。</li><li><strong>目标</strong>：通过保持树的“平衡”，使高度近似O(log n)，从而保证操作的高效性。</li><li><strong>平衡定义</strong>：每个节点的左右子树高度差（平衡因子）绝对值不超过1。平衡因子 = 右子树高度 - 左子树高度。</li></ul><h4 id="2-avl">2. <strong>AVL树的平衡操作（四种旋转）</strong></h4><p>当插入或删除节点导致某节点平衡因子为±2时，需要通过旋转重新平衡。以下是四种旋转的核心逻辑：</p><h5 id="ll"><strong>LL旋转（左左失衡，右单旋）</strong></h5><ul><li><strong>场景</strong>：节点A左子树高度比右子树高2，且左子节点B是左重（平衡因子≤0）。</li><li><strong>操作</strong>：将B提升为新根，A变为B的右子节点，B原来的右子树T3变为A的左子树。</li><li><p><strong>示例</strong>：</p><pre class=""><code class="">     A(-2)            B(0)
    /                /   \
   B(-1)    →       T1    A(0)
  /  \                 /  \
 T1  T2               T2  T3

</code></pre>
</li></ul><h5 id="rr"><strong>RR旋转（右右失衡，左单旋）</strong></h5><ul><li><strong>场景</strong>：节点A右子树高度比左子树高2，且右子节点B是右重（平衡因子≥0）。</li><li><strong>操作</strong>：将B提升为新根，A变为B的左子节点，B原来的左子树T2变为A的右子树。</li><li><p><strong>示例</strong>：</p><pre class=""><code class="">     A(+2)            B(0)
      \                /   \
       B(+1)   →      A(0)  T3
      /  \              /  \
     T1  T2            T1  T2

</code></pre>
</li></ul><h5 id="lrba"><strong>LR旋转（左右失衡，先左旋B再右旋A）</strong></h5><ul><li><strong>场景</strong>：节点A左子树高度比右子树高2，且左子节点B是右重（平衡因子+1）。</li><li><strong>操作</strong>：先对B进行左旋转，再对A进行右旋转，中间节点C成为新根。</li><li><p><strong>示例</strong>：</p><pre class=""><code class="">     A(-2)            C(0)
    /                 /   \
   B(+1)    →       B(0)  A(0)
    \                /  \
     C(0)          T1  T2
    /  \
   T1  T2

</code></pre>
</li></ul><h5 id="rlba"><strong>RL旋转（右左失衡，先右旋B再左旋A）</strong></h5><ul><li><strong>场景</strong>：节点A右子树高度比左子树高2，且右子节点B是左重（平衡因子-1）。</li><li><strong>操作</strong>：先对B进行右旋转，再对A进行左旋转，中间节点C成为新根。</li><li><p><strong>示例</strong>：</p><pre class=""><code class="">     A(+2)            C(0)
      \                 /   \
       B(-1)   →      A(0)  B(0)
      /                /  \
     C(0)            T1  T2
    /  \
   T1  T2

</code></pre>
</li></ul><h4 id="3-avl">3. <strong>AVL树的实现关键点</strong></h4><ul><li><strong>节点设计</strong>：每个节点包含高度属性，用于计算平衡因子。</li><li><strong>插入/删除流程</strong>1
<ol start="1"><li>按BST规则插入/删除节点。</li><li>从插入/删除节点向上遍历，更新路径上所有节点的高度。</li><li>检查每个节点的平衡因子，触发对应的旋转操作。</li></ol></li><li><p><strong>代码示例（插入后的平衡处理）</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">private void balancePath(E e) {
    ArrayList&amp;lt;TreeNode&lt;E&amp;gt;&gt; path = path(e); // 获取插入路径
    for (int i = path.size() - 1; i &gt;= 0; i--) {
        AVLTreeNode&amp;lt;E&amp;gt; A = (AVLTreeNode&amp;lt;E&amp;gt;) path.get(i);
        updateHeight(A); // 更新高度
        int bf = balanceFactor(A);
        if (bf == -2) { // 左重
            if (balanceFactor((AVLTreeNode&amp;lt;E&amp;gt;) A.left) &lt;= 0) {
                balanceLL(A, parent); // LL旋转
            } else {
                balanceLR(A, parent); // LR旋转
            }
        } else if (bf == +2) { // 右重
            if (balanceFactor((AVLTreeNode&amp;lt;E&amp;gt;) A.right) &gt;= 0) {
                balanceRR(A, parent); // RR旋转
            } else {
                balanceRL(A, parent); // RL旋转
            }
        }
    }
}

</code></pre>
</li></ul><h4 id="4-">4. <strong>时间复杂度分析</strong></h4><ul><li>树的高度为O(log n)，平衡操作（旋转）是常数时间，因此插入、删除、搜索的时间复杂度均为<strong>O(log n)</strong>。</li></ul><h3 id=""><strong>二、哈希表核心知识</strong></h3><h4 id="1-">1. <strong>什么是哈希表？</strong></h4><ul><li><strong>目标</strong>：通过哈希函数将键映射到数组索引，实现O(1)平均时间复杂度的查找、插入、删除。</li><li><strong>核心组件</strong>：
<ul><li><strong>哈希函数</strong>：将键转换为哈希码（整数），再压缩为数组索引（如<code>hashCode % N</code>，N为表大小）。</li><li><strong>碰撞处理</strong>：当不同键映射到同一索引时，通过开放寻址法或链地址法处理。</li></ul></li></ul><h4 id="2-">2. <strong>哈希函数与哈希码</strong></h4><ul><li><strong>哈希码计算</strong>：
<ul><li><strong>基本类型</strong>：直接转换（如int类型直接作为哈希码）。</li><li><strong>长类型/双精度</strong>：通过高32位和低32位异或（XOR）避免信息丢失。</li><li><strong>字符串</strong>：通过多项式哈希（如<code>hash = 31 * hash + charValue</code>）。</li></ul></li><li><strong>压缩方法</strong>：使用取模运算<code>hashCode % N</code>，N通常为质数以减少碰撞。</li></ul><h4 id="3-">3. <strong>碰撞处理方法</strong></h4><h5 id=""><strong>开放寻址法</strong>：在数组中寻找下一个可用位置。</h5><ul><li><strong>线性探测</strong>：依次检查下一个位置（<code>(index + 1) % N</code>，<code>(index + 2) % N</code>...），易形成“聚类”。</li><li><strong>二次探测</strong>：检查<code>(index + j²) % N</code>（j=1,2,3...），减少聚类但可能导致“二次聚类”。</li><li><strong>双重哈希</strong>：使用第二个哈希函数确定步长（如<code>h&#x27;(key) = 7 - key % 7</code>），步长需与N互质。</li></ul><h5 id="java-8hashmap"><strong>链地址法（分离链接）</strong>：每个索引对应一个链表（或链表+红黑树，如Java 8后的HashMap）。</h5><ul><li><strong>优点</strong>：处理碰撞简单，无负载因子上限。</li><li><strong>缺点</strong>：链表过长时查找效率下降，需结合负载因子扩容。</li></ul><h4 id="4-">4. <strong>负载因子与重新哈希</strong></h4><ul><li><strong>负载因子</strong>：<code>λ = 元素数 / 表大小</code>，开放寻址法通常需λ&lt;0.5，链地址法λ&lt;0.9。</li><li><strong>重新哈希</strong>：当λ超过阈值时，增大表大小（如翻倍），重新计算所有键的索引，避免性能下降。</li></ul><h4 id="5-myhashmap">5. <strong>MyHashMap实现关键点</strong></h4><ul><li><strong>哈希函数优化</strong>：通过位移和异或（如Java的<code>supplementalHash</code>）让哈希码更均匀分布。</li><li><strong>链地址法实现</strong>：使用数组存储链表，插入时检查键是否存在，存在则更新值，否则添加到链表尾部。</li><li><p><strong>代码示例（哈希函数）</strong>：</p><pre class="language-java lang-java"><code class="language-java lang-java">private int hash(int hashCode) {
    hashCode = (hashCode &gt;&gt;&gt; 20) ^ (hashCode &gt;&gt;&gt; 12); // 混合高位
    return hashCode ^ (hashCode &gt;&gt;&gt; 7) ^ (hashCode &gt;&gt;&gt; 4); // 进一步混合
}

</code></pre>
</li></ul><h3 id=""><strong>三、学习建议</strong></h3><ol start="1"><li><strong>AVL树部分</strong>：
<ul><li>手动绘制插入/删除示例（如文档中的插入25,20,5,34...的过程），观察旋转如何恢复平衡。</li><li>编写旋转函数时，先理清节点指针的变化（父节点、左右子树的连接），再处理高度更新。</li></ul></li><li><strong>哈希表部分</strong>：
<ul><li>对比不同碰撞处理方法的优缺点，思考为何Java的HashMap在链长较长时转为红黑树。</li><li>实现MyHashMap时，注意负载因子的阈值设置和重新哈希的时机，避免频繁扩容。</li></ul></li><li><strong>实践步骤</strong>：
<ul><li>先完成AVL树的节点定义、插入/删除的BST部分，再逐步添加平衡逻辑。</li><li>哈希表从简单的链地址法开始，实现put、get、containsKey方法，再考虑扩容和性能优化。</li></ul></li></ol><h3 id=""><strong>四、课后小问题（检验理解）</strong></h3><ol start="1"><li>AVL树中，LL旋转和LR旋转的区别是什么？什么场景下触发？</li><li>哈希表中，为什么负载因子过高会影响性能？重新哈希的代价是什么？</li><li>链地址法和开放寻址法在删除操作上有何不同？（提示：开放寻址法删除需标记“墓碑”）</li></ol></div><p style="text-align:right"><a href="https://blog.chiran.top/posts/大学生涯/CPT204#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.chiran.top/posts/大学生涯/CPT204</link><guid isPermaLink="true">https://blog.chiran.top/posts/大学生涯/CPT204</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Fri, 03 Apr 2026 08:37:52 GMT</pubDate></item><item><title><![CDATA[CAN201-网络安全3]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A83">https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A83</a></blockquote><div><h1 id="network-layer-security-ipsec">Network layer security: IPsec</h1><ol start="1"><li><strong>网络层机密性（Network-layer confidentiality）</strong>：在两个网络实体之间，发送实体对数据报负载（payload）进行加密，这里的负载可以是TCP或UDP段、ICMP消息、OSPF消息等各种类型的数据。这样，所有从一个实体发送到另一个实体的数据都会对任何第三方（可能在嗅探网络的）隐藏起来，包括网页、电子邮件、P2P文件传输、TCP SYN数据包等，实现了全面的“覆盖式”保护。</li><li><strong>虚拟专用网络（Virtual Private Networks ，VPNs）</strong> 
<ul><li><strong>动机（motivation）</strong>：机构常常为了安全需要私有网络。虽然可以部署独立的物理网络，但成本高昂，需要单独的路由器、链路、DNS基础设施等。VPN则是将机构的办公室间流量通过公共互联网发送，在进入公共互联网之前进行加密，并且在逻辑上与其他流量分离，既保证了安全性又降低了成本。</li><li><strong>工作原理（IPsec in VPNs）</strong>：以笔记本电脑（laptop）和销售人员（salesperson）通过IPsec与总部（headquarters）和分支机构（branch office）通信为例，IPsec在其中起到了关键的安全保障作用。</li></ul></li><li><strong>IPsec服务（IPsec services）</strong> 
<ul><li><strong>数据完整性（data integrity）</strong>：确保数据在传输过程中没有被篡改。</li><li><strong>源认证（origin authentication）</strong>：验证数据的发送源是否合法。</li><li><strong>防止重放攻击（replay attack prevention）</strong>：通过一些机制（如序列号）来防止攻击者重放之前捕获的数据包。</li><li><strong>机密性（confidentiality）</strong>：保证数据内容不被未授权方获取。</li><li><strong>协议（protocols）</strong>：包括Authentication Header（AH）和Encapsulation Security Payload （ESP）。AH提供源认证和数据完整性，但不提供机密性；ESP则提供源认证、数据完整性和机密性，并且比AH更广泛使用。</li></ul></li><li><strong>IPsec模式（IPsec modes）</strong> 
<ul><li><strong>传输模式（主机模式）（IPsec transport mode ，host mode）</strong>：IPsec数据报由端系统发送和接收，主要用于保护上层协议。例如，当两个主机之间进行通信时，直接在主机上对传输的数据进行IPsec处理。</li><li><strong>隧道模式（IPsec tunneling mode）</strong>：边缘路由器需要支持IPsec。常用于连接不同网络，如分支机构与总部之间的连接，在边缘路由器处对数据包进行封装和加密，通过公共网络传输，到达对端边缘路由器后再解封装还原数据。在实际应用中，有四种可能的组合，其中隧道模式结合ESP是最常见和最重要的。</li></ul></li><li><strong>安全关联（Security Associations ，SAs）</strong> 
<ul><li><strong>建立与特性</strong>：在发送数据之前，从发送实体到接收实体建立“安全关联（SA）”。SAs是单向的，即只为一个方向建立逻辑连接。发送和接收实体都维护关于SA的状态信息，这与TCP端点维护状态信息类似，但要注意IP是无连接的，而IPsec是面向连接的。例如，在一个包含一个总部办公室、一个分支机构办公室和n个出差销售人员的VPN中，计算SAs的数量就需要考虑到这种单向性和不同实体之间的连接情况。</li><li><strong>SA的存储信息（Example SA）</strong>：以从R1到R2的SA为例，R1存储的信息包括32位SA标识符（Security Parameter Index ，SPI）、源SA接口、目的SA接口、使用的加密类型（如3DES with CBC）、加密密钥、使用的完整性检查类型（如HMAC with MD5）、认证密钥等。这些信息在处理IPsec数据报时起到关键作用，如R1在发送IPsec数据报时，会根据存储在安全关联数据库（Security Association Database，SAD）中的SA信息来确定如何处理数据报；当IPsec数据报到达R2时，R2通过检查数据报中的SPI，在SAD中查找对应的SA并据此处理数据报。</li></ul></li><li><strong>IPsec数据报（IPsec datagram）</strong>：以隧道模式结合ESP为例，IPsec数据报的结构包括新的IP头、原始IP头、ESP头、ESP认证字段（ESP MAC）、数据报负载、ESP尾部（ESP trailer）等部分。原始数据报经过一系列处理生成IPsec数据报，具体步骤为：在原始数据报后添加“ESP trailer”字段，使用SA指定的算法和密钥对结果进行加密，在加密数据前添加“ESP header”形成“enchilada”，再使用SA中的算法和密钥对整个“enchilada”创建认证MAC（ESP MAC）并添加到后面形成负载，最后创建全新的IP头并添加在负载前。在“enchilada”内部，ESP trailer用于块密码的填充，ESP header包含SPI（用于接收实体确定如何处理）和序列号（用于防止重放攻击），ESP认证字段中的MAC是使用共享密钥创建的。</li><li><strong>IPsec序列号（IPsec sequence numbers）</strong>：对于新的SA，发送方将序列号初始化为0。每次在SA上发送数据报时，发送方递增序列号计数器并将值放入序列号字段。其目的是防止攻击者嗅探并重放数据包，因为接收重复的、经过认证的IP数据包可能会干扰服务。接收方通过检查序列号来检测重复数据包，并且不跟踪所有接收的数据包，而是使用一个窗口来实现。这种机制有效地增强了IPsec的安全性，防止重放攻击对网络造成威胁。</li><li><strong>安全策略数据库（Security Policy Database ，SPD）</strong>：发送实体需要根据安全策略来决定对于给定的数据报是使用IPsec还是普通IP，以及使用哪个SA。策略可能基于源和目的IP地址、协议号等信息。SPD中的信息指示了对于到达的数据报“做什么”，而SAD中的信息则指示“怎么做”，两者协同工作来确保IPsec的正确应用。</li><li><strong>IKE （Internet Key Exchange）</strong>：在之前手动建立IPsec SAs的示例中，如手动配置SPI、源IP、目的IP、协议、加密算法、加密密钥、HMAC算法、HMAC密钥等信息在实际的VPN场景中，当有大量端点时是不切实际的。因此，使用IPsec IKE （Internet Key Exchange）协议（在RFC 5996中指定）来自动进行算法、密钥和SPI号码的交换，简化了IPsec的配置和管理过程，提高了效率和可扩展性。 
IPsec通过多种机制和协议，在网络层为数据提供了全面的安全保障，包括机密性、完整性、源认证和防止重放攻击等，适用于多种网络设备和连接场景，是构建安全网络通信的重要技术。</li></ol><h1 id="operational-security-firewalls-and-ids">Operational security: firewalls and IDS</h1><ol start="1"><li><strong>防火墙（Firewalls）</strong> 
<ul><li><strong>隔离与作用（Isolation and Functions）</strong>：防火墙用于将组织的内部网络与更大的互联网隔离开，只允许特定的数据包通过，阻止其他数据包。其主要作用包括防止拒绝服务攻击（如SYN洪水攻击，攻击者建立大量虚假TCP连接，耗尽资源，使“真实”连接无法建立）、防止内部数据被非法修改或访问（例如防止攻击者篡改CIA的主页）以及只允许授权用户访问内部网络。</li><li><strong>类型（Types）</strong>
<ul><li><strong>无状态包过滤器（Stateless Packet Filters）</strong>：内部网络通过路由器防火墙连接到互联网，路由器逐个数据包进行过滤，根据源IP地址、目的IP地址、TCP/UDP源和目的端口号、ICMP消息类型、TCP SYN和ACK位等决定是否转发或丢弃数据包。例如，可以通过设置规则来阻止特定协议或端口的数据包，如阻止IP协议字段为17且源或目的端口为23的传入和传出数据报（会导致所有传入、传出UDP流和telnet连接被阻止），或者阻止ACK = 0的入站TCP段（防止外部客户端与内部客户端建立TCP连接，但允许内部客户端连接到外部）。还可以通过访问控制列表（Access Control Lists，ACL）来更精确地定义过滤规则，ACL是一个规则表，按照从上到下的顺序应用于传入数据包，每个规则是（动作，条件）对，类似于OpenFlow转发表。</li><li><strong>有状态包过滤器（Stateful Packet Filters）</strong>：无状态包过滤器可能会允许一些“不合理”的数据包通过，如有状态包过滤器则会跟踪每个TCP连接的状态，包括连接建立（SYN）和拆除（FIN），从而确定传入和传出数据包是否“合理”，并在防火墙处对不活动的连接进行超时处理，不再允许其数据包通过。ACL会扩充以指示在允许数据包之前需要检查连接状态表。</li><li><strong>应用网关（Application Gateways）</strong>：应用网关不仅在IP/TCP/UDP字段上过滤数据包，还在应用数据上进行过滤。例如，若要允许特定内部用户进行telnet外部访问，需要所有telnet用户通过网关进行telnet操作，对于授权用户，网关会建立到目标主机的telnet连接，并在两个连接之间中继数据，同时路由器过滤器会阻止所有非源自网关的telnet连接。</li></ul></li><li><strong>局限性（Limitations）</strong>：防火墙存在一些局限性，如路由器难以确定数据是否真正来自声称的源（IP欺骗问题）；如果多个应用需要特殊处理，每个应用都需要自己的应用网关；客户端软件必须知道如何联系网关（如在Web浏览器中设置代理的IP地址）；过滤器对UDP通常采用全有或全无的策略；并且在安全与对外通信程度之间需要权衡，即使有防火墙保护，许多高度受保护的站点仍可能遭受攻击。</li></ul></li><li><strong>入侵检测系统（Intrusion Detection Systems ，IDS）</strong> 
<ul><li><strong>与包过滤的区别（Difference from Packet Filtering）</strong>：包过滤仅对TCP/IP头进行操作，且不进行会话之间的关联检查。而IDS进行深度包检测，会查看数据包内容（例如将数据包中的字符串与已知病毒、攻击字符串数据库进行检查），并且会检查多个数据包之间的相关性，能够检测端口扫描、网络映射、拒绝服务（DoS）攻击等多种攻击行为。</li><li><strong>部署（Deployment）</strong>：通常会在网络中的不同位置部署多个IDS，例如在防火墙、内部网络、Web服务器、FTP服务器、DNS服务器等周围，形成一个多层次的检测体系，以提高检测的准确性和全面性，更好地保护网络安全。 
防火墙和IDS在网络安全中起着至关重要的作用，防火墙主要用于阻止未经授权的访问，而IDS则侧重于检测潜在的入侵行为，两者相辅相成，共同构建起网络安全的防护体系。</li></ul></li></ol></div><p style="text-align:right"><a href="https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A83#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A83</link><guid isPermaLink="true">https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A83</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Mon, 06 Jan 2025 06:11:45 GMT</pubDate></item><item><title><![CDATA[CAN201-网络安全2]]></title><description><![CDATA[<p>当前内容无法在 RSS 阅读器中正确渲染，请前往：<a href="https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A82">https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A82</a></p>]]></description><link>https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A82</link><guid isPermaLink="true">https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A82</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Mon, 06 Jan 2025 05:43:45 GMT</pubDate></item><item><title><![CDATA[CAN201-网络安全1]]></title><description><![CDATA[<p>当前内容无法在 RSS 阅读器中正确渲染，请前往：<a href="https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A81">https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A81</a></p>]]></description><link>https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A81</link><guid isPermaLink="true">https://blog.chiran.top/posts/大学生涯/CAN201-%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A81</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Mon, 06 Jan 2025 04:43:45 GMT</pubDate></item><item><title><![CDATA[CAN201-链路层 2]]></title><description><![CDATA[<p>当前内容无法在 RSS 阅读器中正确渲染，请前往：<a href="https://blog.chiran.top/posts/大学生涯/CAN201-%E9%93%BE%E8%B7%AF%E5%B1%82%202">https://blog.chiran.top/posts/大学生涯/CAN201-%E9%93%BE%E8%B7%AF%E5%B1%82%202</a></p>]]></description><link>https://blog.chiran.top/posts/大学生涯/CAN201-%E9%93%BE%E8%B7%AF%E5%B1%82%202</link><guid isPermaLink="true">https://blog.chiran.top/posts/大学生涯/CAN201-%E9%93%BE%E8%B7%AF%E5%B1%82%202</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Mon, 06 Jan 2025 03:43:45 GMT</pubDate></item><item><title><![CDATA[CPT205-光与材质]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.chiran.top/posts/大学生涯/CPT205-%E5%85%89%E4%B8%8E%E6%9D%90%E8%B4%A8">https://blog.chiran.top/posts/大学生涯/CPT205-%E5%85%89%E4%B8%8E%E6%9D%90%E8%B4%A8</a></blockquote><div><h3 id="">光源</h3><ol start="1"><li><strong>点光源（Point Light）</strong> 
<ul><li>模型简单，用RGB三个分量指定单一颜色发射辐射能。</li><li>离场景不太近的大光源可视为点光源，光线从光源位置沿径向发散。</li></ul></li><li><strong>方向光（Directional Light）</strong> 
<ul><li>也叫无限远/平行光，光线平行。</li><li>离场景很远的大光源（如太阳）可近似为点光源，但方向效果变化小。</li></ul></li><li><strong>聚光灯（Spot Light）</strong>
<ul><li>由局部光源改造，可设置方向向量、角度限制、位置和颜色，物体在光源方向限制外不被照亮。</li></ul></li></ol><h3 id="">光照效果</h3><ol start="1"><li><strong>环境光（Ambient Light）</strong> 
<ul><li>是一种通用背景非定向照明，使物体显示固有强度，若无不同阴影，物体呈单色轮廓。</li><li>由场景中各种表面反射光产生，撞击表面后向各方向均匀散射，方向难以确定，室内背光环境光成分大，室外聚光灯环境光成分小。</li></ul></li><li><strong>漫反射（Diffuse Reflectance）</strong> 
<ul><li>光线从一个方向来，垂直照射表面比斜射更亮，撞击表面后向各方向均匀散射，无论眼睛位置在哪，看起来亮度相同。</li><li>粗糙或颗粒状表面易向各方向散射反射光，影响照明因素包括点光源强度、材料漫反射系数和入射角。</li></ul></li><li><strong>镜面反射（Specular Reflectance）</strong> 
<ul><li>光线从特定方向来，倾向于在特定方向反射，如激光束从高质量镜子反射。</li><li>影响因素包括材料镜面反射系数、视角与反射方向的相对角度、光源强度，反射光颜色与光源相同，随着视角偏离反射方向，反射率迅速下降。</li></ul></li></ol><h3 id="attenuation">衰减（Attenuation）</h3><ol start="1"><li>对于点光源，考虑光因距离产生的衰减，距离衰减模型包含常数、线性和二次项，公式为$f(d)=\frac{1}{k<em>{c}+k l d+k</em>{q}d^{2}}$。</li><li>方向光因无限远不考虑衰减。</li></ol><h3 id="">光照模型与着色</h3><ol start="1"><li><strong>光照模型（Lighting Model）</strong> 
<ul><li>计算物体表面光照位置颜色，考虑透明度、颜色反射系数和纹理参数等光学属性，可应用于每个投影位置或通过插值计算表面颜色。</li></ul></li><li><strong>Phong模型</strong> 
<ul><li>计算迅速，考虑漫反射、镜面反射和环境光三个分量，使用到光源方向向量、观察方向向量、面法向量和完美反射方向向量四个向量。</li><li>对于每个点光源有9个系数，与材料属性匹配，模型公式为$I = k<em>{d}I</em>{d}l \cdot n + k<em>{s}I</em>{s}(v \cdot r)^{\alpha}+k<em>{a}I</em>{a}$（不含距离项），每个颜色分量需累加所有光源贡献。</li></ul></li><li><strong>多边形着色（Polygonal Shading）</strong> 
<ul><li><strong>顶点着色（Vertex Shading）</strong>：对每个顶点进行着色计算，顶点颜色可作为顶点属性发送到顶点着色器，或在顶点着色器中计算，默认顶点颜色在传递到片元着色器时进行插值（平滑着色），也可用统一变量进行单一颜色着色（平面着色）。</li><li><strong>平面（恒定）着色（Flat (Constant) Shading）</strong>：若三个向量（光源方向、观察方向、法向量）恒定，每个多边形只需计算一次着色，多边形上所有点颜色相同，常用于建模光滑表面，但相邻多边形间小的着色差异可能导致效果不佳。</li><li><strong>平滑（插值）着色（Smooth (Interpolative) Shading）</strong>：光栅化器在多边形上插值顶点颜色，若光源远且无镜面反射或观察者远，多边形以恒定颜色着色。</li><li><strong>Gouraud着色</strong>：确定多边形顶点法向量（周围网格顶点法向量平均值），使用光照模型计算顶点像素强度，多边形边缘点强度通过顶点值加权平均计算，顶点强度在多边形表面线性插值。</li></ul></li></ol><h3 id="">材质属性</h3><ol start="1"><li>材质颜色取决于对入射红、绿、蓝光线的反射百分比，如红色球在不同颜色光下的反射情况。</li><li>材质有环境、漫反射和镜面反射属性，环境反射与入射光源环境光结合，漫反射与光源漫反射光结合，镜面反射同理。环境和漫反射属性定义材质颜色，通常相似，镜面反射通常为白色或灰色，使高光呈现光源镜面强度颜色。</li><li>漫反射在决定物体颜色感知中最重要，环境反射影响物体整体颜色，环境反射在无直接光照处最明显，漫反射和环境反射不受视点位置影响，镜面反射产生高光，高光强度取决于视点位置。</li></ol><h3 id="opengl">OpenGL中的实现</h3><ol start="1"><li><strong>光源与颜色</strong> 
<ul><li>使用<code>glLight*()</code>函数指定光源属性，包括颜色、位置和方向等，函数参数为光源标识、属性和属性值。</li><li>OpenGL中可设置多种光源属性，如环境光、漫反射光、镜面反射光强度、位置、聚光灯方向、指数、截止角、衰减因子等，默认值各有不同。</li></ul></li><li><strong>光源位置与方向</strong> 
<ul><li>光源可视为无限远（方向光）或靠近场景（点光源），通过<code>GL_POSITION</code>参数的<code>w</code>值判断，<code>w = 0</code>为方向光，<code>(x, y, z)</code>描述方向；<code>w ≠ 0</code>为点光源，<code>(x, y, z)</code>指定位置，位置和方向会被模型 - 视图矩阵变换。</li><li>通过改变光源位置设置点，可实现光源固定、绕静止物体移动或随视点移动三种效果。</li></ul></li><li><strong>聚光灯</strong> 
<ul><li>聚光灯默认关闭（<code>GL_SPOT_CUTOFF</code>为180.0，即向所有方向发光），可设置截止角（范围[0.0, 90.0]，特殊值180.0除外）、指数控制光的集中程度，光强度在锥中心最高，向边缘衰减，方向也会被模型</li><li>视图矩阵变换。</li></ul></li><li><strong>多光源</strong>
<ul><li>OpenGL场景中最多可有8个光源（取决于实现），增加光源数量会影响性能。</li><li>示例代码展示了如何定义点光源和聚光灯，包括设置各种属性并启用光源。</li></ul></li><li><strong>全局环境光</strong> 
<ul><li>使用<code>GL_LIGHT_MODEL_AMBIENT</code>参数指定全局环境光的RGBA强度，即使场景中无特定光源，也可使物体可见。</li></ul></li><li><strong>局部或无限视点</strong> 
<ul><li>视点位置影响镜面反射高光计算，局部视点更真实但性能降低，默认是无限视点，可通过<code>glLightModeli()</code>函数切换。</li></ul></li><li><strong>双面光照</strong> 
<ul><li>通常只设置前向多边形光照条件，后向多边形可能照明不正确，若物体内部表面可见，可通过<code>glMaterial*()</code>函数为前后面提供不同材质属性，该函数用于设置材质属性，参数包括面、属性和属性值，材质属性大多为RGBA颜色，不同面可设置不同属性，alpha值在任何顶点为漫反射材质的alpha值，在颜色索引模式下，RGBA材质属性不适用。</li></ul></li></ol><h3 id="">总结</h3><p>光照与材质对图形渲染有重要影响，需考虑多种因素，包括光源类型、光照效果、光照模型、着色方法等，结合全局环境光和多光源与材质属性，为减少计算量可应用一些近似方法。</p></div><p style="text-align:right"><a href="https://blog.chiran.top/posts/大学生涯/CPT205-%E5%85%89%E4%B8%8E%E6%9D%90%E8%B4%A8#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.chiran.top/posts/大学生涯/CPT205-%E5%85%89%E4%B8%8E%E6%9D%90%E8%B4%A8</link><guid isPermaLink="true">https://blog.chiran.top/posts/大学生涯/CPT205-%E5%85%89%E4%B8%8E%E6%9D%90%E8%B4%A8</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Sat, 04 Jan 2025 12:43:45 GMT</pubDate></item><item><title><![CDATA[EEE104-Lecture 3]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.chiran.top/posts/大学生涯/EEE104-Lecture%203">https://blog.chiran.top/posts/大学生涯/EEE104-Lecture%203</a></blockquote><div><h1 id="">比较器</h1><p>比较器是一种组合逻辑电路，用于比较两个二进制数的大小。比较器可以分为相等比较器和不等比较器。以下是比较器的工作原理：</p>
<h3 id="1-">1. 相等比较器</h3><p>相等比较器用于判断两个输入是否相等。一个简单的相等比较器可以使用异或（XOR）门来实现。</p><ul><li><p><strong>1位相等比较器</strong>：</p><ul><li>输入：A 和 B</li><li>输出：如果 A 和 B 相等，输出为1；否则为0。</li><li>实现方法：使用一个异或门。如果 A 和 B 相等，A XOR B 的输出为0。然后取反，可以得到相等比较器的输出。</li></ul></li><li><p><strong>多位相等比较器</strong>：</p><ul><li>输入：多个位的二进制数，例如 $A = A<em>3A</em>2A<em>1A</em>0$ 和 $B = B<em>3B</em>2B<em>1B</em>0$。</li><li>输出：如果所有对应位相等，输出为1；否则为0。</li><li>实现方法：将每个位对使用异或门比较，然后将所有异或门的输出输入到一个与（AND）门。如果所有位相等，异或门的输出都为0，与门的输出为1。</li></ul></li></ul><h3 id="2-">2. 不等比较器</h3><p>不等比较器用于判断两个输入数的大小关系（大于、小于或等于）。通常使用逐位比较的方法。</p><ul><li><strong>多位不等比较器</strong>：
<ul><li>输入：多个位的二进制数，例如 $A = A<em>3A</em>2A<em>1A</em>0$ 和 $B = B<em>3B</em>2B<em>1B</em>0$。</li><li>输出：A &gt; B、A &lt; B 或 A = B。</li><li>实现方法：从最高位开始比较。如果最高位的比较结果不相等，则高位决定大小关系；如果相等，则继续比较下一位，直到找到不同位或者比较完所有位。</li></ul></li></ul><p>例如，74HC85 是一个常用的 4位不等比较器，其工作方式如下：</p><ol start="1"><li><p><strong>比较最高位</strong>：</p><ul><li>检查 $A<em>3$ 和 $B</em>3$：
<ul><li>如果 $A<em>3 &gt; B</em>3$，则 A &gt; B。</li><li>如果 $A<em>3 &lt; B</em>3$，则 A &lt; B。</li><li>如果 $A<em>3 = B</em>3$，继续比较下一位。</li></ul></li></ul></li><li><p><strong>逐位比较</strong>：</p><ul><li>重复上述过程，依次比较 $A<em>2$ 和 $B</em>2$、$A<em>1$ 和 $B</em>1$、$A<em>0$ 和 $B</em>0$。</li><li>如果所有位都相等，则 A = B。</li></ul></li><li><p><strong>实现电路</strong>：</p><ul><li>使用级联的比较电路进行逐位比较。</li><li>74HC85 具有级联输入和输出，可以实现多位数的比较，通过连接多个74HC85实现更高位的比较。</li></ul></li></ol><h3 id="3-">3. 不等比较器的例子</h3><p>具体实现一个 4位不等比较器（如 74HC85）的电路逻辑：</p><ul><li>输入：A 和 B 的每个位 $A<em>3A</em>2A<em>1A</em>0$ 和 $B<em>3B</em>2B<em>1B</em>0$。</li><li>输出：A &gt; B、A &lt; B 和 A = B。</li><li>逻辑：
<ul><li>对于每一位，使用以下逻辑：
<ul><li>$A<em>i &gt; B</em>i$：输出1，表示 A &gt; B。</li><li>$A<em>i &lt; B</em>i$：输出1，表示 A &lt; B。</li><li>如果 $A<em>i = B</em>i$，则检查下一位。</li></ul></li></ul></li></ul><p>通过组合这些逻辑门，可以设计一个完整的比较器电路，用于判断两个二进制数的大小关系。这些比较器在数字电路中广泛用于排序、选择和控制逻辑中。</p>
<h1 id="">解码器</h1><p>解码器是一种组合逻辑电路，用于将编码的数据转换成指定的输出信号。它的工作原理是根据输入的二进制编码，激活唯一的输出线。以下是解码器的详细工作原理和示例：</p>
<h3 id="">解码器的基本概念</h3><p>解码器接受 n 位二进制输入，并生成 2^n 个输出。每一个输出对应于输入的一个唯一的二进制组合。</p>
<h3 id="">工作原理</h3><ol start="1"><li><p><strong>输入和输出关系</strong>：</p><ul><li>输入：n 位二进制数（例如 $A<em>{n-1}A</em>{n-2}...A_0$）。</li><li>输出：2^n 个输出线中的一个被激活（例如 $Y<em>0, Y</em>1, ..., Y_{2^n-1}$）。</li><li>当输入的二进制数对应某一个输出时，该输出线被激活（输出1），其他输出线保持不激活（输出0）。</li></ul></li><li><p><strong>逻辑实现</strong>：</p><ul><li>解码器使用逻辑与门（AND gates）和非门（NOT gates）实现输入到输出的映射。</li><li>每一个输出对应于一个唯一的输入组合。</li><li>非门用于将输入的某些位取反，以实现正确的逻辑组合。</li></ul></li></ol><h3 id="242-to-4-decoder">示例：2位输入的4线解码器（2-to-4 Decoder）</h3><p>假设有2位输入 $A<em>1$ 和 $A</em>0$，输出为 $Y<em>0, Y</em>1, Y<em>2, Y</em>3$。</p><table><thead><tr><th> $A_1$ </th><th> $A_0$ </th><th> $Y_0$ </th><th> $Y_1$ </th><th> $Y_2$ </th><th> $Y_3$ </th></tr></thead><tbody><tr><td> 0        </td><td> 0        </td><td> 1        </td><td> 0        </td><td> 0        </td><td> 0        </td></tr><tr><td> 0        </td><td> 1        </td><td> 0        </td><td> 1        </td><td> 0        </td><td> 0        </td></tr><tr><td> 1        </td><td> 0        </td><td> 0        </td><td> 0        </td><td> 1        </td><td> 0        </td></tr><tr><td> 1        </td><td> 1        </td><td> 0        </td><td> 0        </td><td> 0        </td><td> 1        </td></tr></tbody></table><ul><li><strong>逻辑表达式</strong>：
<ul><li>$Y<em>0 = \overline{A</em>1} \cdot \overline{A_0}$</li><li>$Y<em>1 = \overline{A</em>1} \cdot A_0$</li><li>$Y<em>2 = A</em>1 \cdot \overline{A_0}$</li><li>$Y<em>3 = A</em>1 \cdot A_0$</li></ul></li></ul><h3 id="4164-to-16-decoder">4位输入的16线解码器（4-to-16 Decoder）</h3><p>假设有4位输入 $A<em>3, A</em>2, A<em>1, A</em>0$，输出为 $Y<em>0, Y</em>1, ..., Y_{15}$。</p><ul><li><strong>逻辑表达式</strong>：
<ul><li>$Y<em>0 = \overline{A</em>3} \cdot \overline{A<em>2} \cdot \overline{A</em>1} \cdot \overline{A_0}$</li><li>$Y<em>1 = \overline{A</em>3} \cdot \overline{A<em>2} \cdot \overline{A</em>1} \cdot A_0$</li><li>$Y<em>2 = \overline{A</em>3} \cdot \overline{A<em>2} \cdot A</em>1 \cdot \overline{A_0}$</li><li>$...$</li><li>$Y<em>{15} = A</em>3 \cdot A<em>2 \cdot A</em>1 \cdot A_0$</li></ul></li></ul><h3 id="">特殊解码器</h3><ol start="1"><li><p><strong>BCD到7段显示解码器</strong>：</p><ul><li>将BCD码（Binary-Coded Decimal）转换为7段显示器的控制信号。</li><li>每个输出对应7段显示器的一个段（a-g）的点亮状态。</li></ul></li><li><p><strong>多级解码器</strong>：</p><ul><li>通过级联多个解码器实现更高位数的解码。例如，使用两个4-to-16解码器可以实现5位输入的解码。</li></ul></li></ol><h3 id="">实际应用</h3><p>解码器在数字电路中有广泛的应用，包括：</p><ul><li>地址解码：在存储器中选择特定的存储单元。</li><li>数据选择：在多路复用器中选择特定的数据路径。</li><li>显示驱动：控制7段显示器或其他显示设备。</li></ul><h3 id="74hc154-416">例子：74HC154 4线到16线解码器</h3><ul><li><strong>输入</strong>：4位输入 $A<em>3, A</em>2, A<em>1, A</em>0$。</li><li><strong>输出</strong>：16条输出线中的一条被激活。</li><li><strong>特点</strong>：具有使能输入（chip select），可以通过使能输入选择是否激活解码器。</li></ul><p>通过以上描述可以看出，解码器的核心工作原理是将输入的二进制编码转换成唯一的输出信号，这一过程主要依赖逻辑与门和非门的组合来实现。</p>
<h1 id="">编码器</h1><p>编码器是一种组合逻辑电路，用于将输入信号转换为二进制编码输出。与解码器的工作方式相反，编码器将多个输入信号中的一个高电平信号转换为相应的二进制编码。以下是编码器的工作原理及其应用。</p>
<h3 id="">编码器的基本概念</h3><p>编码器接受 2^n 个输入信号，并生成 n 位二进制输出。每一个输出编码代表一个唯一的输入信号。</p>
<h3 id="">工作原理</h3><ol start="1"><li><p><strong>输入和输出关系</strong>：</p><ul><li>输入：2^n 个信号线，其中只有一条信号线处于高电平（逻辑1）。</li><li>输出：n 位二进制数，对应于高电平输入信号的位置。</li></ul></li><li><p><strong>逻辑实现</strong>：</p><ul><li>编码器使用逻辑或门（OR gates）来实现输入到输出的映射。</li><li>每一个输出位由多个输入信号通过或门组合而成。</li></ul></li></ol><h3 id="4-to-2-4-to-2-priority-encoder">示例：4-to-2 优先编码器（4-to-2 Priority Encoder）</h3><p>假设有4个输入 $D<em>3, D</em>2, D<em>1, D</em>0$ 和2位输出 $A<em>1, A</em>0$。</p><table><thead><tr><th> 输入 $D_3$ </th><th> 输入 $D_2$ </th><th> 输入 $D_1$ </th><th> 输入 $D_0$ </th><th> 输出 $A_1$ </th><th> 输出 $A_0$ </th></tr></thead><tbody><tr><td> 0         </td><td> 0         </td><td> 0         </td><td> 1         </td><td> 0         </td><td> 0         </td></tr><tr><td> 0         </td><td> 0         </td><td> 1         </td><td> 0         </td><td> 0         </td><td> 1         </td></tr><tr><td> 0         </td><td> 1         </td><td> 0         </td><td> 0         </td><td> 1         </td><td> 0         </td></tr><tr><td> 1         </td><td> 0         </td><td> 0         </td><td> 0         </td><td> 1         </td><td> 1         </td></tr></tbody></table><ul><li><strong>逻辑表达式</strong>：
<ul><li>$A<em>1 = D</em>3$ 或 $D_2$</li><li>$A<em>0 = D</em>3$ 或 $D_1$</li></ul></li></ul><h3 id="">优先编码器</h3><p>优先编码器是一种特殊的编码器，它处理多个输入同时为高电平的情况，优先编码器选择具有最高优先级的输入进行编码。</p><p>例如，4-to-2 优先编码器的逻辑如下：</p><ul><li><strong>输入</strong>： $D<em>3, D</em>2, D<em>1, D</em>0$ （优先级从高到低）</li><li><strong>输出</strong>： $A<em>1, A</em>0$</li></ul><table><thead><tr><th> 输入 $D_3$ </th><th> 输入 $D_2$ </th><th> 输入 $D_1$ </th><th> 输入 $D_0$ </th><th> 输出 $A_1$ </th><th> 输出 $A_0$ </th></tr></thead><tbody><tr><td> 0         </td><td> 0         </td><td> 0         </td><td> 1         </td><td> 0         </td><td> 0         </td></tr><tr><td> 0         </td><td> 0         </td><td> 1         </td><td> 0         </td><td> 0         </td><td> 1         </td></tr><tr><td> 0         </td><td> 1         </td><td> 0         </td><td> 1         </td><td> 1         </td><td> 0         </td></tr><tr><td> 1         </td><td> 1         </td><td> 1         </td><td> 1         </td><td> 1         </td><td> 1         </td></tr></tbody></table><ul><li><strong>逻辑表达式</strong>：
<ul><li>$A<em>1 = D</em>3$ 或 $(D<em>2 和 \overline{D</em>3})$</li><li>$A<em>0 = D</em>3$ 或 $(D<em>1 和 \overline{D</em>3} 和 \overline{D_2})$</li></ul></li></ul><h3 id="bcddecimal-to-bcd-encoder">示例：十进制到BCD编码器（Decimal-to-BCD Encoder）</h3><p>假设有10个输入 $D<em>9, D</em>8, D<em>7, ..., D</em>0$ 和4位BCD输出 $B<em>3, B</em>2, B<em>1, B</em>0$。</p><ul><li><strong>逻辑表达式</strong>：
<ul><li>$B<em>3 = D</em>8$ 或 $D_9$</li><li>$B<em>2 = D</em>4$ 或 $D<em>5$ 或 $D</em>6$ 或 $D_7$</li><li>$B<em>1 = D</em>2$ 或 $D<em>3$ 或 $D</em>6$ 或 $D_7$</li><li>$B<em>0 = D</em>1$ 或 $D<em>3$ 或 $D</em>5$ 或 $D<em>7$ 或 $D</em>9$</li></ul></li></ul><h3 id="">实际应用</h3><p>编码器在数字系统中有许多实际应用，包括：</p><ul><li><strong>键盘编码器</strong>：将按键输入转换为相应的二进制编码。</li><li><strong>地址编码器</strong>：将存储器地址转换为编码信号。</li><li><strong>传感器数据编码</strong>：将来自多个传感器的信号转换为二进制编码，用于进一步处理。</li></ul><h3 id="74hc147-bcd">例子：74HC147 十进制到BCD优先编码器</h3><ul><li><strong>输入</strong>：10个输入 $D<em>9, D</em>8, ..., D_0$，仅一个为高电平。</li><li><strong>输出</strong>：4位BCD码 $B<em>3, B</em>2, B<em>1, B</em>0$，表示高电平输入的位置。</li><li><strong>特点</strong>：优先编码功能，多个输入为高电平时，优先输出最高位的编码。</li></ul><p>通过以上描述可以看出，编码器的核心工作原理是将多个输入信号中的一个高电平信号转换为对应的二进制编码输出，这一过程主要依赖逻辑或门的组合来实现。</p>
<h1 id="">多路复用器</h1><p>多路复用器（Multiplexer，简称MUX）是一种组合逻辑电路，用于选择多个输入信号中的一个并将其传输到输出端。它通常被称为数据选择器，因为它根据控制信号（选择信号）来选择哪个输入信号通过到输出端。</p>
<h3 id="">多路复用器的基本概念</h3><p>多路复用器具有多个数据输入端、若干个选择输入端和一个输出端。选择输入端决定了哪个数据输入端的信号被传输到输出端。</p>
<h3 id="">工作原理</h3><ol start="1"><li><p><strong>输入和输出关系</strong>：</p><ul><li>输入：多个数据输入（例如 $D<em>0, D</em>1, D<em>2, ..., D</em>{n-1}$）。</li><li>选择输入：决定选择哪个数据输入（例如 $S<em>0, S</em>1, ..., S<em>m$，其中 $m = \log</em>2(n)$）。</li><li>输出：单个输出（Y）。</li></ul></li><li><p><strong>选择逻辑</strong>：</p><ul><li>多路复用器使用选择输入信号来选择其中一个数据输入并将其连接到输出。</li><li>根据选择输入的二进制值，MUX的输出端连接到对应的输入端。</li></ul></li></ol><h3 id="2-to-1-">示例：2-to-1 多路复用器</h3><p>假设有2个数据输入 $D<em>0$ 和 $D</em>1$，1个选择输入 $S$，和1个输出 $Y$。</p><ul><li><strong>逻辑表达式</strong>：
<ul><li>$Y = \overline{S} \cdot D<em>0 + S \cdot D</em>1$</li><li>当 $S = 0$ 时，$Y = D_0$。</li><li>当 $S = 1$ 时，$Y = D_1$。</li></ul></li></ul><h3 id="4-to-1-">示例：4-to-1 多路复用器</h3><p>假设有4个数据输入 $D<em>0, D</em>1, D<em>2, D</em>3$，2个选择输入 $S<em>1$ 和 $S</em>0$，1个输出 $Y$。</p><table><thead><tr><th> 选择输入 $S_1$ </th><th> 选择输入 $S_0$ </th><th> 输出 $Y$ </th></tr></thead><tbody><tr><td> 0                 </td><td> 0                 </td><td> $D_0$   </td></tr><tr><td> 0                 </td><td> 1                 </td><td> $D_1$   </td></tr><tr><td> 1                 </td><td> 0                 </td><td> $D_2$   </td></tr><tr><td> 1                 </td><td> 1                 </td><td> $D_3$   </td></tr></tbody></table><ul><li><strong>逻辑表达式</strong>：
<ul><li>$Y = \overline{S<em>1} \cdot \overline{S</em>0} \cdot D<em>0 + \overline{S</em>1} \cdot S<em>0 \cdot D</em>1 + S<em>1 \cdot \overline{S</em>0} \cdot D<em>2 + S</em>1 \cdot S<em>0 \cdot D</em>3$</li></ul></li></ul><h3 id="">多路复用器的实际应用</h3><ol start="1"><li><p><strong>数据选择</strong>：</p><ul><li>多路复用器可以选择不同的数据源传输到处理器或其他电路中。</li></ul></li><li><p><strong>信号路由</strong>：</p><ul><li>在通信系统中，多路复用器可以用于信号的路由选择，将多个信号通过一个通道传输。</li></ul></li><li><p><strong>逻辑函数实现</strong>：</p><ul><li>多路复用器可以用于实现任意逻辑函数。例如，通过设置不同的选择输入，可以实现特定的逻辑组合。</li></ul></li></ol><h3 id="74hc151-8-to-1-">例子：74HC151 8-to-1 多路复用器</h3><ul><li><strong>输入</strong>：8个数据输入 $D<em>0, D</em>1, ..., D<em>7$，3个选择输入 $S</em>2, S<em>1, S</em>0$。</li><li><strong>输出</strong>：单个输出 $Y$。</li><li><strong>特点</strong>：74HC151 可以根据选择输入将一个数据输入传输到输出端，具有使能输入来控制多路复用器是否工作。</li></ul><h3 id="">多路复用器的电路实现</h3><p>多路复用器通常使用与门（AND gates）、或门（OR gates）和非门（NOT gates）来实现。以下是4-to-1多路复用器的电路实现示例：</p><ul><li><strong>选择输入</strong>： $S<em>1$ 和 $S</em>0$</li><li><strong>数据输入</strong>： $D<em>0, D</em>1, D<em>2, D</em>3$</li><li><strong>输出</strong>： $Y$</li></ul><ol start="1"><li>使用两个选择输入 $S<em>1$ 和 $S</em>0$ 的组合生成选择控制信号。</li><li>将选择控制信号与数据输入通过与门相连接，生成部分选择信号。</li><li>将所有部分选择信号通过或门组合，生成最终输出信号。</li></ol><p>通过这种方式，多路复用器可以实现对多个数据输入的选择和输出控制。多路复用器在数字电路设计中有广泛的应用，尤其在需要选择和路由多个信号的场合。</p>
<h1 id="">解多路复用器</h1><p>解多路复用器（Demultiplexer，简称Demux）是一种组合逻辑电路，用于将单一的输入信号分配到多个输出线中的一个。与多路复用器（Mux）相反，解多路复用器根据选择输入信号将数据输入传输到指定的输出端。</p>
<h3 id="">解多路复用器的基本概念</h3><p>解多路复用器接受一个数据输入、若干个选择输入，并生成多个输出。选择输入决定了哪个输出线被激活，并传输数据输入信号。</p>
<h3 id="">工作原理</h3><ol start="1"><li><p><strong>输入和输出关系</strong>：</p><ul><li><strong>数据输入</strong>：单个输入信号（例如 $D$）。</li><li><strong>选择输入</strong>：决定数据输入信号被传输到哪个输出（例如 $S<em>0, S</em>1, ..., S<em>m$，其中 $m = \log</em>2(n)$）。</li><li><strong>输出</strong>：多个输出信号（例如 $Y<em>0, Y</em>1, ..., Y_{n-1}$）。</li></ul></li><li><p><strong>选择逻辑</strong>：</p><ul><li>解多路复用器使用选择输入信号来选择其中一个输出并将数据输入信号连接到该输出。</li><li>根据选择输入的二进制值，Demux的输入端连接到对应的输出端。</li></ul></li></ol><h3 id="1-to-4-">示例：1-to-4 解多路复用器</h3><p>假设有1个数据输入 $D$，2个选择输入 $S<em>1, S</em>0$，和4个输出 $Y<em>0, Y</em>1, Y<em>2, Y</em>3$。</p><table><thead><tr><th> 选择输入 $S_1$ </th><th> 选择输入 $S_0$ </th><th> 激活的输出 </th><th> 输出逻辑 </th></tr></thead><tbody><tr><td> 0                 </td><td> 0                 </td><td> $Y_0$ </td><td> $Y_0 = D$ </td></tr><tr><td> 0                 </td><td> 1                 </td><td> $Y_1$ </td><td> $Y_1 = D$ </td></tr><tr><td> 1                 </td><td> 0                 </td><td> $Y_2$ </td><td> $Y_2 = D$ </td></tr><tr><td> 1                 </td><td> 1                 </td><td> $Y_3$ </td><td> $Y_3 = D$ </td></tr></tbody></table><ul><li><strong>逻辑表达式</strong>：
<ul><li>$Y<em>0 = \overline{S</em>1} \cdot \overline{S_0} \cdot D$</li><li>$Y<em>1 = \overline{S</em>1} \cdot S_0 \cdot D$</li><li>$Y<em>2 = S</em>1 \cdot \overline{S_0} \cdot D$</li><li>$Y<em>3 = S</em>1 \cdot S_0 \cdot D$</li></ul></li></ul><h3 id="">解多路复用器的实际应用</h3><ol start="1"><li><p><strong>数据分配</strong>：</p><ul><li>解多路复用器用于将单一的数据输入信号分配到多个输出设备或线路中。</li></ul></li><li><p><strong>信号路由</strong>：</p><ul><li>在通信系统中，解多路复用器用于信号的分配选择，将输入信号路由到特定的输出通道。</li></ul></li><li><p><strong>存储器地址解码</strong>：</p><ul><li>在存储器系统中，解多路复用器用于地址解码，将地址信号分配到相应的存储单元。</li></ul></li></ol><h3 id="74hc139-1-to-4-">例子：74HC139 双1-to-4 解多路复用器</h3><ul><li><strong>输入</strong>：单个数据输入 $D$，两个选择输入 $S<em>1, S</em>0$。</li><li><strong>输出</strong>：四个输出 $Y<em>0, Y</em>1, Y<em>2, Y</em>3$。</li><li><strong>特点</strong>：74HC139 具有两个独立的1-to-4解多路复用器，可以分别控制两个数据输入的分配。</li></ul><h3 id="">解多路复用器的电路实现</h3><p>解多路复用器通常使用与门（AND gates）和非门（NOT gates）来实现。以下是1-to-4解多路复用器的电路实现示例：</p><ul><li><strong>选择输入</strong>： $S<em>1$ 和 $S</em>0$</li><li><strong>数据输入</strong>： $D$</li><li><strong>输出</strong>： $Y<em>0, Y</em>1, Y<em>2, Y</em>3$</li></ul><ol start="1"><li>使用两个选择输入 $S<em>1$ 和 $S</em>0$ 的组合生成选择控制信号。</li><li>将数据输入与选择控制信号通过与门相连接，生成部分选择信号。</li><li>将各个与门的输出连接到相应的输出线，实现数据输入的分配。</li></ol><p>通过这种方式，解多路复用器可以实现将单一输入信号分配到多个输出的功能，广泛应用于数字电路设计中。</p></div><p style="text-align:right"><a href="https://blog.chiran.top/posts/大学生涯/EEE104-Lecture%203#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.chiran.top/posts/大学生涯/EEE104-Lecture%203</link><guid isPermaLink="true">https://blog.chiran.top/posts/大学生涯/EEE104-Lecture%203</guid><dc:creator><![CDATA[苏辰]]></dc:creator><pubDate>Sun, 02 Jun 2024 02:49:45 GMT</pubDate></item></channel></rss>