本期任务:介绍算法中关于回溯思想的几个经典问题
一、问题描述
假设正表达式中只包含 和 这两种通配符, 其中, 匹配任意多个(大于等于0个)任意字符, 匹配零个或者一个任意字符。基于以上背景假设,如何判断一个给定的文本,能否跟给定的正则表达式匹配?
二、算法思路
1. 策略选择
- 正则表达式问题是典型的“多阶段决策最优解”问题:每个位置都需要决策一次;最优解是模式串与给定串匹配或者遍历穷举完所有可能情况之后发现不匹配。
- 不满足无后效性:例如通配符 匹配不同长度的字符,可能导致不存在可行解,即后面的状态可能影响前面状态,故不能使用动态规划。
- 本问题也不满足贪心选择性,即无法通过局部最优的选择,能产生全局的最优选择,故不能使用贪心策略。
- 本题使用回溯算法暴力穷举所有可能的匹配方式,并通过剪枝策略进行优化。
2. 回溯算法思路
- 暴力穷举,依次考察正则表达式中的每个字符,
- 当是非通配符时,我们就直接跟文本的字符进行匹配,如果相同,则继续往下处理;如果不同,则回溯。
- 当是特殊字符时,比如“*”,可以匹配任意个文本串中的字符,我们就先随意的选择一种匹配方案,然后继续考察剩下的字符。如果中途发现无法继续匹配下去了,我们就回到上一个岔路口,重新选择一种匹配方案,然后再继续匹配剩下的字符。
- 剪枝策略:一旦出现匹配的情形,则直接停止递归,返回True。
- 结算情形:正则表达式到结尾了
三、Python代码实现
class Regex():
def __init__(self, pattern, text):
self.pattern = pattern
self.plen = len(self.pattern)
self.text = text
self.tlen = len(self.text)
self.matched = False
pass
def regex(self):
"""
我们依次考察正则表达式中的每个字符,
当是非通配符时,我们就直接跟文本的字符进行匹配,如果相同,则继续往下处理;如果不同,则回溯。
如果遇到特殊字符的时候,我们就有多种处理方式了,也就是所谓的岔路口,
比如“*”有多种匹配方案,可以匹配任意个文本串中的字符,我们就先随意的选择一种匹配方案,然后继续考察剩下的字符。
如果中途发现无法继续匹配下去了,我们就回到这个岔路口,重新选择一种匹配方案,然后再继续匹配剩下的字符。
"""
self.rmatch(0, 0)
return self.matched
def rmatch(self, ti, pj):
if self.matched: # 如果已经匹配了,就不要继续递归了
return
if pj == self.plen: # 正则表达式到结尾了
if ti == self.tlen:
self.matched = True
return
if self.pattern[pj] == '*': # *匹配任意个字符
for k in range(self.tlen - ti):
self.rmatch(ti + k, pj + 1)
elif self.pattern[pj] == '?': # ?匹配0个或者1个字符
self.rmatch(ti, pj + 1)
self.rmatch(ti + 1, pj + 1)
elif ti < self.tlen and self.pattern[pj] == self.text[ti]: # 纯字符匹配才行
self.rmatch(ti + 1, pj + 1)
def main():
p = Regex('ab*a', 'abbbbbbba') # 正例1
print(p.regex())
p = Regex('ab?a', 'abba') # 正例2
print(p.regex())
p = Regex('ab*ab', 'abbbbbbba') # 反例1
print(p.regex())
p = Regex('ab?a', 'abbba') # 反例2
print(p.regex())
pass
if __name__ == '__main__':
main()
运行结果:
True
True
False
False