有趣的跳马问题与最优路径

献给皮鞋👞经理

有一个无限大的棋盘,在某个点有一个只能走日的马,计算马到达棋盘上任意一个点 P(x, y) 最小步数。

“走日” 规则下,任意坐标的 “马” 是否可达任意其它坐标需要证明。按照递归原则,只需证明 “马” 可达它的前后左右四个坐标就足够了,再根据对称原理,只需证明 “马” 能可达它相邻的坐标就足够,而这很容易:
在这里插入图片描述

上来就 BFS 遍历肯定没问题,答案一定是正确的:

#!/Users/zhaoya/myenv/bin/python3from collections import dequedx = [1, 2, 2, 1, -1, -2, -2, -1]
dy = [2, 1, -1, -2, -2, -1, 1, 2]def horse_move(target_x, target_y):queue = deque([((0, 0), 0)])visited = set([(0, 0)])while queue:(curr_x, curr_y), steps = queue.popleft()if curr_x == target_x and curr_y == target_y:return stepsfor i in range(8):next_x = curr_x + dx[i]next_y = curr_y + dy[i]if (next_x, next_y) not in visited:visited.add((next_x, next_y))queue.append(((next_x, next_y), steps + 1))if __name__ == "__main__":target_x, target_y = map(int, input("target: ").split())print(horse_move(target_x, target_y))

这最简单,也最蠢,主要就是欺负计算机算得快,但即便如此,在 target 距离原点非常遥远时,BFS 计算量将爆炸式递增。设搜索深度为 d(即最终的结果),涉及棋盘面积为 n^2,最坏情况下,d 接近 n^2,跳马问题的分支因子为 8,因此最坏情况下时间复杂度为 O ( 8 n 2 ) O(8^{n^2}) O(8n2),在无任何优化涉入时,盲目遍历的总体性能非常接近最坏情况,运行以上代码,输入 (10000 10000),几近卡死,归根结底是能耗。因此需要剪枝。

一个自然的优化就是按 “两点之间线段最短” 原则,在原点和 target 连线周边约束 BFS,其它点不予考虑:
在这里插入图片描述

由于 “走日” 规则并不能让坐标连续移动,为避免过度拟合,到达目标点前的 “一定距离” 后,关闭启发式优化,回退到标准 BFS:

#!/Users/zhaoya/myenv/bin/python3from collections import deque
import mathdx = [1, 2, 2, 1, -1, -2, -2, -1]
dy = [2, 1, -1, -2, -2, -1, 1, 2]
MIN_DIST = 20def point_to_line_distance(x1, y1, x2, y2, x0, y0):A = y2 - y1B = x1 - x2C = x2 * y1 - x1 * y2distance = abs(A * x0 + B * y0 + C) / math.sqrt(A ** 2 + B ** 2)return distancedef horse_move(target_x, target_y):queue = deque([((0, 0), 0)])visited = set([(0, 0)])total_cnt = 0while queue:(curr_x, curr_y), steps = queue.popleft()total_cnt += 1if curr_x == target_x and curr_y == target_y:return steps, total_cntcurr_dist = pow((target_x - curr_x)**2 + (target_y - curr_y)**2, 0.5);for i in range(8):next_x = curr_x + dx[i]next_y = curr_y + dy[i]if (next_x, next_y) not in visited and curr_dist < MIN_DIST:visited.add((next_x, next_y))queue.append(((next_x, next_y), steps + 1))elif (next_x, next_y) not in visited:line_dis = point_to_line_distance(0, 0, target_x, target_y, next_x, next_y)if line_dis < 2:visited.add((next_x, next_y))queue.append(((next_x, next_y), steps + 1))else:visited.add((next_x, next_y))if __name__ == "__main__":target_x, target_y = map(int, input("target: ").split())print(horse_move(target_x, target_y))

看出问题了吗?

给一个遥远的 target,比如 (10000, 10000),与原始 BFS 相比,total_cnt 小了几个数量级,但优化后的 steps 可能并非最优的,因为在退出 “瞄准目标” 这种优化前,可能已经走了弯路(虽然概率上不太可能,但受限于 target 坐标和 MIN_DIST,也不是不可能)。这就是优化的代价。

后面我会提到,如此约束的可扩展节点实际上接近 DFS,下图展示相比 BFS,DFS 如何错过最短路径:
在这里插入图片描述

求一个 “最值” 时,需考虑该 “最值” 在付出多大代价后会削减它的收益,这就需要权衡。这也是人和 计算机(或许还有 AI?)的最大区别之一。人总是会求一个足够好但并非绝对最优的解,但计算机利用其强大的算力,总是试图通过试错的方式遍历求解绝对最优解。

用最优效益替代最优解是高尚的,它可以做成一个比值:

E = 解的收益 获得解的代价 E=\dfrac{解的收益}{获得解的代价} E=获得解的代价解的收益

看一个效率和正确性的折中方案:
在这里插入图片描述

上述代码输入 (1234567 10000) 时,输出如下:(617285, 9877442),比较次数相比 steps 还是多了一个数量级,在最最理想情况下,二者应该相等,如何逼近这种相等呢?

观察上面的优化,虽然随着到达直线的距离约束的缩短,黑色实心点变少,空心点变多,但依然有太多实心坐标参与遍历,需要用一种更加确定的手段排除尽可能多的坐标。这里的哲学是,目标遥远时采用深度优先,接近目标时采用广度优先。我们从学习一门课程的实践,或寻找一个从未去过的地点时,可以体验到这样做的效率。

从上面的折中方案图示我们可以看到 BFS 到 DFS 再到 BFS 的渐变过程。最初我们希望 BFS 遍历,但由于源到达目标直线斜率的约束,我们排除了 BFS 过程中许多 child,这相当于转向了 DFS,然而随着目标的接近,约束逐渐放宽,更多下一级 child 加入,DFS 转向 BFS。BFS 倾向于正确性,DFS 倾向于效率,它们在互相转换中折中。

若牺牲一定正确性,考虑到 target 非常遥远,便不去榨取 BFS 避免过拟合节省的比较次数,持续使用 DFS。代码如下:

#!/Users/zhaoya/myenv/bin/python3from collections import dequedx = [1, 2, 2, 1, -1, -2, -2, -1]
dy = [2, 1, -1, -2, -2, -1, 1, 2]def horse_move(target_x, target_y):stack = deque()d0 = 0 + math.sqrt((target_x - 0) ** 2 + (target_y - 0) ** 2)stack.append(((0, 0), 0, d0)) visited = set([(0, 0)])total_cnt = 0while stack:(curr_x, curr_y), steps, d = stack.popleft()total_cnt += 1if curr_x == target_x and curr_y == target_y:return steps, total_cntcandidates = []for item in stack:candidates.append(item)for i in range(8):next_x = curr_x + dx[i]next_y = curr_y + dy[i]new_steps = steps + 1# 只度量相对大小,为确保精度不缺失,不再进行 sqrt。new_d = new_steps + ((target_x - next_x) ** 2 + (target_y - next_y) ** 2)candidates.append(((next_x, next_y), steps + 1, new_d))candidates.sort(key=lambda x: (x[1] + x[2]))# 这里实现复杂了,我的本意是对 8 个 child 在整个 stack 中做基于 new_d 的排序插入,# 以确保 stack 从栈顶到栈底的 dist 越来越大,于是距离目标最短的 item 优先弹出继续# 向目标推进。stack = deque()for candidate in candidates:if candidate[0] not in visited:visited.add(candidate[0])stack.append(candidate)if __name__ == "__main__":target_x, target_y = map(int, input("target: ").split())print(horse_move(target_x, target_y))

同样的 (1234567, 10000) 输入,输出为 (617289, 617290),目标已经达成。但还是那句话,这个解不能保证最优,但足够好,至少它不仅算出了足够小的步数,还保证了这个计算的代价足够低。
这里有一个使用 Python heapq 的实现,一个意思:

#!/Users/zhaoya/myenv/bin/python3import heapqdx = [1, 2, 2, 1, -1, -2, -2, -1]
dy = [2, 1, -1, -2, -2, -1, 1, 2]def horse_move_optimized(target_x, target_y):queue = [(0, ((0, 0), 0))]visited = set([(0, 0)])total_cnt = 0while queue:_, ((current_x, current_y), steps) = heapq.heappop(queue)total_cnt += 1if current_x == target_x and current_y == target_y:return steps, total_cntfor i in range(8):next_x = current_x + dx[i]next_y = current_y + dy[i]if (next_x, next_y) not in visited:visited.add((next_x, next_y))new_steps = steps + 1prior = new_steps + ((target_x - next_x) ** 2 + (target_y - next_y) ** 2)heapq.heappush(queue, (prior, ((next_x, next_y), new_steps)))if __name__ == "__main__":target_x, target_y = map(int, input("target: ").split())print(horse_move_optimized(target_x, target_y))

到此为止,就差一步便导出 A* 搜索算法的形式,用方向(类似哈密尔顿距离的启发)逼近替代欧几里得距离:

def heuristic(current_x, current_y, target_x, target_y):return max(abs(current_x - target_x), abs(current_y - target_y))prior = new_steps + heuristic(next_x, next_y, target_x, target_y)

A* 算法用总代价 f(x) = g(x) + h(x) 作为节点的扩展优先级,其中 g(x) 为起点到 x 的实际代价,h(x) 为 x 到目标节点的预估代价,h(x) <= ‘x 到目标节点的实际代价’ 确保代价不会被高估,从而保证被低估的实际代价逐渐叠加到 g(x) 上,优先级队列使实际代价低却被隐藏的节点往上浮,这就保证了最优解的正确性,h(x) 的该特性称作可采纳性。

可采纳性保证了算法能朝最优解的方向合理引导搜索过程,避免过度探索偏离最优路径的区域,优于采用欧氏距离作为引导的搜索,在跳马类问题中,欧氏距离作为预估代价经常导致看似很远的节点却比欧氏距离很近的节点更容易到达,不具备可采纳性。

与上面一系列优化相比,A* 算法显然兼具正确性和效率,棒极了,我没有自然而然推出 A*(虽然我不知道名字,但知道意思) 算法,因为我没有从全局优先级队列去设计算法,而是采用 “优化 BFS” 的思路,有了 BFS 本就在那里待优化的前提,大方向一开始就错了,但我对过程中最优解和次优解的辩证转换还是很满意,转念一想,A* 算法在优先级队列(或堆)里消耗了太多时间,如排序,插入,这并非免费,同样是代价,而我最初用直线斜率约束 8 个 child 扩展资格的方法在稍微松弛了最优解的 “最” 字之后就自然避免了这些代价,交易依然存在。

遗留一个小问题,如果棋盘不是无穷大,而是 mm,mn 则如何?

若在有限范围内跳马,徒增约束而已,在 BFS 过程中,8 个 child 节点的添加需要考虑边界约束。但最大的问题是无法利用 “方向约束” 进行有效 DFS 了。但仔细一想,这也不是问题,如果 m,n 够大,依然可以利用 DFS,如果 m,n 很小,BFS 遍历时间复杂度也会被有效抑制,也就用不到 DFS 优化。

参考 Dijkstra 算法,它其实就是个洪水泛滥的过程,背后是第一性原理,最短路径恰由 BFS 保证,自然界的最小作用量原则总让最短路径的目标首当其冲,基于此我提出一个一次 BFS 寻遍最短路径的方法:单源最短路径新方法。将算法约束松弛一下,便可在快得多的时间内寻遍 “不一定最优但足够好” 的 “短路径”,这在实践上非常有意义。

早些年做 SDWAN 流量调度,为疏导拥塞流量,我会搜罗一箩筐指标进行加权,运行 Dijkstra 算法,作为 overlay 隧道路径质量度量 ,随节点规模扩大,稳定收敛时间到达秒级以上,终于还是遇到扩展性问题。每当这时,我心里清楚得很,需要做交易,但内心还是希望白嫖,通过一个巧妙的把戏妙手回春。

我的目标是将拥塞流量疏导,动作一定要快才能避免更严重丢包,而丢包重传意味着能耗的浪费。换句话说,只要找到一条或几条不拥塞的路径构建隧道,而不是为了找到任意两点的最短路径而构建一棵最短路径树。这连次优解都不需要,甚至只需要几个次差路径就够了。

我毅然抛弃了通过最短路径树稳定收敛的方法,转向了随机试错的方式(我对 PFC 拥塞疏导还是一样的观点),在此基础上构建松散的最大流网络。虽然得到的不是最优解,但我根本不需要最优解,能快速疏导大流量拥塞就完成目标了。

重新审视这事,方法论和本文前半部分针对跳马问题的逐步优化如出一辙,理解交易的本质,你需要什么,你能付出什么来交换。

更进一步讲,基于 TCP/IP 的互联网路由真的需要 “最短路径优先” 吗?流量在 “最短” 路径上真的能获得最优传输 QoS/QoE 吗?如果最优路径形成共识,次优路径有何种概率可成为事实上的最优路径却又形不成共识呢?与最短路径优先(比如,所有节点都保有一棵一致的最短路径树的副本)共识相反,本质上路由就是个无共识的概率问题,能顺利通过的概率,而顺利度需要以不同的方式度量,而不是一般数学意义上的 “最优”。概率路由更符合连通的第一性原理。

最优解的目标是约束计算复杂度,但获得最优解的过程却有代价,和往常一样,现实的最优解要做个除法。

从开始学编程做课后习题到各类考试,面试,晋升,再到应经理对立项挑战,论文 PR…始终绕不开 “最优解”,最短路径,最小生成树,最大流,最小割…似乎最优解就是最有效率解,能挑战一切。但实际情况中我们考虑的更多是平均情况下差不多的解,多低的概率不跌落到最坏情况。比如最常见的排序算法,没人任何算法声称最快,但快速排序却在大多数情况下表现足够好以获得其名。此外,我们虽在效率上敢于打折扣,却几乎不敢在正确性上打折扣,本文提出一个观点,为了效率亦可挑战 正确性,为了效率亦可牺牲正确性,全局最优要同时考虑正确性和效率。挑战正确性的前提是正确性是否为效率服务,如果是,那正确性本身就可退而求其次被挑战,幸运的是,绝大多数求最优解的算法都是为了效率,这意味着它们可以被挑战,求次优解的代价可小多了。

浙江温州皮鞋湿,下雨进水不会胖。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/475246.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDEA自定义文件打开格式

介绍在IDEA中自定义文件打开格式的方法&#xff0c;比如一个文件&#xff0c;可以选择用txt格式打开&#xff0c;也可以选择用xml格式打开&#xff0c;也可以用java格式打开等等&#xff0c;通过这个方法可以方便的用任意格式在idea中打开想要打开的文件。 下面分别讨论三种不…

百度智能云千帆大模型平台引领企业创新增长

本文整理自百度世界大会 2024——「智能跃迁 产业加速」论坛的同名演讲。 更多大会演讲内容&#xff0c;请访问&#xff1a; https://baiduworld.baidu.com 首先&#xff0c;跟大家分享一张图&#xff0c;这个是我们目前大模型应用落地的场景分布。可以看到&#xff0c;大模型…

【蓝桥杯C/C++】翻转游戏:多种实现与解法解析

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: 蓝桥杯C/C 文章目录 &#x1f4af;题目&#x1f4af;问题分析解法一&#xff1a;减法法解法二&#xff1a;位运算解法解法三&#xff1a;逻辑非解法解法四&#xff1a;条件运算符解法解法五&#xff1a;数组映射法不同解法的比较…

第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇

Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…

04 - Clickhouse-21.7.3.14-2单机版安装

目录 一、准备工作 1、确定防火墙处于关闭状态 2、CentOS 取消打开文件数限制 3、安装依赖 4、CentOS取消SELINUX 二、单机安装 2.1、下载安装 2.2、安装这4个rpm包 2.3、修改配置文件 2.4、启动服务 2.5、关闭开机自启 2.6、使用Client连接server 一、准备工作 1…

Python脚本-linux远程安装某个服务

需求&#xff1a; 某公司因为网站服务经常出现异常&#xff0c;需要你开发一个脚本对服务器上的服务进行监控&#xff1b;检测目标服务器上是否存在nginx软件(提供web服务的软件)&#xff0c;如果不存在则安装(服务器可能的操作系统Ubuntu24/RedHat9)&#xff1b;如果nginx软件…

芯片之殇——“零日漏洞”(文后附高通64款存在漏洞的芯片型号)

芯片之殇——“零日漏洞”(文后附高通64款存在漏洞的芯片型号) 本期是平台君和您分享的第113期内容 前一段时间,高通公司(Qualcomm)发布安全警告称,提供的60多款芯片潜在严重的“零日漏洞”,芯片安全再一次暴露在大众视野。 那什么是“零日漏洞”?平台君从网上找了一段…

x-cmd mod | x pixi - 兼容 Conda 生态的极速包管理器,conda 和 mamba 用户的另一选择

目录 介绍使用语法参数子命令 介绍 x pixi 模块是由 x-cmd 团队使用 posix shell 实现的 pixi 命令增强工具。它能优化 pixi 命令的安装和使用体验&#xff0c;具体如下&#xff1a; 提供带有 advise 的自动补全功能。对于中国区用户&#xff0c;我们还提供了汉化版的 advise…

Rust derive macro(Rust #[derive])Rust派生宏

参考文章&#xff1a;附录 D&#xff1a;派生特征 trait 文章目录 Rust 中的派生宏 #[derive]基础使用示例&#xff1a;派生 Debug 派生其他常用特征示例&#xff1a;派生 Clone 和 Copy 派生宏的限制和自定义派生自定义派生宏上面代码运行时报错了&#xff0c;以下是解释 结论…

Node.js | npm下载安装及环境配置教程

前言&#xff1a; npm 是 Nodejs 下的包管理器&#xff0c;在下载 Node.js 后自动安装&#xff0c;因此本文同时适合 Node.js / npm 的下载安装及环境配置。 一、软件安装 Node.js中文网官网下载页&#xff1a;Node.js 中文网 (nodejs.com.cn) 1&#xff09;进入下载页&#xf…

pytorch奇怪错误

ValueError: At least one stride in the given numpy array is negative, and tensors with negative strides are not currently supported. (You can probably work around this by making a copy of your array with array.copy().) 今天在这里遇到了一个奇怪的bug impor…

EDA实验设计-led灯管动态显示;VHDL;Quartus编程

EDA实验设计-led灯管动态显示&#xff1b;VHDL&#xff1b;Quartus编程 引脚配置实现代码RTL引脚展示现象记录效果展示 引脚配置 #------------------GLOBAL--------------------# set_global_assignment -name RESERVE_ALL_UNUSED_PINS "AS INPUT TRI-STATED" set_…

151页PDF | XX集团数字化转型SAP项目规划方案(限免下载)

一、前言 这份报告是XX集团数字化转型SAP项目规划方案&#xff0c;该报告涵盖了集团战略解读、管理痛点分析、信息化建设目标、整体架构方案、实施策略、SAP系统价值和预期收益&#xff0c;旨在通过数字化推动集团运营模式变革&#xff0c;实现降本增效和价值创新。 《XX集团…

共建智能软件开发联合实验室,怿星科技助力东风柳汽加速智能化技术创新

11月14日&#xff0c;以“奋进70载&#xff0c;智创新纪元”为主题的2024东风柳汽第二届科技周在柳州盛大开幕&#xff0c;吸引了来自全国的汽车行业嘉宾、技术专家齐聚一堂&#xff0c;共襄盛举&#xff0c;一同探寻如何凭借 “新技术、新实力” 这一关键契机&#xff0c;为新…

在 cmd 输入 python.exe 后不报错也无反应的问题

在 cmd 输入 python.exe 后不报错&#xff1a;‘python.exe ’不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件&#xff0c;也无反应。只是显示这样一个弹窗&#xff1a; 查了下环境变量path&#xff0c;看看有什么地方有python.exe&#xff0c;发现原来在C:\Us…

算法日记 30 day 动态规划(背包问题)

今天是动态规划的另一个大类&#xff0c;背包问题。 背包问题的分类 这张图中涵盖了我们能遇到的绝大部分背包问题。 首先是01背包问题 01背包问题 01背包问题&#xff1a;有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value…

如何修复WordPress卡在维护模式

当你管理WordPress网站时&#xff0c;没有什么比看到它卡在维护模式更令人沮丧的了。特别是在你进行重要更新或期望大量流量的时候&#xff0c;这种情况会更加令人不安。 维护模式可能由许多因素引起&#xff0c;从简单的文件损坏到更复杂的插件冲突或存在的.maintenance文件。…

Oracle OCP认证考试考点详解082系列22

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 105. 第105题&#xff1a; 题目 解析及答案&#xff1a; 题目翻译&#xff1a; 关于Oracle数据库中的事务请选择两个正确的陈述&#xf…

目录背景缺少vscode右键打开选项

目录背景缺少vscode右键打开选项 1.打开右键管理 下载地址&#xff1a;https://wwyz.lanzoul.com/iZy9G2fl28uj 2.开始搜索框搜索vscode&#xff0c; 找到其源目录 3.目录背景里面&#xff0c; 加入vscode.exe 3.然后在目录背景下&#xff0c; 右键&#xff0c; code就可以打…

第三届航空航天与控制工程国际学术会议 (ICoACE 2024)

重要信息 会议官网&#xff1a;www.icoace.com 线下召开&#xff1a;2024年11月29日-12月1日 会议地点&#xff1a;陕西西安理工大学金花校区 &#xff08;西安市金花南路5号&#xff09; 大会简介 2024年第三届航空航天与控制工程国际学术会议&#xff08;ICoACE 2024&a…