AC
一个文章,给一堆候选串,哪些串在文章中出现过,加到list里面,并返回。
本身是前缀树,但有其他内容,有fail指针,每一个节点多出一条指针。
KMP:给一个str和match,问match在这个字符串最早出现的位置。
字符串加入前缀树。
头结点的fail指针,人为指向空,一级的fail指针人为规定指向头,把字符串拿过来,加好前缀树之后再按照宽度优先遍历设置上fail指针。
两个逻辑:
1.自己的fail指针指向甲,如果我和我孩子是b的路,甲也有b的路,所以我孩子的fail指针就指向甲的b的路、
2.自己的fail指针指向空,孩子的fail指针指向头。
跳转
fail指针含义:指向前缀和自己后缀且最长的串。
fali指针多次跳转,只找最大可能性
所有可能性都没有丢,只是一次一次跳转,依次考虑
如何收集答案
最后一个节点描黑
前缀树种描黑表示一个字符串的结尾
找到a,在a附件围着fail指针转一圈,找找有没有描黑的点,没有就继续匹配
收集失败是fail指针跳到另一个路径,但附近绕一圈并不是跳转路径
一但被收集了就改白
当走abct第一条路,跳到头节点时,说明abc为前缀的路已经失败了,换第二条路
代码:
# 局限26个字母
class Node: # 节点类型
def __init__(self):
# 如果一个node,end为空,不是结尾
# 如果end不为空,表示这个点是某个字符串的结尾,end的值就是这个字符串
self.end = None
# 只有上面的end变量不为空的时候,endUse才有意义
# 表示,这个字符串之前有没有加入过答案
self.endUse = False # 如果字符串被收集过答案了,这个值就是true
self.fail = None
self.nexts = [None] * 26 # nexts数组存放的是Node节点类型的内容,python不需要指定类型,26只是小写字母
# 准备26条路
# 0....25 代表字符走过的路 a...z
# nexts[i] == null i方向的路不存在
# next[i] != null i方向的路存在
class ACAutomation:
# 只创建一个头节点
def __init__(self):
self.root = Node() # Node为头节点,root是指向头节点的指针,不需要头节点可写成 self.root = None
def insert(self,word): # 加入字符串
str = list(word) # 转成字符类型的数组
cur = self.root # 只要新加字符串,一定是在头节点加的,node指向头节点
index = 0
for i in range(len(str)): # 遍历字符串的每一个字符
index = ord(str[i]) - ord('a') # ord 字符串相减 减去a的asc值,就对应到某条路上了
if cur.nexts[index] is None: # 这个节点的方向上的路的底层节点是否为空,就是有没有这个方向上的路
cur.nexts[index] = Node() # 如果此方向节点没有,那么就新建一个节点
cur = cur.nexts[index] # 建立完新节点,node指针指向新节点
cur.end = word # 到结尾,end++
def build(self):
queue = []
queue.append(self.root) # 每个头结点都扔进去
cfail = None
while queue:
# 当前节点弹出
# 当前节点的所有后代加入到队列里去
# 当前节点给它的子去设置fail指针
# cur->父亲节点
cur = queue.pop(0) # 模拟队列
for i in range(26): # 所有的路
if cur.nexts[i]: # 如果某条路不为空,说明有相应的孩子
# cur.nexts[i] 是孩子
cur.nexts[i].fail = self.root # 先设置指向头结点
cfail = cur.fail
while cfail:
if cfail.nexts[i]: # cfail也有i方向上的路
cur.nexts[i].fail = cfail.nexts[i] # 我孩子的fail指针就指向它孩子
break
cfail = cfail.fail # 没有路就跳一段路
queue.append(cur.nexts[i])
# 查文章,给一个大文章,中了哪些候选串给你返回
def containWords(self,content):
str = list(content) # 转成字符数组
cur = self.root
follow = None
path = 0
ans = []
for i in range(len(str)): # 依次遍历文章中的字符
path = ord(str[i]) - ord('a') # 路
# 如果当前字符在这条路上没有配出来,就随着fail方向走向下条路径
# 如果当前cur节点,没有path的路,就通过fail,跳到别的前缀上去
while cur.nexts[path] is None and cur is not self.root:
cur = cur.fail # 跳到另一条前缀上去
# 1. 现在来到的路径(前缀),是可以继续匹配的
# 2. 现在来到的节点,就是前缀树的根节点,已经是头结点了
cur = cur.nexts[path] if cur.nexts[path] else self.root # 能走就继续走,没办法就回到头继续走
# 跳到什么位置,都收集一圈答案
follow = cur # cur不变的,所以follow是自己动
while follow is not self.root:
if follow.endUse:
break
# 不同的需求,在这一段之间修改
if follow.end:
ans.append(follow.end)
follow.endUse = True
# 不同的需求,在这一段之间修改
follow = follow.fail
return ans
ac = ACAutomation()
# 建立前缀树,子串必须一下全部给我
ac.insert("dhe")
ac.insert("he")
ac.insert("abcdheks")
# 设置fail指针
ac.build()
contains = ac.containWords("abcdhekskdjfafhasldkflskdjhwqaeruv")
for word in contains:
print(word)