惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Register - Security
The Register - Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
L
LangChain Blog
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security @ Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
H
Hacker News: Front Page
L
Lohrmann on Cybersecurity
T
Troy Hunt's Blog
T
Threat Research - Cisco Blogs
A
About on SuperTechFans
T
Threatpost
AWS News Blog
AWS News Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Tor Project blog
Google Online Security Blog
Google Online Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
W
WeLiveSecurity
博客园 - 叶小钗
K
Kaspersky official blog
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
Hugging Face - Blog
Hugging Face - Blog
M
MIT News - Artificial intelligence
Hacker News - Newest:
Hacker News - Newest: "LLM"
Engineering at Meta
Engineering at Meta
有赞技术团队
有赞技术团队
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
小众软件
小众软件
D
Docker
爱范儿
爱范儿
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net

smallyu的博客

为什么买的加密货币一定要去中心化 RISC-V 虚拟机无法代替 EVM 虚拟机 Polkadot 的 Existential Deposit 机制错在哪里 利息,套利,交易策略,金融市场 LayerZero 从来不挑战比特币的地位 默认参数引起的以太坊节点运行错误 硬件钱包与资产安全 预测市场比币圈更赌场 为什么去中心化的跨链桥不可能实现 我对于 AI 时代的答案 不要投资任何隐私币 区块链技术世界的三大真理 以太坊 AA 钱包的致命问题是什么 基于 AI 语义执行的 MCP 区块链的设计 一个集成 Geth 和 CometBFT 的兼容层 我的加密货币定投策略(二) DeFi 基础: 理解 AMM 定价机制 Go 语言 GMP 调度器的原理是什么 Web3 项目分析计划 对 Psyche Network 项目的分析 continuation 教程: 理解 CPS 如何开发一个比特币符文(Runes)协议 比特币脚本开发教程 我从王垠的计算机科学视频班学到了什么 区块链技术面试题(2025年版) Rust 语言容易让新手困惑的一个“过度优化” Solana 智能合约开发教程 (1) 尝试开发一个最小 EVM 虚拟机 基于 ZK 的链上身份系统设计 一个 Web3 打赏系统的设计 鼓吹 Cursor 的人技术能力都差 关于 Code Review 的礼节 假如启动一个新的以太坊 PoS 网络 发行加密货币的最好方式 所有 BFT 共识的区块链都是中心化的 对 2025 年区块链行业的预测 Restaking 项目的经济难题 如何看懂任意区块链项目的技术架构 为什么不要做以太坊的二次开发 为什么不要做智能合约开发和 DeFi 开发 我的加密货币定投策略(一) PoS 类型的区块链如何处理分叉 Ethereum Casper 为什么需要 EIP-7251 区块链中的 PBFT 不需要第二次投票 开发者的思维方式 发币的核心要点 炒币投资的小 tips 为什么说 PoW 比 PoS 更加去中心化 牛市对普通人来说意味着什么 如何健康地远程工作 为什么比特币不用概率加密函数 程序员的 “服从权威” 心理 区块链技术面试题 如何区分公有链和联盟链 对 Layer 2 项目创业想法的回复 对区块链共识机制的理解 Pebbling Game 鹅卵石游戏 PDP 文件证明的局限性 不要小瞧 ChatGPT 为什么炒币不是一个好主意 一种在区块链上生成随机数的机制 为什么以太坊的私钥计算不可逆 关于以太坊的私钥碰撞 “猜均值的2/3” dApp 游戏设计 Proof of Storage/Space/Replication 的区别 Proofs of Retrievability 文件证明的含义 对 S-PDP 文件证明的示例和解释 我的加密货币交易机器人 对区块链行业的见闻 随机确认块的共识机制 VRF + BFT 共识引起交易失败的问题 为什么要重视编程思想 对 Web 3.0 的理解 GitBook 好用吗? 一种区块链节点存储扩容的方式 一种基于“自我中心主义”的共识机制 链表常见算法题及解析 基于 Multi-Linked List 的区块链设想 理解哈希函数与序列化 联盟链比公有链差在哪儿 为什么数字货币使用区块链是政治问题 区块链:下一代数字身份认证体系的基石 网页技术能实现 3D 建模吗? 给区块链一个定义 从 Erlang 开始了解 Actor 模型 一种侧边导航栏的交互方式 Rust 的 ownership 是什么? Haskell 中的 Monad 是什么? 浅析 Libra 背后的区块链技术 对区块链的理性认识 Rust 基础语法概述 基于 Java 的爬虫框架 WebCollector Kotlin:简化版的 Scala JavaScript 有关联数组吗? 主流编程语言的异常处理机制 Go 语言基本语法 Scala 语法基础 用 Scala 改写 Java 浅度实践 Python 获取海贼王更新信息 HTML5 音乐可视化
在 Dijkstra 算法中保存路径
2021-09-18 · via smallyu的博客

2021-09-18

区块链的 Layer 2 中有一种 State Channels 的扩容方案,其中会需要搜索距离最近的路由节点。

Dijkstra 算法思路

Dijkstra 算法能够解决 single-source 的最短路径问题,算法本身只输出一个点到其他点的最短距离。比如在这样一个图中,起点是 A,想知道到 D 点的最短距离是多少:

Dijkstra 算法实质是动态规划的贪心算法的结合,要寻找最短路径,就去遍历所有的点,每到一个点更新最短距离的记录,直到走过所有的点,就可以确信拿到了可靠的最短距离的记录。初始化的状态集合为:

A B C D
0 - - -

此时位于 A 点,未出发的状态,到自身的距离为 0,到其余点的距离未知。

从 A 点出发后,发现 A 点可以到达 B 点和 C 点,距离分别为 4 和 2,那么就更新状态集合为:

A B C D
0 - - -
4 [2] -

中括号的含义是在当前这一轮中距离最短的点,哪个距离最短,下一步就到哪个点。到 C 点的距离比到 B 点的距离短,所以下一轮到 C 点:

到 C 点以后,发现 C 点可以到达 A、B、D 三个点,这个时候意识到,其实 A 点已经走过了,不会再往回走的。于是需要另一个集合记录走到过哪些点,以避免下一步重复。定义 prev = [],因为 A 和 C 已经走过了,就把这两个点放到集合里, prev = [A, C]

在这一步的时候,到达 B 点的距离从 4 变成了 3,A -> C -> B 的距离小于 A -> B 的距离,更新状态集合,同时因为已经能够到 D 点了,更新到 D 点的距离:

A B C D
0 - - -
4 [2] -
[3] 5

这一轮中,到达 B 点的距离小于到达 D 点的距离,中括号选中 3,并且下一步到 B 点:

此时 prev = [A, C, B],状态集合更新为:

A B C D
0 - - -
4 [2] -
[3] 5
[5]

中括号只剩一个选择,只有 D 点没去过了:

prev = [A, C, B, D],所有点遍历结束,最终结果为:

A B C D
0 3 2 5

现在就可以知道从 A 点到 D 点的最短距离为 5.

最短路径跟踪

算法结束后,可以得到从 A 点到其他点的最短距离数据。可是如果不只想要距离值,还想要具体路径,比如从 A 点到 D 点的最短路径,该怎么处理?

正向贪心算法

可以判断出,从 A 到 D 的最短路径是 A -> C -> D,而上面的 prev 集合为 A, C, B, D。因为从 C 直接到 D 比 C -> B -> D 的距离要短,所以在路径中抛弃了 B 点。

按照这样的现象进行对比,是不是只要在 prev 的基础上,在合适时候抛弃某些点,就可以得到正确路径了?比如上面从 B 到 D,存在 4 种情况:

  • B 可以到达 D
  • B 不可以到达 D
  • 通过 B 到达 D 是状态集合中到达 D 距离最短的方案
  • 通过 B 到达 D 不是状态集合中到达 D 距离最短的方案

这 4 中情况中,只有 B 可以到达 D 并且 通过 B 到达 D 是状态集合中到达 D 距离最短的方案 的时候,才会保留 B 这个点到路径中。否则就应该去掉 B 点。

中括号每选择到一个点,就把点放到路径中,如果不满足上面的条件,就从路径中去掉这个点,也就是不放到路径里面。这样的话,即使有其他捣乱的点存在,程序也可以应对,比如:

在选中 B 点后,发现 B 点不满足条件,此时路径由 path = [A, C, B] 回退到了 path = [A, C]。如果下一轮最小的点选中了 E,path = [A, C, E],但是 E 点不满足条件,path = [A, C]。直到最小的点选中目标点 D,整个程序结束。

或者这样的,也可以处理,E 点不会被放到路径中:

那么这样的思路存在问题吗?当然有问题,这样的程序是不能处理这种情况的:

假如最短路径是 [A, E, C, D],E 点是不满足上面被放进路径的条件的,E 点无法直接到达 D 点,但是又必须被包含在路径里。去掉 可以直接到达 D 点 的限制?那上上图的 E 点也会被放到路径里。

也就是说,需不需要能够直接到达目标点,取决于对于最终的路径,被选中的点是不是倒数第二个点。这样的条件在一个未知的图中是无法判断的,谁能知道一个点是最终路径的倒数第几个点?

正向的贪心算法试图每一次都把距离最小并且在最终路径上的点记录下来,但其实很难做到,因为根本无法判断一个点是不是在最终的路径上。

反向贪心算法

当 D 点被中括号选中,作为本轮距离最小的点,就已经能够确定从 A 点到 D 点最短距离了。那么只要知道这一步是从哪个点过来的,来源的点就一定是最短路径的倒数第二个点。依次类推,只要层层回推到出发的点,整条路径就出来了。

假如在到达 D 点后,能够知道是从 C 点而不是 B 点过来,在 C 点的时候,能够知道是从 A 点而不是 B 点过来,整个路径就很清晰了。

问题是怎么在 D 点的时候,知道是从 C 点而不是 B 点过来的?选中最小距离点的顺序可是 [A, C, B, D],按照最小点的顺序显然是不行的。

这看起来不是一件难事,在 DFS 或者树的遍历中,经常会前后进入多个路径然后在适当的时候返回以修正路径。换个角度看,其实在 DFS 中维护最短距离,也可以达到目的。维护了距离状态的 DFS == Dijkstra algorithm 吗?显然不是。

递归 vs 尾递归

Dijkstra 适合写成循环的形式:

for {

}

更适合写成尾递归的形式:

func recursion() {
    
    recursion()
}

总之,程序会是单向的循环。适合写成递归的形式吗?

func recursion() {
    for {
        recursion()
    }
}

当遇到分支情况的时候,用 for 循环 “同时” 进入多个路径,寻找最合适的那个。比如到 C 点的时候,for 循环前后进入 C -> B -> DC -> D 的路径,每次循环将只保留一条路径,找到最合适的直接终止递归就可以。

这样的写法存在问题吗?问题在于,怎么确定在哪个节点进行分叉。在 C 点分叉?为什么是 C 点?为什么不是 B 点?如果是 B 点,路径上就会多出 B 点。为什么不是 A 点?如果是 A 点,到了 C 点的时候需不需要继续分叉?是每一个点都需要分叉吗?想象一下那会造成多么大的冗余……为什么树可以同时遍历?因为树的节点不会交叉。

第二个动态规划

第一个动态规划是指算法本身距离数据的维护。第二个动态规划可以维护一个路径数据的状态:

pathList = {
    A: [],
    B: [],
    C: [],
    D: []
}

路径状态保存从源点到达每个节点在当前阶段的最短路径,在一开始的时候,因为 A 点已经可以到达 B 和 C:

pathList = {
    A: [A],
    B: [A, B],
    C: [A, C],
    D: []
}

选择并到达 C 点,这个时候因为 C 点可以到达 B 点并且 A -> C -> B 的距离小于 A -> B,所以更新路径状态数据为 pathList[C].push(B)。D 点也可以到达了,更新路径状态。(更新路径状态数据发生在进入下一个点之前,甚至发生在选择下一个节点之前。可以想一想为什么这样做。)

pathList = {
    A: [A],
    B: [A, C, B],
    C: [A, C],
    D: [A, C, D]
}

这一轮在距离的状态数据上,会把 B 点选中为最小距离的节点,判断到达 D 的路径 A -> C -> B -> D 大于目前已有的距离记录 A -> C -> D,所以不更新路径状态。(判断距离是否大于已有距离是根据距离的状态数据,也就是表格的数据。)

最终进入目标 D 结束,路径状态不更新。

得到路径 A -> C -> D

路径的状态数据可以为了节省空间,只维护到达目标点的路径吗?不可以,因为更新下一个点的路径需要依赖当前点的路径,路径的状态必须是全量的。

非最短路径跟踪

Dijkstra 算法包含了贪心算法的思维,每一步选出的都是距离最短的点。如果需要保存不是最短路径的路径,Dijkstra 算法也许可以做到,但是就已经不需要 Dijkstra 算法了。DFS/BFS 更合适一点。

补充(2025.05.11)

这个 Dijkstra 相关的工作,是当时在一个 State Channels 的项目 pylons 上,用来在多个通道之间寻找最短路径用的,原本是 DFS,后来我加了一个 Dijkstra,带有黑名单的功能,以及把手续费作为路径距离的计算依据。

现在把 route 部分的代码单独拆分出一个仓库 smallyunet/dijkstra-demo 留作纪念。