AC自动机

用途

如果你想在一篇文章快速的查询含有的所有关键字,可以考虑AC自动机。

例子

  • 关键字:“bbbbb”、“aba”、“aab”、“aabba”、“bbaa”
  • 文章:aabbbbaa
    文章中包含有 “aab”、“bbaa”
    效率低的方式,你可以利用KMP算法,然后利用语句去一个关键字一个关键字,但效率差,AC自动机可以一次遍历即可匹配完成

思路

  1. 将关键字创建成字典树如下图:双圈的表示当前找一个关键字了,如果查找到当前节点,即可返回一个关键字
    在这里插入图片描述
  2. 赋fail指针(红色部分)
    2.1 0节点和0 节点的子节点 1,6 的fail指针指向0节点
    2.2 从第二层,程序的遍历上面的字典树,为每个节点的子节点赋值fail指针
    2.3 找到父类的fail指针对应的节点,是否含有和当前一样的节点的字符(建议直接先看例子
    - 如果Fail指针对应的节点,含有和当前一样的字符,则将改字符对应的节点赋给当前的fail指针
    - 否则,就再找父节点的fail指针对应节点的fail指针,重复上面的步骤

注意:如果fail指针对应的节点是关键字,则将当前的节点也标记为关键字

例子:比如给节点11 赋fail指针 (重点)

  1. 找到节点11的父节点10,节点10对应的fail指针是7;
  2. 但节点7不包含字符‘b’,则继续找节点7的fail指针对应的节点为1;
  3. 节点1包含字符‘b’,则将节点2赋给节点11的fail指针。
    在这里插入图片描述
  4. 查找
    从root开始,对应的文章aabbbbaa。走的步骤如下
    a: 节点6
    aa:节点9
    aab:节点10 ,找到一个关键字
    aabb:节点11
    aabbb::节点3(11后匹配失败,走向了节点2,节点2有b字符)
    aabbbb:节点4
    aabbbba:节点13(节点4匹配失败,跳转到节点3,节点3再次失败,跳转到节点2,匹配成功)
    aabbbbaa::节点14,找到另外一个关键字,匹配成功

代码

public class StreamChecker01 {

    public static void main(String[] args) {
        StreamChecker01 streamChecker01 = new StreamChecker01(new String[]{"bbbbb", "aba", "aab","aabba","bbaa"});
        String str = "aabbbbaa";
        for (int i = 0; i < str.length(); i++) {
            if(streamChecker01.query(str.charAt(i))){
                System.out.println("匹配成功一个关键字");
            }
        }

    }
    TrieNode root = new TrieNode();
    public StreamChecker01(String[] words) {
        // 创建字符字典
        for (String word:words) {
            TrieNode curNode = root;
            for (int i = 0; i < word.length(); i++) {
                curNode = curNode.add(word.charAt(i));
            }
            curNode.setEnd(true);
        }
        TrieNode[] child = root.child;
        root.fail = root;
        for (int i = 0; i < child.length; i++) {
            if(child[i] != null){
                child[i].fail = root;
                list.add(child[i]);
            }
        }

        while (list.size() > 0){
            TrieNode cur = list.pollFirst();
            setFail(cur); // 为他的孩子,设置fail
        }


    }

    TrieNode curNode = root;
    public boolean query(char letter) {

        while (curNode.getChild(letter) == null && curNode != root){
            curNode = curNode.fail;
        }
        if (curNode == root){
            if(curNode.getChild(letter) == null){ // 找到结尾
                return false;
            }
        }
        curNode = curNode.getChild(letter);


        return curNode.isEnd;

    }

    LinkedList<TrieNode> list = new LinkedList<>();
    private void setFail(TrieNode pNode){
        TrieNode[] child = pNode.child;
        if(child == null){
            return;
        }
        for (int i = 0; i < child.length; i++) {
            TrieNode curPNode = pNode;
            if(child[i] != null){
                TrieNode[] pfchild = curPNode.fail.child;

                while ((pfchild == null || pfchild[i] == null) && curPNode != root){ // 表示没有匹配上
                    curPNode = curPNode.fail;
                    pfchild = curPNode.fail.child;
                }
                if(curPNode == root){
                    if(root.child[i] != null){
                        child[i].fail = root.child[i];
                    } else {
                        child[i].fail = root;
                    }
                } else {
                    child[i].fail = pfchild[i];
                }
                if(child[i].fail.isEnd){
                    child[i].isEnd = true;
                }
//                setFail(child[i]);
                list.addLast(child[i]);
            }
        }
    }

}

class TrieNode{
    boolean isEnd = false; // 是否是一个字符串的结尾
    TrieNode fail = null; // 是否是一个字符串的结尾
    TrieNode[] child = null;
    // 添加功能
    public TrieNode add(char c){
        if(c == 'c'){
            System.out.println(c);
        }
        if(child == null){
            child = new TrieNode[26];
        }
        if(child[c - 'a'] == null){
            child[c - 'a'] = new TrieNode();
        }
        return child[c - 'a'];
    }
    // 返回子节点功能
    public TrieNode getChild(char c){
        if(child == null){
            return null;
        }
        return child[c - 'a'];
    }

    // 是否是一个字符串

    public boolean isEnd() {
        return isEnd;
    }

    public void setEnd(boolean end) {
        isEnd = end;
    }

}

练习题:字符流

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值