python_21_ACM自动机

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值