如果说上节的兔子问题还可以用非递归的方法实现的话,那么下面这个例子就很难用非递归方法来实现了。
假设“*”可以匹配0个或0个以上的字符,“?”可以匹配且仅匹配一个字符。请写一个递归函数match(pattern, str)判断字符串str是否与模式pattern匹配。
比如,在操作系统里寻找一个文件时,不必写全文件的名字。可以使用*和?这两个通配符。比如AB*C?.doc表示任何以AB打头,倒数第二个字符是C的doc文档。
我们仍然按照递归三部曲来考虑这个问题的解法。
第一,确定边界条件。
在什么情况下我们可以直接判断str是否与pattern匹配?显然,如果str是个空字符串(即长度为0的字符串)的话,那pattern也必须是个空字符串两者才能匹配。反过来,如果pattern是个空字符串的话,那它也只能和空字符串匹配。所以这个边界条件是str或pattern是空字符串。
第二,递归假设。
假设在pattern或者str比原来少一个字符情况下match函数总能正确地判定二者是否匹配。注意递归假设所用到的参数要比原参数更靠近边界,只要满足这个条件,任何假设都是合理的。
第三步,递归推导。
我们可以考虑pattern的第一个字符first。
- 如果它是普通字符,看它是否与str的第一个字符相等,如果不等意味着pattern和str不匹配;如果相等,则还要看pattern的剩余部分与str的剩余部分是否匹配。
- 如果first是字符?,则str的第一个字符与它匹配,只需考虑pattern和str各自的剩余部分是否匹配即可。
- 如果first是字符*,则又分为两种情况考虑:
- 字符*只匹配0个字符,这意味着我们可以把*删除,然后考虑pattern剩下的部分是否与str匹配即可。
- 字符*匹配1个或1个以上的字符,这意味着str除了第一个字符以外,还可以有0个或0个以上的字符与*匹配。所以,可以把str的第一个字符删除,然后看它剩下的部分与pattern是否匹配即可。
综上所述,给出Python递归代码如下:
解决字符串匹配问题的递归程序
def match(s, p):
if len(p) == 0:
return len(s) == 0
first = p[0]
if first == '?':
return len(s) > 0 and match(s[1:], p[1:])
if first == '*':
return match(s, p[1:]) or len(s) > 0 and match(s[1:], p)
return len(s) > 0 and first == s[0] and match(s[1:], p[1:])
def _test_match(s, p, result):
print('%s, %s, %s, %s' % (s, p, result, match(s, p)))
if __name__ == '__main__':
_test_match('ababaab', 'a*b', True)
_test_match('ababaab', '*abab*', True)
_test_match('ababaab', 'a*a?b', True)
_test_match('ababaab', 'a*bb', False)
_test_match('ababaab', 'aabab*', False)
_test_match('ababaab', 'a*b?b', False)
_test_match()函数名以下划线打头,这意味着这是一个私有方法,不能被其他Python文件通过import或者from语句引用。
代码中使用了形如s[1:]的语法,表示取s从第二个字符开始直到最后的所有字符构成的新字符串。比如"abcde"[1:]的结果是"bcde",Python把字符串看成是字符的列表,所以可以用求子列表的办法获得子串,再如:"abcde"[2:4]和"abcde"[:4]的结果分别是"cd"和"abcd"。而在C、C++、Java中要取一个子串则必须调用内部函数。代码运行结果如下:
ababaab, a*b, True, True
ababaab, *abab*, True, True
ababaab, a*a?b, True, True
ababaab, a*bb, False, False
ababaab, aabab*, False, False
ababaab, a*b?b, False, False