SciTech-BigDataAIML-Algorithm-Heuristic启发式 最优化算法: KMP字符串匹配 + A*(star)+ Prim + Dynamic Planning动态规划 + Floyd(弗落伊得)最短路线
SciTech-BigDataAIML-Algorithm-Heuristic启发式 最优化算法
所有的Heuristic(启发式)算法 的 “精髓” 在于对“Context(上下文)”的精准“计算、估计和预测”;
每一种算法都有其最适用的场景,算法的设计、选取 应当适配 场景;
非常好的“斯坦福大学”计算机科学毕业生的网站
Amit Patelamitp@cs.stanford.edu
很好的游戏算法+编程网:
https://www.redblobgames.com/pathfinding/a-star/introduction.html
- KMP字符串匹配算法
- Dynamic Planning动态规划
- Dijkstra迪杰斯特拉算法
- A*算法
- Floyd(弗落伊得)最短路线算法
- Prim算法
KMP字符串匹配算法
def kmp_search(string, patt):
"""signature: 字符串匹配算法: 在<string>,搜索<pattern>"""
patt_len = len(patt)
cmp_len = len(string) - patt_len + 1
next = build_next(patt) # next数组的数值,代表"当前字符"匹配"失败"时,"可跳过"的"匹配字符个数".
i = 0 # string 的指针: 只增不减,同时 pattern 的指针还可以预先偏移比较,是KMP的精华
j = 0 # pattern 的指针
while i < cmp_len : # 注意: i 只增不减, KMP算法的精华
if string[i] == patt[j]:
# 字符匹配成功:
i+= 1 # string 指针前移一个
j+= 1 # pattern 指针前移一个
elif j > 0:
# 字符匹配失败: 之前有匹配成功的不只一个字符,next匹配时, 可跳过"上一次"匹配过的“那一部分”
j=next[j-1] # pattern 指针退一字符, 停在最长匹配的字符上; 跳过pattern的前面一些子串. KMP精华
else:
# 字符匹配失败: 第一个字符就失配,只将"string"指针前移一个字符
i+= 1 # string 指针前移一个字符.
if j== patt_len: # 匹配成功: 当指针 j 等于 pattern 长度
print(string)
print("%s%s" % (' '*(i-j), patt))
return i-j # 退出. return (i-j)
def build_next(patt):
"""
预先计算构造出Next数组: PMT(Partial Match Table, 部分匹配表)
用pdb 的step debug调试 build_next("ababcdabak"), 就对PMT的构造有最深刻的认知.
"""
# next数组的数值,代表"当前字符"匹配"失败"时,"可跳过"的匹配字符个数".
prefix_len = 0 #当前共同"前后缀"的长度
next = [0] #next数组,初始时第一个元素的0
k=1
while k< len(patt):
if patt[prefix_len] == patt[k]:
prefix_len+=1
next.append(prefix_len)
k+=1 # k 前移一个字符
else:
if prefix_len==0:
# 前一个字符不同;
next.append(0)
k+=1 # k 前移一个字符
else:
# **k不变**,**只改变 prefix_len**, 可能经过好几次的iteration, 才能得出要插入next数组的最终数值.
# 当: k 由增加态, 首次转为0时,总之, 分两类情况:
# prefix_len=1, 例如 pattern = "ABCBAK",
# 当k=5时, 先prefix_len=1, 当前k位"反溯 prefix_len 个字符"的 "AK", 与 头部的 "AB"的"偏移"同;
执行next step后, 有prefix_len=next[0]=0,
# prefix_len>1, 例如 pattern = "AACAAK",
# 当k=5时, 先prefix_len=2, 当前k位"反溯 prefix_len 个字符"的 "AAK", 与 头部的 "AAB"的"偏移"同;
执行next step后, 有prefix_len=next[1]=1
prefix_len=next[prefix_len-1] # KMP的精华;
return next
s = "ABABABCAA"
p = "ABABC"
def print_next(patt):
print(build_next(patt))
# 用pdb 的step debug调试 build_next("ababcdabak"),
# 就对 next数组(PMT)的构造有最深刻的认知.
# In [9]: print_next("ababcdabak")
# [0, 0, 1, 2, 0, 0, 1, 2, 3, 0]
KMP 字符串匹配算法:
- KMP算法的时间复杂度为
- 源于 Knuth, Morris, Pratt 三位大神的一篇论文"
FAST PATTERN MATCHING IN STRINGS
".
BFM(Brute Force Matching)
先引入BFM(Brute Force Matching, 暴力匹配):
- BFM算法的时间复杂度为
- 即在<str>搜索<pattern>,
由第一个字符开始一个个字符的与<pattern>比较,完全匹配上则return;
如果不完全匹配,则跳到下一个字符,重新一个个字符与<pattern>比较。
直到比较完成<str>的所有可能。
KMP( KMP Fast Pattern Matching)
KMP 快速匹配算法的思路是,不用BFM算法的"backup(回退)":
在匹配某一个字符时,
既然字符串在比对失败的时候,我们已经知道都读取比对过哪些字符序列,
有没有可能避兔“backup(回退)到下一个字符,重新匹配”的步骤?
KMP 匹配实例:
假设"string"为"ABABABCAA", 查找的"pattern"为"ABABC":
-
KMP匹配可能的最大长度字符串时: (i, j)指针对, 历经匹配的(0,0), (1,1), (2,2), (3,3),
-
KMP 匹配某一个字符, 字符串在比对失败时:
-
到(i, j) 为 (4,4) 时,"pattern"出现第一个“不匹配字符”, 此时 j = 4,
-
设置pattern头部"偏移": j=next[j-1], 偏移量可能值的情况是
- j = 0, 当 都可能将"j"设置为0,即对 string 的 i 处的字符与 pattern 的首个字符开始,一一比较.
- 0 < j < n, 当 pattern为一个字符重复出现 n次时, j 取得最大值:n-1
本例是 j=next[j-1]=next[3]=2
此时 j = 2, 同时 i=4(i不变) -
注意: 整个查找过程, i都是只向前移动,而pattern头部的“偏移量”根据"匹配情况"动态设置为[0, n)
这是 KMP算法的两个最大特点。
-
-
KMP 匹配某一个字符, 可以只 "Partial matching"(上一步比较过, 并且有Partial matched)跳过matched的部分:
在本例:- 即"AB"已经在上一次时匹配过的LCS(Longest Common Substring, 最长匹配substr),本次可以跳过Partial;
- 跳过"上一次匹配"过的"Partial matched", 是通过设置"本次匹配"的"pattern头部"的"偏移量"实现(string指针不变).
- 实现上: 由pattern预先build的next数组, 取出"本次匹配"可"预先跳过比较"的"最长pattern字符数".
- 源码:此时 string 指针 i 不变, 只要 pattern 指针设置成
j=next[j-1]
( 因为j>0
).
-
Papers: 下载 Knuth/Morris/Pratt 的Paper论文:
ACM(Association for Computing Machinery) of the USA.
https://dl.acm.org/doi/abs/10.1137/0206024
多读和写论文绝对有好处:
Dynamic Planning动态规划
Dynamic Planning/Programming:
动态规划是计算机解决最优化问题的一种方法; 他给我们的印象是效率高、速度快。
-
首先我们来看一个经典的“动态规划问题”:
-
给一个无序数组,找出所有"最长递增子序列":
举例: 无序数组 的 最长递增子序列: 一个是$ [1,2,4] [1,2,3] $
-
我们可以进一步简化这个问题:
只要算法求出"最长递增子序列"的长度就好; -
枚举(普通解法):
- 试验+探索:枚举代表性的一组数据(cases), 画出其"Recursion Tree":
- 理论设计: 写出其"Recursion Function", 用"科学方法"在"理论上"评估算法性能(时间复杂度, BigO Notion)":
- 试验+探索:枚举代表性的一组数据(cases), 画出其"Recursion Tree":
-
理论优化:
-
Pruning(剪枝)
-
记忆化搜索:
-
工程优化,将Recursion Method改写成 Iteration Method方式:
Iteration Method可以 释放对 Stack memory的占用,将"动态的内存需求"调整到用Heap memory满足:
-
-
最终评估: 用"科学方法"在"理论上"评估算法性能(时间复杂度, BigO Notion)"
-
-
扩展:
A *(Star) Algorithm(A星算法):
amitp@cs.stanford.edu
很好的游戏算法+编程网:
https://www.redblobgames.com/pathfinding/a-star/implementation.html
Loss Function: TotalCost="NewCost + PredicativeCost"
计算"总体的Cost": 包括一部分的"NewCost(已用成本)",和另一部分的"Predicative(预测成本)";
在对"predicative future cost(预测的将来成本)"的估计,和评估 TotalCost(总体成本)这些方面,
A *(Star) Algorithm(A星算法) 要优于 只计算 best next cost的Dijkstra(迪杰斯特拉算法)。
这是 A *(Star) Algorithm 多数时间 优于 Dijkstra Algorithm 的 root cause;
但是 Dijkstra Algorithm 的计算量小、算法精简。
1解决游戏的自动路线规划 | 2算法图示 | 3对比多种 |
---|---|---|
![]() |
![]() |
![]() |
主体实现
def heuristic(a, b):
# Manhattan distance on a square grid
return abs(a.x - b.x) + abs(a.y - b.y)
def a_star_search(graph,start,goal):
frontier=PriorityQueue()
frontier.put(start,0)
came_from = {start:None}
cost_so_far ={start:0}
while not frontier.empty():
# 由"优先队列"抽取出"总代价最低"(new_cost+heuristic(goal,next))的node
current=frontier.get()
if current= goal:
break
for next in graph.neighbors(current):
new_cost = cost_so_far[current] + graph.cost(current,next)
if next not in cost_so_far and new_cost < cost_so_far[next]:
came_from[next]=current
cost_so_far[next] = new_cost
# 将next的node及其"总代价"插入"优先级队列"
frontier.put(next, new_cost+heuristic(goal,next))
return came_from, cost_so_far
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南