尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
CTFHub SSRF POST请求:gopher协议构造POST报文
CTFHub 技能树里的Web-SSRF POST请求这道题我带过好几个刚入门的朋友做几乎每个人第一次都会卡在同一个地方前面几道 SSRF 关卡用?urlhttp://127.0.0.1/xxx打得顺手一到这题把 URL 换成同样的形式回显不是空白就是一堆看不懂的报错payload 改了七八遍还是拿不到东西。问题不在你的思路而在于POST 请求这个形态本身就没办法塞进一个普通的 URL 字符串里。这道题真正考的是你能不能把一段完整的 HTTP 报文搬到 URL 参数里去。它适合已经理解 SSRF 基本原理、学过内网探测和伪协议读取正准备突破只能发 GET这道瓶颈的人。下面我把自己从踩坑到跑通的全过程拆开讲包括协议选型的理由、报文里每个字节为什么这么写、编码要套几层以及几个网上教程基本不会提的排查细节。1. 拆解关卡POST请求这道题到底在考什么1.1 从题面提示反推内网结构先别急着上手打把题目给的信息读透这题的线索其实藏得不算深。典型的题面会告诉你两件事第一内网里跑着一个服务这个服务只认 POST 请求你用 GET 去访问它它要么直接拒绝要么返回一个提示页面告诉你方法不对第二你需要向它提交一个特定的表单字段值对了才会把 flag 吐出来。把这两条信息翻译成技术语言就是下面这个模型目标机器上的 PHP 代码大概长这样——?php if ($_SERVER[REQUEST_METHOD] POST) { if ($_POST[key] 某段题目要求的值) { echo $flag; } else { echo wrong param; } } else { echo please use POST method; }而题目提供的那个可注入点是一个会用curl去访问你传入 URL 的接口。也就是说你的请求会被服务端代发到内网。这个代发环节就是 SSRF 的本质服务器替你发起一次它自己有权限、而你没有权限的网络请求。这里有个思维转换很关键。前一道题你可能已经用过?urlhttp://127.0.0.1:80/去看内网服务长什么样那是 GET 语义——curl 打开一个 URL就是发一条 GET 报文。现在你要控制的不只是发给谁还有发什么内容、用什么方法、带什么头。GET 的能力边界在这里就到头了。提示内网服务的监听地址、端口、路径、参数名都要从题目提示或源码注释里确认别照抄任何一篇 writeup 里的固定值环境不同这些都可能变。1.2 为什么 GET 型 Payload 在这里必然失灵很多人卡住的第一个心理障碍是舍不得放弃已经熟练的http://写法。我理解这种惯性但从协议层面看这条路在这题上就是死的原因有三层。第一层URL 本身不携带请求体。一个 URL 的结构是scheme://host:port/path?query#fragment它能描述去哪里和带什么查询串但没有一个字段是留给请求体的。而 POST 报文的核心特征恰好就是参数放在 body 里不在 URL 上。第二层curl处理http://开头的 URL 时默认行为就是发 GET。你没法通过 URL 字符串去告诉 curl 这次改成 POST因为CURLOPT_POST、CURLOPT_POSTFIELDS都是代码里写死的选项跟 URL 内容无关。注入点只控制 URL控制不了这些选项。第三层有些人会想那我加个?keyxxx让参数走查询串行不行不行。目标脚本用的是$_POST[key]PHP 里$_POST只从请求体里取值查询串里的内容落在$_GET里。你 URL 上拼再多参数到了对方那儿$_POST[key]还是空的。三层叠加的结果就是你必须想办法让服务端的 curl 发出一段由你完全定制的、方法为 POST 的原始 HTTP 报文。而能让 curl 干这件事的协议主角只有一个——gopher。2. gopher协议打通POST型SSRF的主力武器2.1 gopher的本质把字节流原样塞进TCP连接要理解 gopher 为什么能打 POST得先绕开协议这个词带来的心理负担。gopher 出现在 HTTP 之前它的设计极简客户端连上服务器的指定端口然后把一串字节原样写进去再读回服务器返回的字节。没有头部没有握手没有字段解析。这恰好就是 SSRF 里最好用的一种能力。因为 HTTP 本身也是跑在 TCP 上的纯文本协议——你只要能把一段符合 HTTP 格式的文本通过任意一个 TCP 连接送到目标服务的 80 端口目标服务就会把它当成一个正常的 HTTP 请求来处理返回一个正常的 HTTP 响应。curl 在处理gopher://时的动作可以概括为解析出 host 和 port建立 TCP 连接把 URL 里数据段的内容解码后写进去然后把响应读回来。整个过程它不关心你写的是什么你写 HTTP 报文它就是 HTTP你写 Redis 命令它就是 Redis 命令。这就是 gopher 在 SSRF 里被称作万能协议的原因。它不局限于打某个特定服务理论上任何基于 TCP 的明文协议都能用它的方式去交互HTTP 只是其中最常用的一种。如果说http://是让服务器替你点一下链接那gopher://就是让服务器替你敲一遍键盘。注意curl 从 7.66.0 版本开始默认编译时不再包含 gopher 支持。CTF 赛题环境通常用的还是老版本所以能打通但如果你在自己机器上本地复现先用curl -V看一眼 Protocols 那一行里有没有 gopher没有的话本地怎么试都是不通的别在这上面耗时间。2.2 gopher、http、dict三种协议的能力边界选协议这件事我习惯用一个表格把它一次说清楚省得来回试协议能否指定方法能否带请求体能否自定义请求头适用场景http://否默认 GET否否简单探测、读取 GET 型接口dict://部分可控否否探测端口存活、拿服务 bannergopher://完全可控完全可控完全可控打 POST 接口、打 Redis/FastCGI 等dict://值得单独提一句它在做端口探测时非常好用格式是dict://127.0.0.1:6379/info会连上目标端口并把后面的内容发过去。但它的表达能力也就到发一行简单命令为止构造不了带 CRLF 换行和多个头的完整报文。所以在这道题里dict 只能帮你确认内网端口是否开放真正下 payload 还得靠 gopher。这里也顺便解释一个初学者常见的疑问为什么不能用file://读一下目标脚本源码可以但那属于另一道伪协议读取关卡的考点。这道题的目标是让内网服务执行一次 POST 逻辑读源码解决不了方法不对的问题思路别跑偏。2.3 gopher URL 里那个下划线是干什么的第一次看到gopher://127.0.0.1:80/_POST /flag.php ...这种写法几乎所有人都会盯着端口后面那个/_发呆。这个下划线不是装饰也不是分隔符的艺术它是在吃掉一个位置。gopher 协议在设计 URL 表示法时端口后面紧跟在斜杠后的第一个字符被定义为 gopher 的type 字符用来区分菜单、文本、二进制等资源类型。如果我们直接在斜杠后面写POST那么P就会被当成 type 字符吞掉真正发出去的报文会从OST开始报文第一行就废了。用_占住这个位置表示 type 为空_之后的全部内容才作为数据写入 TCP 连接。这是一个纯粹的实现细节不需要你理解 gopher 的完整标准记住端口后面加/_数据从下划线之后算起就够了。但少了这个下划线你的报文头会被切掉一个字符这是最隐蔽也最容易忽略的错误之一。3. 逐字节构造 gopher 报文3.1 报文骨架一个合法的 POST 请求长什么样在把报文塞进 URL 之前先在纸上或者记事本里把它写正确。一个最小的、能被 PHP 正确解析的 POST 请求是这样的POST /flag.php HTTP/1.1 Host: 127.0.0.1 Content-Type: application/x-www-form-urlencoded Content-Length: 10 keyctfhub逐行说明每一行都不是可选的请求行POST /flag.php HTTP/1.1方法必须是大写 POST路径必须是目标脚本的真实路径协议版本用 1.1。Host 头HTTP/1.1 强制要求缺了它很多服务端会直接返回 400。值填目标的host:port如果是 80 端口可以不写端口号。Content-Type 头这行决定服务端用什么方式解析请求体。POST 表单必须用application/x-www-form-urlencoded否则 PHP 的$_POST数组是空的你会收到参数错误而不是方法错误。Content-Length 头告诉服务端请求体有多少字节少算或多算都会导致解析异常。空行请求头和请求体之间必须有一个空行这是 HTTP 协议的硬性规定也是新手最常漏掉的一行。请求体keyctfhub用连接多个参数键值对之间用。提示Content-Type这一行是这题最容易翻车的地方。我见过不少人拿着从 Redis 反弹 shell 的 payload 直接改忘了改 Content-Type结果服务端收到了体但$_POST是空的然后百思不得其解为什么参数明明是传了的。3.2 Content-Length 到底怎么算这一行的值必须是请求体的字节数不是字符数——这两者在纯 ASCII 场景下相等但一旦参数值里出现中文或特殊符号就会分道扬镳。计算方式很直白把请求体当成一个字符串数它的长度。以keyctfhub为例逐字符数k(1)e(2)y(3)(4)c(5)t(6)f(7)h(8)u(9)b(10)一共 10 个字节所以写Content-Length: 10。如果你用的是真实的题目要求值比如某个更长的字符串别手数交给工具算。命令行里echo -n key你的值 | wc -c直接出结果Python 里len(key你的值.encode())也一样。手算错一位服务端要么等更多数据超时要么把下一段数据当成新请求解析返回的错误都跟你真正的问题无关排查起来极其痛苦。3.3 换行必须是 CRLF不是 LFHTTP 协议规定的行分隔符是CRLF也就是\r\n十六进制0d0a不是 Unix 里常见的单个\n。有些宽松的服务端能容忍纯\n但 PHP 内置服务器、Nginx 的反代、以及各种中间件在严格模式下都可能因为行尾不对而拒绝解析。在 URL 编码的语境下\r\n要写成%0d%0a。整个报文里每一个换行——包括请求行末尾、每个头末尾、以及请求头和请求体之间的那个空行——都要替换成%0d%0a。空行则对应连续的两个%0d%0a看起来就是...%0d%0a%0d%0aPOST请求头结束或者...Content-Length:%2010%0d%0a%0d%0akeyctfhub。顺带说下空格。URL 里的空格必须编码成%20直接写空格在很多解析器里会被截断成 URL 的结束你的报文从第一个空格处就断了。所以POST /flag.php HTTP/1.1要写成POST%20/flag.php%20HTTP/1.1。3.4 编码要套几层一个必须想清楚的问题这是这道题最有迷惑性的地方。网上很多 writeup 写的是需要二次 URL 编码但很少有人解释为什么结果换个环境照做就废了。本质原因是从你手里到最终 TCP 连接之间%会被解码多次你的 payload 得为每次解码都预留一层。把链路拆开看一共三层第一层投递工具。你用浏览器地址栏、Python 的 requests、还是 curl 命令行对%的处理行为不同。浏览器地址栏里输入%0d通常会原样发出去而 requests 在处理 URL 时可能会对已经含有百分号的字符串做一次规范化。第二层目标脚本的参数接收。$_GET[url]或$_REQUEST[url]拿到的是 PHP 自动 URL 解码后的结果。你传过去的一个%25%的编码到这里会变成%。第三层curl 解析 gopher URL。curl 在处理 gopher 的数据段时会对百分号编码做一次解码把%0d%0a还原成真正的 CRLF 字节写进 TCP 流。所以结论是你在 URL 参数里最终要传递给curl 解析这一层的内容必须是%0d%0a这种单层编码形式而你实际提交给服务器的字符串得再往上套一层把%变成%25比如%250d%250a。用最稳的方式理解先把原始报文写成单层编码版本这是 curl 想看到的输入然后对这整个字符串再做一次全量的%→%25替换这是你提交时要用的版本。用 Python 做这个转换只有一行import urllib.parse raw POST /flag.php HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 10\r\n\r\nkeyctfhub # 第一层编码成 curl 需要的 gopher 数据段形式 once urllib.parse.quote(raw, safe) # 第二层把百分号再编码一次供参数投递 twice once.replace(%, %25) payload fgopher://127.0.0.1:80/_{once} # 本地/直接投递用 print(payload)注意urllib.parse.quote的safe参数它表示连斜杠都不放行全部编码。这是必须的因为 gopher 数据段里的/如果没编码会被 URL 解析器当成路径分隔符切开POST /flag.php里的斜杠正是受害者。4. 三种投递方式实测对比4.1 浏览器地址栏直投最省事也最容易翻车拿到 payload 之后最直接的方式就是拼到 URL 参数里然后贴到地址栏。这一步的坑在于浏览器对%的处理会给你添麻烦。我实测下来Chrome 和 Firefox 在地址栏里输入含有%25的 URL 时大部分情况下会原样发送但偶尔会在某些位置做自动编码尤其是你从别处复制粘贴时。稳妥的判断方法是看返回内容的形态如果服务器返回 400、或者回显里能看到你的报文碎片比如只认出了POST三个字母那说明报文在传输链路上被破坏了一层把编码层数再加一层试试。用浏览器投递还有个隐性成本——很难看清单次请求的原始响应头。而这题判断成功与否往往需要看响应的状态码和 body 内容用浏览器开发者工具的 Network 面板总归比命令行麻烦。提示如果地址栏直投连续失败三次以上别继续在浏览器里试了直接切到 Python 脚本能精确控制每一层编码省下的是时间。4.2 Python requests可控性最好用 requests 投递的关键是别让 requests 帮你编码。直接拼 URL 字符串然后用get发出去import requests import urllib.parse raw (POST /flag.php HTTP/1.1\r\n Host: 127.0.0.1\r\n Content-Type: application/x-www-form-urlencoded\r\n Content-Length: 10\r\n \r\n keyctfhub) inner urllib.parse.quote(raw, safe) gopher fgopher://127.0.0.1:80/_{inner} # 注意别再让 requests 转义一次直接拼成完整 URL url fhttp://题目地址/?url{gopher} resp requests.get(url, timeout10) print(resp.status_code) print(resp.text[:1500])这里有个细节值得说requests.get(url)里如果按上面这样直接拼字符串requests 默认不会去动 URL 里的%所以上面这段代码用的是单层编码 直接投递的组合等价于手工投递时把编码层数减一层。如果你不确定自己的投递链路会被解码几次最可靠的办法是拿三份 payload一层、两层、三层编码各打一次看哪份回显正常。这种笨办法在赛场上比纠结原理更快拿到 flag 之后再去复盘原理也不迟。4.3 Burp Repeater排查问题的首选当 payload 死活打不通、又看不出哪里错的时候我会切到 Burp 的 Repeater。理由是它能让你逐字符看到你实际发出去的 URL并且能很方便地改一个字符重放。操作上没什么玄机抓一个?url...的包扔进 Repeater把 url 参数值换成你的 gopher payload。重点是关掉 Burp 自动对 URL 编码的干扰——在 Proxy 的设置里把 Automatically URL-encode these characters 这套规则之外的自动修改关掉或者在 Repeater 里手动检查尖括号、冒号、百分号有没有被工具偷偷改过。另一个实用技巧如果目标服务端有curl_setopt($ch, CURLOPT_HEADER, 1)或者返回了原始响应头你可以在 Repeater 里立刻看到 HTTP/1.1 的状态行比如HTTP/1.1 200 OK说明报文格式完全对了只是参数值不对如果看到HTTP/1.1 400 Bad Request那就是报文本身有结构错误长度或者 CRLF 的问题。这两种错误的区分能帮你把排查范围立刻缩小一半。4.4 不依赖gopher的备选思路严格说这道题的标准解就是 gopher但我还是把几个备选方向列一下万一环境里 gopher 不可用比如题目用的是新版本 curl 编译不至于卡死。思路一找有没有别的注入点。有些环境里同一个服务另有?url之外的功能比如上传、重定向跳转利用302配合Content-Type的变换有时能间接构造 POST但可控性极差成功率低。思路二看目标脚本是否只校验REQUEST_METHOD。如果它只判断了是不是 POST而没有严格解析 body那么用dict://发一行POST /flag.php HTTP/1.1也许能骗过方法判断。这只在极少数宽松环境里有效别抱太大期望。思路三改用其他支持自定义报文的协议。老一些的 curl 还支持tftp、ftp等协议但它们的报文格式固定表达不了 HTTP。这条基本走不通列出来是为了让你别去浪费时间。总结一句这道题老老实实用 gopher是最短路径不要绕。5. 踩坑实录与排查速查表5.1 现象与原因的对照我把带人过程中反复出现的错误整理成了一张表出问题时按现象倒查比漫无目的地改 payload 高效得多回显现象最可能的原因处理方式返回空内容gopher 未编译进 curl / 端口不通curl -V确认先探测端口提示方法不对GET报文第一行被_或编码破坏退化成了 GET检查/_是否存在、%20是否漏编码提示参数错误Content-Type 缺失或写错补上application/x-www-form-urlencoded400 Bad RequestCRLF 用了\n、Content-Length 不对全部换行改%0d%0a重新算长度连接超时目标端口/地址写错用dict://探测端口存活回显成了半截报文编码层数不对解码过度或不足换一层编码重试502/504内网服务没起或路径不对换路径试试根目录5.2 内网端口探测的正确姿势在正式下 payload 之前花一分钟做一个端口探测能帮你排除掉地址写错这一类最冤的错误。方法很简单用 gopher 或者 dict 探一下端口?urldict://127.0.0.1:80/如果这个请求返回了任何内容哪怕是一段 HTML说明 80 端口是通的如果返回连接被拒绝或者超时那你后面所有 payload 都白搭先解决连通性。更细一点你可以试探性地发一个不完整的请求看看服务端报什么错。能收到 400就说明 TCP 层通了、HTTP 层解析了、只是报文内容不对这一步的确认价值极高它把你后面的调试空间从整条链路缩小到报文内容这一小块。5.3 几个我踩过、教程里很少提的坑坑一参数值里带特殊字符忘了编码。如果题目要求的那个 key 值里有、、这类字符它们会破坏请求体里键值对的解析。记得单独对参数值做一次 URL 编码。尤其阴险表单解析时会被还原成空格。坑二Content-Length 和实际字符不是一一对应。举例说key%E4%B8%AD这种字符有 12 个但实际字节数要对解码后的值算。永远用字节数别用你看到的百分号数量。坑三误以为二次编码是绝对的。我在不同环境里遇到过需要一层、两层、三层编码的三种情况共同点是层数取决于投递链路上有几个解码器不是越多越安全。编码层数加多了反而会让 curl 收到字面量%0d%0a而不是换行字节报文直接塌掉。所以层数要判断不能盲加。坑四Host 头写成了带端口但端口不匹配。如果目标监听在 80Host 写127.0.0.1:80和写127.0.0.1一般都可以但如果目标在非标准端口比如 8080Host 头里必须带上端口否则部分服务端会把请求路由到错误的虚拟主机上。坑五只顾着改 payload忘了url参数名。有些环境参数名不是url而是u、target、proxy之类先看题目给的源码或者前端表单确认清楚再拼。这个错误低级的程度和它的常见程度成正比。6. 换个视角这类SSRF在真实系统里怎么防6.1 服务端加固的核心思路做 CTF 是为了学防御所以最后必须把视角翻回来。POST 型 SSRF 之所以能成立根因是服务端把用户可控的字符串直接交给了 curl 之类的网络客户端。防御的第一原则就是切断这个直接通道。具体落地时有几条我比较推荐的策略。一是协议白名单只允许http和https把gopher、dict、file、ftp全部禁用curl 里对应CURLOPT_PROTOCOLS选项一行配置就能堵掉一大类攻击。二是地址白名单或黑名单白名单更好如果业务确实需要访问任意地址那至少要把127.0.0.1、0.0.0.0、内网网段比如10.0.0.0/8、172.16.0.0/12、192.168.0.0/16拦掉同时解析 DNS 后拿到真实 IP 再校验一次防止用域名绕过。三是禁止跟随重定向CURLOPT_FOLLOWLOCATION设为 false堵住先跳到一个外部 302、再跳回内网的绕过手法。四是统一错误信息别把 curl 的原始错误直接回显给用户那个错误信息本身就泄露了内网连通性和服务状态。6.2 容易被忽略的一层DNS 重绑定与解析后校验单独把这点拎出来说是因为它在 CTF 里不常考但真实场景里是绕过 IP 黑名单的经典手法。攻击者注册一个域名第一次解析返回外网 IP 通过校验客户端真正连接时第二次解析返回127.0.0.1于是黑名单形同虚设。对抗方式是不做解析前校验而是先解析成 IP、校验这个 IP、然后强制用这个 IP 去连接保证校验和连接用的是同一个地址。这个细节很多人写防护代码时会漏值得留意。6.3 云环境下的额外注意点如果服务部署在云主机上还要额外防一个特殊地址——云厂商的元数据服务地址。一旦 SSRF 能打到它攻击者可能拿到实例的临时凭证危害等级直接拉到最高。做法和上面一样把对应网段的地址加进黑名单或者在网络层面安全组禁止实例主动访问元数据地址。回到这道题本身它的价值不在于拿到一个 flag而在于让你亲手体会一遍用户输入如何一步步变成一次内网请求。你走通了 gopher 构造报文的完整链路之后再去看真实系统里那些只允许访问指定域名的校验代码判断它们有没有绕过口子会变得非常直观。我个人在带人的时候宁可让人在这道题上多花两个小时把 CRLF 和 coding 层数彻底搞明白也不愿意他记一个 payload 就走——因为前者是能力后者是运气换个环境就失效了。
RELATED

相关推荐

昇腾950PR上Qwen3-27B解码性能优化实战:从带宽瓶颈到连续批处理

昇腾950PR上Qwen3-27B解码性能优化实战:从带宽瓶颈到连续批处理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/16 23:30:21
HTTP Host头攻击漏洞:从扫描器告警到修复复测全流程

HTTP Host头攻击漏洞:从扫描器告警到修复复测全流程

1. 扫描器甩来一顶帽子:先把HTTP Host头攻击漏洞说清楚早上打开漏洞管理平台,看到一条高危标注:检测到目标URL存在HTTP Host头攻击漏洞。点进去看详情,请求包里就是改了一个 Host 头,响应里居然把那个伪造的域名原样吐…

📅 2026/9/16 23:30:21
睿尔曼超轻量仿人机械臂接口全解析:从物理层到ROS封装

睿尔曼超轻量仿人机械臂接口全解析:从物理层到ROS封装

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/16 23:30:21
MORE NEWS

更多资讯

📰

Linux信号机制:原理、实战与性能优化

1. Linux信号机制深度解析:从原理到实战信号(Signal)作为Linux系统中进程间通信的重要机制,已经伴随Unix/Linux系统走过了半个世纪。这种软件层次的中断模拟机制,在系统编程中扮演着关键角色——当我在处理一个耗时计算…

📰

牛顿-拉夫逊法三相潮流计算程序开发与实践

1. 三相潮流计算程序概述在电力系统分析领域,三相潮流计算是最基础也是最重要的计算工具之一。我开发的这个基于牛顿-拉夫逊法的三相潮流计算程序,主要用于解决电力系统稳态运行分析中的各类计算问题。不同于教科书上的简化示例,这个程序针对…

📰

光模块TEC温控设计:从选型到验证的工程实践指南

1. 为什么光模块必须配TEC——不是“要不要”,而是“怎么配才不翻车”光模块里的TEC(Thermoelectric Cooler,热电制冷器),常被当成一个可有可无的“高级配件”:有些厂商宣传“全温范围工作”,就…

📰

Invalid default_text_model ‘auto‘ 报错?TaoToken 下这样指定 provider

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Mac终端Git账号密码设置与清除完全指南

Mac 终端里 Git 突然要账号密码,多半不是什么好兆头:可能是你换了电脑,可能是公司要求换 token,也可能只是你密码输错太多次被缓存了。我在 Mac 上处理这类问题次数不少,从 git push 弹出钥匙串窗口,到明明…

📰

树和堆:从完全二叉树到优先队列的算法进阶

把树和堆放在同一个标题里,其实不是偷懒,它们本来就是一对需要放在一起理解的搭档。我在准备数据结构期末考试、考研专业课和大厂算法面试的时候,都会把树和堆归成一类来复习。树给出了递归结构的骨架,堆则在这套骨架上实现了最简…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬