[{"data":1,"prerenderedAt":406},["ShallowReactive",2],{"article:column:/column/math-beauty/relations-to-graphs":3},{"id":4,"title":5,"body":6,"description":12,"extension":396,"meta":397,"navigation":401,"path":402,"seo":403,"stem":404,"__hash__":405},"column/column/math-beauty/relations-to-graphs.md","从关系到图：节点和边从哪里来",{"type":7,"value":8,"toc":381},"minimark",[9,13,16,19,24,27,30,33,36,39,42,45,48,55,59,62,69,72,75,78,83,86,89,92,98,101,105,108,111,127,132,135,138,142,148,151,154,160,163,168,171,174,178,181,260,263,268,274,277,280,283,287,290,293,296,303,306,309,314,317,320,323,328,331,334,337,340,353,364,375,378],[10,11,12],"p",{},"一个网站有四个模块：页面 P、推荐 G、特征 F、存储 S。页面直接使用推荐和特征，推荐使用特征，特征再从存储读取数据。",[10,14,15],{},"现在准备修改存储模块。有人翻遍页面代码，说：“页面没有直接调用存储，应该不用检查它。”另一个人却担心页面会受影响。两人看到的代码可以都没错，分歧藏在“有关”这个词里：一个说的是直接使用，另一个说的是沿着依赖关系间接联系。",[10,17,18],{},"画几条线就能解决吗？先别急。我们必须约定线究竟代表什么，再谈怎样顺着线推理。本章要从这项约定出发，把上一章的集合变成图，并看清图能保留什么、推出什么。",[20,21,23],"h2",{"id":22},"_1-先把关系写成可以核对的记录","1 先把关系写成可以核对的记录",[10,25,26],{},"把研究对象放进集合：",[10,28,29],{},"V = {P, G, F, S}",[10,31,32],{},"这里把每个模块当成一个对象，暂时不拆开模块内部的函数。再规定“x 直接使用 y”为我们唯一记录的关系。",[10,34,35],{},"用有序对 (x, y) 记一条记录。括号里先后顺序有意义：(P, G) 表示页面使用推荐；(G, P) 表示推荐使用页面，二者是不同的事。“有序”只强调位置，不表示时间早晚。",[10,37,38],{},"于是，全部记录可以写成：",[10,40,41],{},"R = {(P, G), (P, F), (G, F), (F, S)}",[10,43,44],{},"V 中任取一个对象放前面，再任取一个放后面，共有 4 × 4 = 16 种有序对，包含 (P, P)。全部可能的有序对组成 V × V，读作“V 与 V 的笛卡尔积”。这里的乘号表示配对集合。",[10,46,47],{},"R 从这些可能的配对里选出关系成立的那些，数学上写作 R ⊆ V × V，称为 V 上的二元关系。“二元”说的是每条记录涉及两个位置，不要求这两个位置一定放不同对象。",[10,49,50,51],{},"这也接上了上一章的命题：给定 x 和 y，“x 直接使用 y”就能判断真假；关系 R 收集所有判断为真的配对。",[52,53,54],"span",{},"1",[20,56,58],{"id":57},"_2-每个对象画成点每条记录画成箭头","2 每个对象画成点，每条记录画成箭头",[10,60,61],{},"把 V 中的对象各画成一个节点，也叫顶点；把 R 中的 (x, y) 画成 x → y，这就得到一个有向图。我们用 H = (V, E) 记整个图：V 是顶点集合，E 是边集合，此处 E 就取 R。",[10,63,64],{},[65,66],"img",{"alt":67,"src":68},"同一组有序对对应两种布局，箭头方向和端点完全一致","/collections-assets/math-beauty/ch03/figure-01-relation-layout.png",[10,70,71],{},"图1：移动节点、弯曲边，并没有改变“谁直接使用谁”。",[10,73,74],{},"左图像分层架构，右图像一个环形布局。逐条检查，两张图都只有 P → G、P → F、G → F、F → S 四条边。线段画得更长，不代表调用更慢；两条线在纸上交叉，也不会自动增加一个节点。",[10,76,77],{},"给定顶点集合和这项关系，在不允许重复边的约定下，可以从记录画出图，也可以从图恢复记录。对象间的直接关系没有丢失。没有任何关系的对象也要保留成孤立节点，单记边的清单可能漏掉它。页面代码的细节、模块所在服务器、团队负责人则从未被记入，当然无法凭空读出来。",[79,80,82],"h3",{"id":81},"箭头圈和数字要按问题选择","箭头、圈和数字要按问题选择",[10,84,85],{},"“互为邻居”通常是对称关系，可以画无向边 {x, y}，端点不分先后。如果改用有序对记录，就需要同时写 (x, y) 和 (y, x)。模块依赖可能只有单向，不能随意擦掉箭头。",[10,87,88],{},"(x, x) 对应自环，即从节点出发又指向自己的边。本例没有自环；若节点表示状态，“收到一个无须处理的消息后仍处于原状态”就可能需要自环。",[10,90,91],{},"要区分连接耗时，可以给边加权重，但必须说明数字的意义和单位。标上“20 毫秒”，还要说明是某次测量、平均值还是上限；一个数字不能自动保证运行时表现。",[10,93,94,95],{},"若两个模块既通过 HTTP 请求交互，又通过消息队列交互，而问题要求保留这两种通道，就需要两条端点相同的边，即多重边。此时单靠有序对的集合会把它们合并，必须额外记录边的身份或类型。",[52,96,97],{},"2",[10,99,100],{},"下面仍使用最初那个没有自环、没有多重边的有向图。选择简单形式，是因为当前只问“是否直接使用”。",[20,102,104],{"id":103},"_3-相邻只看一步可达允许接着走","3 相邻只看一步，可达允许接着走",[10,106,107],{},"P → F 是一条边，所以 F 是 P 的一个出邻居；这个“出”字提醒我们遵守箭头方向。P 与 S 没有直接边，但可以顺着 P → F → S 走过去。",[10,109,110],{},"为免日常说法混淆，这里明确术语约定：",[112,113,114,118,121,124],"ul",{},[115,116,117],"li",{},"游走：沿箭头接连经过若干条边，允许重复经过节点和边",[115,119,120],{},"路径：不重复节点的游走，也常叫简单路径",[115,122,123],{},"长度：经过的边数，暂时不计算权重",[115,125,126],{},"可达：从起点到终点存在一条游走",[10,128,129,130],{},"不同教材对“路径”的用法略有差别；本章遵循上面的约定。P → G → F → S 是长度为 3 的路径，P → F → S 是长度为 2 的路径。反过来，从 S 出发没有边可走，因此 P 从 S 不可达。",[52,131,54],{},[10,133,134],{},"我们也允许长度为 0 的游走：停在原点，不走任何边。因此每个节点都从自身可达，即使没有自环。“走过至少一条边后回来”是另一个问题，不能拿零步作答。",[10,136,137],{},"原来的 R 记录直接使用；把所有可达配对收集起来，又得到一个关系。这个新关系包含 (P, S)，原来的 R 却不包含它。把“间接也有关”混入直接边，会改变图所表达的事实。",[20,139,141],{"id":140},"_4-一个可以亲手检查的结论","4 一个可以亲手检查的结论",[10,143,144],{},[145,146,147],"strong",{},"命题：在有 n 个顶点的有限有向图中，如果从 u 可以到达 v，就存在一条从 u 到 v、长度不超过 n − 1 的路径。",[10,149,150],{},"这里 n 至少为 1；当 u = v 时，零步路径已经满足结论。对于不同的起终点，先拿出任意一条确实走得到的游走。",[10,152,153],{},"如果它重复经过某个节点 x，就找到两次到达 x 的位置。两次之间的那段绕行从 x 出发又回到 x。删掉整段绕行后，前半段仍停在 x，可以直接接上后半段原本从 x 出发的边，终点不变。",[10,155,156],{},[65,157],{"alt":158,"src":159},"游走A到B到C到B到D删除B到C到B的绕行后，保留路径A到B到D","/collections-assets/math-beauty/ch03/figure-02-remove-detour.png",[10,161,162],{},"图2：另取一个含回路的小图，删掉绕行仍然可以到达原终点。",[10,164,165,166],{},"每删一次，边数至少减少 1。原来的边数是有限的，所以不可能无止境地删。最后没有重复节点，得到一条路径。图中一共只有 n 个顶点，路径最多经过 n 个；连接这些顶点最多需要 n − 1 条边。证明完成。",[52,167,54],{},[10,169,170],{},"这告诉我们：判断纯粹的可达性时，绕圈不会成为必需条件。它给出了一个有限长度的见证上界，但还没告诉我们怎样高效找到这条见证。",[10,172,173],{},"条件也很重要。假如绕一圈能够拿到钥匙，那么“回到同一个房间”未必回到了同一个状态。若后续通行取决于钥匙，就要把“房间位置、是否持有钥匙”一起作为节点内容。证明没有失效，原来只记录房间的模型漏掉了影响行动的信息。",[20,175,177],{"id":176},"_5-计算机可以不画图","5 计算机可以不画图",[10,179,180],{},"纸上的点和线便于理解，程序更适合保存明确的记录。仍以 P、G、F、S 为顺序，可以写一张表：行表示起点，列表示终点，有边填 1，没有填 0。",[182,183,184,207],"table",{},[185,186,187],"thead",{},[188,189,190,194,198,201,204],"tr",{},[191,192,193],"th",{},"从哪到哪",[191,195,197],{"align":196},"right","P",[191,199,200],{"align":196},"G",[191,202,203],{"align":196},"F",[191,205,206],{"align":196},"S",[208,209,210,224,236,248],"tbody",{},[188,211,212,215,218,220,222],{},[213,214,197],"td",{},[213,216,217],{"align":196},"0",[213,219,54],{"align":196},[213,221,54],{"align":196},[213,223,217],{"align":196},[188,225,226,228,230,232,234],{},[213,227,200],{},[213,229,217],{"align":196},[213,231,217],{"align":196},[213,233,54],{"align":196},[213,235,217],{"align":196},[188,237,238,240,242,244,246],{},[213,239,203],{},[213,241,217],{"align":196},[213,243,217],{"align":196},[213,245,217],{"align":196},[213,247,54],{"align":196},[188,249,250,252,254,256,258],{},[213,251,206],{},[213,253,217],{"align":196},[213,255,217],{"align":196},[213,257,217],{"align":196},[213,259,217],{"align":196},[10,261,262],{},"这叫邻接矩阵。现在只把它看成有固定行列的记录表，不需要先学线性代数。P 行 S 列为 0，仍然不妨碍 P 到 S 可达，因为这张表仅记录一步。",[10,264,265,266],{},"另一种方式是邻接表：给每个顶点列出它能直接指向的顶点。",[52,267,97],{},[10,269,270],{},[65,271],{"alt":272,"src":273},"同一张图的邻接矩阵和邻接表，突出显示P直接指向F的两种记录方式","/collections-assets/math-beauty/ch03/figure-03-representations.png",[10,275,276],{},"图3：同一条 P → F，在矩阵中占一个格，在邻接表中占一个条目。",[10,278,279],{},"邻接矩阵给每一种可能的配对留位置，n 个顶点就有 n × n 个格。若有一万个顶点，即使只有两万条边，也需要一亿个格。邻接表保留一万份顶点入口和两万条边记录，在这样的稀疏关系里通常更节省空间；具体字节数取决于实现。",[10,281,282],{},"要检查某条边是否存在，矩阵可直接查一个格；要列出某个节点的所有出邻居，邻接表可以直接读取那份名单。二者表达同一张图，却让不同操作更方便。这是数据结构选择的一个早期例子。",[20,284,286],{"id":285},"_6-图给出检查范围结论还需要更多内容","6 图给出检查范围，结论还需要更多内容",[10,288,289],{},"回到开头。P 能到达 S，表明页面对存储存在一条依赖链，因此页面属于值得检查的范围。沿我们的箭头是寻找依赖；从存储查哪些模块可能受影响，则要反着追踪。",[10,291,292],{},"但“可能受影响”不等于“必然出错”。存储可能保持了接口兼容，调用也可能发生在本次没有执行的分支。单凭这四条边，无法推断故障概率或响应时间。",[10,294,295],{},"我们还假设这份关系清单是完整的。如果只从一次运行日志里收集调用，没看到一条边，可能只是那段代码没有执行。此时矩阵里的 0 表示“尚未观察到”，就不能直接当成“确实不存在”。",[10,297,298,299,302],{},"AI 系统也会记录计算之间的依赖。例如 y = (a + b) × c，可以把加法、乘法及其输入输出组织成计算图。PyTorch 的自动求导会记录实际执行的运算，并利用这些记录计算梯度，也就是描述输入的微小变化如何影响结果的变化率。",[52,300,301],{},"3"," 图的连接关系说明谁依赖谁，运算类型和保存的数据才进一步决定怎样计算；擦掉“加”与“乘”等信息，只剩箭头骨架，就不足以恢复结果。",[10,304,305],{},"图的价值在于把一项明确的关系保存下来，让原本藏在叙述中的连锁联系变得可检查。下一章会进一步追问：当程序一步步改变状态时，有哪些性质能够始终成立？",[20,307,308],{"id":308},"顺手试一下",[10,310,311],{},[145,312,313],{},"题目1：一条新增的边",[10,315,316],{},"给最初的四模块图增加 S → G。现在 S 能否到达 P？F 能否经过至少一条边回到 F？请分别给出理由。",[10,318,319],{},"**提示：**从 S 开始顺箭头走；不要因为图里出现了圈，就认为所有节点都相互可达。",[10,321,322],{},"**参考答案：**S 不能到达 P，因为没有任何边指向 P，而 S 与 P 不同。F 可以沿 F → S → G → F 返回，长度为 3。这是重复了起终点的游走，不是本章定义的路径。",[10,324,325],{},[145,326,327],{},"题目2：把箭头擦掉会怎样",[10,329,330],{},"仍用最初的四模块图。若把全部边改成无向边，“S 能到达 P”的答案会改变吗？这说明什么？",[10,332,333],{},"**提示：**试走 S、F、P。",[10,335,336],{},"**参考答案：**会。无向图中可以沿 S—F—P 到达；原有向图中却不行。擦掉方向丢失了依赖关系的信息，所得结果不能继续解释为原来的“沿直接使用关系追踪”。",[20,338,339],{"id":339},"延伸阅读与来源",[10,341,342,344,345,352],{},[52,343,54],{}," Eric Lehman、F. Thomson Leighton、Albert R. Meyer，",[346,347,351],"a",{"href":348,"rel":349},"https://ocw.mit.edu/courses/6-042j-mathematics-for-computer-science-spring-2015/mit6_042js15_textbook.pdf",[350],"nofollow","《Mathematics for Computer Science》","，MIT，2015。参见4.4节的二元关系、9.2节的游走与路径、9.4节的可达关系。本章采用其不重复顶点的路径约定。",[10,354,355,357,358,363],{},[52,356,97],{}," Robert Sedgewick、Kevin Wayne，",[346,359,362],{"href":360,"rel":361},"https://algs4.cs.princeton.edu/42digraph/",[350],"《Algorithms, 4th Edition》配套材料第4.2节","。参见有向图、自环、平行边，以及邻接表和邻接矩阵的表示。该教材对 path 的术语约定与本文不同，阅读时留意定义。",[10,365,366,368,369,374],{},[52,367,301],{}," PyTorch 官方文档，",[346,370,373],{"href":371,"rel":372},"https://docs.pytorch.org/docs/main/notes/autograd.html",[350],"Autograd mechanics","，参见“How autograd encodes the history”。本章只借此说明计算依赖需要连同运算信息保存，自动求导的计算规则留待后文。",[376,377],"hr",{},[10,379,380],{},"作者：Jiancheng Wang · 数学之美（独立原创）",{"title":382,"searchDepth":383,"depth":383,"links":384},"",3,[385,387,390,391,392,393,394,395],{"id":22,"depth":386,"text":23},2,{"id":57,"depth":386,"text":58,"children":388},[389],{"id":81,"depth":383,"text":82},{"id":103,"depth":386,"text":104},{"id":140,"depth":386,"text":141},{"id":176,"depth":386,"text":177},{"id":285,"depth":386,"text":286},{"id":308,"depth":386,"text":308},{"id":339,"depth":386,"text":339},"md",{"collection":398,"order":383,"slug":399,"author":400},"数学之美","relations-to-graphs","Jiancheng Wang",true,"/column/math-beauty/relations-to-graphs",{"title":5,"description":12},"column/math-beauty/relations-to-graphs","VDPGNkINyUAUWfL8pXwOXKtHJ9UgdY6qkngfb9dYTvk",1791935829065]