AC自动机:Tire树+KMP

简介

AC自动机是一个多模式匹配算法,在模式匹配领域被广泛应用,举一个经典的例子,违禁词查找并替换为***。AC自动机其实是Trie树和KMP 算法的结合,首先将多模式串建立一个Tire树,然后结合KMP算法前缀与后缀匹配可以减少不必要比较的思想达到高效找到字符串中出现的匹配串。 如果不知道什么是Tire树,可以先查看:图解Tire树+代码实现 如果不知道KMP算法,可以先查看:图解KMP字符串匹配算法

工作过程

首先看一下AC自动机的结构,从造型上看,跟我们之前讲Tire树几乎一样,但是多了红色线条(这里因为画完太乱,没有画完),这个红色线条我们称为失败指针。其匹配规则与KMP一致,后缀和前缀的匹配,不一样的是,KMP是同一个模式串的前缀和后缀进行匹配,而这里是当前模式串的后缀,与另一个模式串的前缀进行匹配。如果能够匹配上,因为这两个模式串的前缀一定不同(相同的前缀已经聚合),将当前已匹配的后缀拿出来,比如abo,后缀为o,bo,abo,这时候我们再找另一个模式串的最长前缀与当前后缀匹配上(对应kmp中的最长前缀后缀子串),这时候我们可以找到out的o,则about中的o节点的失败指针指向out的o节点,这么做的意义就是主串可以一直往后比较,不用往前回溯(比如ab,之前匹配过能匹配上,但是到o是失败了,其他匹配串不可能出现ab前缀,所以不必再匹配,一定失败)。 构建过程:建立一棵Tire树,结尾节点需要标志当前模式串的长度,构建失败指针。 查找过程:从根节点出发,查找当前节点的孩子节点是否有与当前字符匹配的字符,匹配则判断是否为尾节点,是则匹配成功,记录。不是尾节点继续匹配。如果孩子节点没有与字符匹配的,则直接转到失败指针继续操作。

AC自动机:Tire树+KMP

数据结构

一个value记录当前节点的值,childNode记录当前节点的子节点(假设仅出现26个小写字母,空间存在浪费,可使用hash表,有序二分,跳表进行优化),isTail标志当前节点是否为尾节点,failNode表示失败指针,即当前节点的孩子节点与当前字符均不匹配的时候,转到哪个节点接续进行匹配,tailLength,记录模式串的长度,方便快速拿出模式串的值(根据长度以及匹配的index,从主串中拿)。

public static class Node{        //当前节点值        private char value;        //当前节点的孩子节点        private Node[] childNode;        //标志当前节点是否是某单词结尾        private boolean isTail;        //失败指针        private Node failNode;        //匹配串长度,当isTail==true时,表示从root当当前位置是一个完整的匹配串,记录这个匹配串的长度,便于之后快速找到匹配串        private Integer tailLength;        public Node(char value) {            this.value = value;        }    }

初始化

初始化一棵仅存在root的根节点,root的失败指针以及长度均为null。

Node root;    public void init() {        root = new Node('\0');        root.childNode = new Node[26];    }

构建字典树

这个过程之前Tire树中已经讲过,不再赘述,唯一的区别是需要在结尾节点上标志当前模式串的长度。

public void insertStr(char[] chars) { &#xA0; &#xA0; &#xA0; &#xA0;//&#x9996;&#x5148;&#x5224;&#x65AD;&#x9996;&#x5B57;&#x7B26;&#x662F;&#x5426;&#x5DF2;&#x7ECF;&#x5728;&#x5B57;&#x5178;&#x6811;&#x4E2D;&#xFF0C;&#x7136;&#x540E;&#x5224;&#x65AD;&#x7B2C;&#x4E8C;&#x5B57;&#x7B26;&#xFF0C;&#x4F9D;&#x6B21;&#x5F80;&#x4E0B;&#x8FDB;&#x884C;&#x5224;&#x65AD;&#xFF0C;&#x627E;&#x5230;&#x7B2C;&#x4E00;&#x4E2A;&#x4E0D;&#x5B58;&#x5728;&#x7684;&#x5B57;&#x7B26;&#x8FDB;&#x884C;&#x63D2;&#x5165;&#x5B69;&#x8282;&#x70B9; &#xA0; &#xA0; &#xA0; &#xA0;Node p = root; &#xA0; &#xA0; &#xA0; &#xA0;//&#x8868;&#x660E;&#x5F53;&#x524D;&#x5904;&#x7406;&#x5230;&#x4E86;&#x7B2C;&#x51E0;&#x4E2A;&#x5B57;&#x7B26; &#xA0; &#xA0; &#xA0; &#xA0;int chIndex = 0; &#xA0; &#xA0; &#xA0; &#xA0;while (chIndex < chars.length) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;while (chIndex < chars.length && null != p) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;Node[] children = p.childNode; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;boolean find = false; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;for (Node child : children) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;if (null == child) {continue;} &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;if (child.value == chars[chIndex]) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5F53;&#x524D;&#x5B57;&#x7B26;&#x5DF2;&#x7ECF;&#x5B58;&#x5728;&#xFF0C;&#x4E0D;&#x9700;&#x8981;&#x518D;&#x8FDB;&#x884C;&#x5B58;&#x50A8; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x4ECE;&#x5F53;&#x524D;&#x8282;&#x70B9;&#x51FA;&#x53D1;&#xFF0C;&#x5B58;&#x50A8;&#x4E0B;&#x4E00;&#x4E2A;&#x5B57;&#x7B26; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;p = child; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;++ chIndex; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;find = true; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;break; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;if (Boolean.TRUE.equals(find)) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5728;&#x5B69;&#x5B50;&#x4E2D;&#x627E;&#x5230;&#x4E86; &#x4E0D;&#x7528;&#x518D;&#x6B21;&#x5B58;&#x50A8; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;break; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5982;&#x679C;&#x628A;&#x5B69;&#x5B50;&#x8282;&#x70B9;&#x90FD;&#x627E;&#x904D;&#x4E86;&#xFF0C;&#x8FD8;&#x6CA1;&#x6709;&#x627E;&#x5230;&#x8FD9;&#x4E2A;&#x5B57;&#x7B26;&#xFF0C;&#x76F4;&#x63A5;&#x5C06;&#x8FD9;&#x4E2A;&#x5B57;&#x7B26;&#x52A0;&#x5165;&#x5F53;&#x524D;&#x8282;&#x70B9;&#x7684;&#x5B69;&#x5B50;&#x8282;&#x70B9; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;Node node = new Node(chars[chIndex]); &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;node.childNode = new Node[26]; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;children[chars[chIndex] - 'a'] = node; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;p = node; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;++ chIndex; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0;//&#x5B57;&#x7B26;&#x4E32;&#x4E2D;&#x5B57;&#x7B26;&#x5168;&#x90E8;&#x8FDB;&#x5165;tire&#x6811;&#x4E2D;&#x540E;&#xFF0C;&#x5C06;&#x6700;&#x540E;&#x4E00;&#x4E2A;&#x5B57;&#x7B26;&#x6240;&#x5728;&#x8282;&#x70B9;&#x6807;&#x5FD7;&#x4E3A;&#x7ED3;&#x5C3E;&#x8282;&#x70B9; &#xA0; &#xA0; &#xA0; &#xA0;p.isTail = true; &#xA0; &#xA0; &#xA0; &#xA0;p.tailLength = chars.length; &#xA0;  }

构建失败指针

从根节点开始层序遍历树结构,构建失败指针。一个节点的子节点的失败指针可以根据当前节点的失败指针得到,因为我们是用后缀去与前缀匹配,所以如果我们采用层序遍历,与当前后缀的前缀一定在上层,已经匹配出来了。那么当前节点的子节点的失败指针则可以根据当前节点的失败指针,查找失败指针指向的节点的子节点是否有与当前节点的子节点相等的,相等则这个子节点的失败指针直接指向,不相等则继续找,找不到直接指向root。根据上面的图,我们来举一个例子,我们已经找到about中o节点(o1)的失败指针是out中的o节点(o2),接下来我们怎么找u(u1)的失败指针呢?首先根据o1的失败指针我们找到了o2,o2的子节点为u(u2),恰好与我们u1的值相等,此时我们就可以将u1的失败指针指向u2。以此类推,如果访问到最后为空(root的失败指针为空),则直接将失败指针指向root。

public void madeFailNext() { &#xA0; &#xA0; &#xA0; &#xA0;//&#x5C42;&#x5E8F;&#x904D;&#x5386;&#xFF0C;&#x4E3A;&#x4E86;&#x4FDD;&#x8BC1;&#x6C42;&#x89E3;&#x8FD9;&#x4E2A;&#x8282;&#x70B9;&#x5931;&#x8D25;&#x6307;&#x9488;&#x7684;&#x65F6;&#x5019;&#xFF0C;&#x5B83;&#x7684;&#x7236;&#x8282;&#x70B9;&#x7684;&#x5931;&#x8D25;&#x6307;&#x9488;&#x4EE5;&#x53CA;&#x5931;&#x8D25;&#x6307;&#x9488;&#x7684;&#x5931;&#x8D25;&#x6307;&#x9488;&#x3002;&#x3002;&#x3002;&#x3002;&#x5DF2;&#x7ECF;&#x6C42;&#x5F97;&#xFF0C;&#x53EF;&#x4EE5;&#x5B8C;&#x5168;&#x6839;&#x636E;&#x8FD9;&#x4E2A;&#x627E; &#xA0; &#xA0; &#xA0; &#xA0;Deque<node> nodes = new LinkedList<>(); &#xA0; &#xA0; &#xA0; &#xA0;nodes.add(root); &#xA0; &#xA0; &#xA0; &#xA0;while (!nodes.isEmpty()) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;Node current = nodes.poll(); &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;Node[] children = current.childNode; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;for (Node child : children) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;if (null == child) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;continue; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;Node failNode = current.failNode; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;while (null != failNode) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x627E;&#x5230;&#x5F53;&#x524D;&#x8282;&#x70B9;&#x7684;&#x5931;&#x8D25;&#x6307;&#x9488;&#xFF0C;&#x67E5;&#x770B;&#x5931;&#x8D25;&#x6307;&#x9488;&#x5B50;&#x8282;&#x70B9;&#x662F;&#x5426;&#x6709;== &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;Node[] failChildren = failNode.childNode; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;Node node = failChildren[child.value - 'a']; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;if (null == node) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x627E;&#x5F53;&#x524D;&#x6307;&#x9488;&#x7684;&#x4E0B;&#x4E00;&#x4E2A;&#x6307;&#x9488; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;failNode = failNode.failNode; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;continue; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5DF2;&#x7ECF;&#x627E;&#x5230;&#x5339;&#x914D;&#x7684; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5C06;&#x5931;&#x8D25;&#x6307;&#x9488;&#x6307;&#x5411;node &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;child.failNode = node; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;break; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5982;&#x679C;&#x627E;&#x5B8C;&#x8FD8;&#x6CA1;&#x6709;&#x627E;&#x5230;&#xFF0C;&#x6307;&#x5411;root &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;if (null == failNode) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;child.failNode = root; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;nodes.add(child); &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0;  } &#xA0;  }
</node>

匹配

从首字符,字典树从root节点开始进行匹配,如果字符与节点值匹配,则判断是否为尾字符,如果是匹配上一个违禁词,记录下来,如果不匹配则转移到失败指针继续进行匹配。

 &#xA0; &#xA0;/** &#xA0; &#xA0; * &#x5339;&#x914D;&#x51FA;str&#x4E2D;&#x6240;&#x6709;&#x51FA;&#x73B0;&#x7684;&#x5173;&#x952E;&#x8BCD; &#xA0; &#xA0; * @param str &#xA0; &#xA0; * @return &#xA0; &#xA0; */ &#xA0; &#xA0;public List<string> match(String str) { &#xA0; &#xA0; &#xA0; &#xA0;//&#x904D;&#x5386;&#x5F53;&#x524D;&#x5B50;&#x4E32;&#x4E32;&#xFF0C;&#x4ECE;&#x6839;&#x8282;&#x70B9;&#x51FA;&#x53D1;&#xFF0C;&#x5982;&#x679C;&#x5339;&#x914D;&#x5C31;&#x4E00;&#x76F4;&#x5F80;&#x4E0B;&#x8FDB;&#x884C;&#x5339;&#x914D;&#xFF0C;&#x540C;&#x65F6;&#x9700;&#x8981;&#x770B;&#x5339;&#x914D;&#x7684;&#x8282;&#x70B9;&#x662F;&#x5426;&#x4E3A;&#x7ED3;&#x5C3E;&#x8282;&#x70B9;&#xFF0C;&#x5982;&#x679C;&#x662F;&#xFF0C;&#x5339;&#x914D;&#x4E0A;&#x4E00;&#x4E2A; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5982;&#x679C;&#x4E0D;&#x5339;&#x914D;&#x5219;&#x901A;&#x8FC7;&#x5931;&#x8D25;&#x6307;&#x9488;&#x8F6C;&#x79FB;&#x5230;&#x4E0B;&#x4E00;&#x4E2A;&#x8282;&#x70B9; &#xA0; &#xA0; &#xA0; &#xA0;this.dfs(root, 0, str); &#xA0; &#xA0; &#xA0; &#xA0;return machStr; &#xA0;  } &#xA0; &#xA0;//abcdeasdabcebcd &#xA0; &#xA0;List<string> machStr = new ArrayList<>(); &#xA0; &#xA0;private void dfs(Node node, int chIndex, String chars) { &#xA0; &#xA0; &#xA0; &#xA0;if (chIndex >= chars.length()) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;return; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0;//&#x4ECE;&#x5C06;&#x5F53;&#x524D;&#x5B57;&#x7B26;&#x4E0E;&#x5F53;&#x524D;node&#x7684;&#x5B69;&#x5B50;&#x8282;&#x70B9;&#x8FDB;&#x884C;&#x5339;&#x914D;&#xFF0C;&#x5982;&#x679C;&#x5F53;&#x524D;&#x5B57;&#x7B26;&#x4E0E;node&#x7684;&#x5B69;&#x5B50;&#x8282;&#x70B9;.value&#x5339;&#x914D;&#xFF0C;&#x5224;&#x65AD;&#x5F53;&#x524D;&#x5B57;&#x7B26;&#x662F;&#x5426;&#x4E3A;&#x5C3E;&#x8282;&#x70B9;&#xFF0C;&#x662F;&#xFF0C;&#x5219;&#x8BB0;&#x5F55;&#xFF0C;&#x5339;&#x914D;&#x5230;&#x4E86;&#x4E00;&#x4E2A; &#xA0; &#xA0; &#xA0; &#xA0;//&#x7EE7;&#x7EED;&#x5339;&#x914D;&#xFF08;&#x5B50;&#x8282;&#x70B9;&#xFF0C;&#x4E0E;&#x4E0B;&#x4E00;&#x4E2A;&#x5143;&#x7D20;&#x8FDB;&#x884C;&#x5339;&#x914D;&#xFF09; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5982;&#x679C;&#x4E0D;&#x5339;&#x914D;&#xFF0C;&#x5219;&#x8F6C;&#x5230;&#x5931;&#x8D25;&#x6307;&#x9488; &#xA0; &#xA0; &#xA0; &#xA0;Node[] children = node.childNode; &#xA0; &#xA0; &#xA0; &#xA0;Node child = children[chars.charAt(chIndex) - 'a']; &#xA0; &#xA0; &#xA0; &#xA0;if (null == child) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x4E0D;&#x5339;&#x914D;&#xFF0C;&#x8F6C;&#x5230;&#x5931;&#x8D25;&#x6307;&#x9488; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5982;&#x679C;&#x5F53;&#x524D;node==root&#xFF0C;&#x4ECE;root&#x5339;&#x914D;&#xFF0C;root&#x7684;&#x5931;&#x8D25;&#x6307;&#x9488;&#x662F;null &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;if (node == root) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;dfs(root, ++ chIndex, chars); &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } else { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;dfs(node.failNode, chIndex, chars); &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0;  } else { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5339;&#x914D;&#x5230;&#x4E86; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;if (child.isTail) { &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;//&#x5E76;&#x4E14;&#x662F;&#x7ED3;&#x5C3E;&#x8282;&#x70B9;&#xFF0C;&#x53D6;&#x4ECE;child.value&#x5230;child.tailLength&#x7684;&#x5B57;&#x7B26; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;machStr.add(chars.substring(chIndex - child.tailLength &#xA0;+ 1, chIndex + 1)); &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;  } &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;dfs(child, ++ chIndex, chars); &#xA0; &#xA0; &#xA0;  } &#xA0;  }
</string></string>

执行结果

public static void main(String[] args) { &#xA0; &#xA0; &#xA0; &#xA0;ACAutomaton acAutomaton = new ACAutomaton(); &#xA0; &#xA0; &#xA0; &#xA0;//&#x521D;&#x59CB;&#x5316;&#x4E00;&#x4E2A;&#x4EC5;&#x6709;&#x6839;&#x8282;&#x70B9;&#x7684;&#x5B57;&#x5178;&#x6811; &#xA0; &#xA0; &#xA0; &#xA0;acAutomaton.init(); &#xA0; &#xA0; &#xA0; &#xA0;//&#x6784;&#x5EFA;Tire&#x6811; &#xA0; &#xA0; &#xA0; &#xA0;acAutomaton.insertStr("out".toCharArray()); &#xA0; &#xA0; &#xA0; &#xA0;acAutomaton.insertStr("about".toCharArray()); &#xA0; &#xA0; &#xA0; &#xA0;acAutomaton.insertStr("act".toCharArray()); &#xA0; &#xA0; &#xA0; &#xA0;//&#x6784;&#x5EFA;&#x5931;&#x8D25;&#x6307;&#x9488; &#xA0; &#xA0; &#xA0; &#xA0;acAutomaton.madeFailNext(); &#xA0; &#xA0; &#xA0; &#xA0;System.out.println("ces"); &#xA0; &#xA0; &#xA0; &#xA0;//&#x5339;&#x914D; &#xA0; &#xA0; &#xA0; &#xA0;List<string> result = acAutomaton.match("abcdeasactdaboutcebcd"); &#xA0;  }
</string>

AC自动机:Tire树+KMP

Original: https://www.cnblogs.com/lin0/p/16268495.html
Author: Carol淋
Title: AC自动机:Tire树+KMP

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/593637/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 多线程、Handler机制、ThreadLocal

    Thread 线程状态:新建(new),就绪(start),运行(run),阻塞,死亡 start 方法内部调用了 run 方法,start 会开启线程,run 只是内部方法; s…

    Java 2023年6月7日
    063
  • Optional 常用方法总结

    转载请注明出处: Optional 类是 JAVA 8 提供的判断程序是否为空提供的包装工具类;可以减少代码中的 是否为空的判断,以及减少 NullPointerException…

    Java 2023年6月8日
    088
  • DataTable 将一列转为List

    c# linq用起来特方便,因此我们习惯性的用list来操作。 这里我们将 DataTable 一列转为List: List homeworkIdList = (from r in…

    Java 2023年6月9日
    076
  • MongoDb在windows10下的安装、创建用户和数据库

    1.mongodb下载地址https://www.mongodb.com/download-center#community 2.安装 3.在D:\MongoDB目录下创建db和l…

    Java 2023年6月16日
    079
  • Java核心技术-内部类(下)

    Day8 局部内部类 优势: 对外部完全隐蔽 可以访问外部类字段和局部变量 package cn.gyk; import javax.swing.*; import java.aw…

    Java 2023年6月5日
    088
  • 写出个灵活的系统竟然可以如此简单!小白也能写出高级的Java业务!

    一 最近正好公司里有个需求,一个短信业务接了多个第三方供应商,某些业务需要查询第三方供应商剩余的短信包数量去选择剩余量最多的渠道去批量发送。有些业务是指定了某个短信供应商,有些场景…

    Java 2023年6月8日
    097
  • jar包解压后怎么还原成jar包

    在需要打包的文件夹中,按住shift+鼠标邮件,点击在此处运行命令窗口(win10是PowerShell),输入jar cvf xxx.jar * xxx.jar是你要打包成的ja…

    Java 2023年6月16日
    0110
  • Spring Boot 入门系列(二十二)使用Swagger2构建 RESTful API文档

    前面介绍了如何Spring Boot 快速打造Restful API 接口,也介绍了如何优雅的实现 Api 版本控制,不清楚的可以看我之前的文章:https://www.cnblo…

    Java 2023年5月30日
    0260
  • 多线程_基础

    一.一个Java程序最少开几个线程? 3个:主线程;gc线程;异常处理线程 二.线程的生命周期以及状态? 阻塞的分类: 等待阻塞:执行wait(),需要notify()/notif…

    Java 2023年6月7日
    093
  • Java实现AES加密

    生成秘钥简单粗暴 这边AES秘钥默认为128位,获得无政策权限后可为192或256,因此对应字符为16位,直接生成16位的秘钥 import java.io.Unsupported…

    Java 2023年6月5日
    0114
  • 《回炉重造》——泛型

    泛型 前言 以前学习到「泛型」的时候,只是浅浅的知道可以限制类型,并没有更深入理解,可以说基础的也没理解到位,只是浮于表面,所以,现在回炉重造,重学泛型!打好基础! 什么是泛型? …

    Java 2023年6月10日
    091
  • 为Eclipse创建Ant的build.xml文件编辑自动提示

    JavaEE版的Eclipse自动集成了Ant插件,但是,并没有提供Ant的DTD文件。原因在Apache官网的Ant项目下的FAQ中有解释,原文如下: **Is there a …

    Java 2023年6月8日
    058
  • centos yum安装ffmpeg

    一、安装前都需要先安装epel扩展源 yum -y install epel-release 二、对于6,安装yum源之后直接安装即可: su -c ‘yum loca…

    Java 2023年6月8日
    093
  • 记一次关于springboot的netty版本冲突问题

    冲突的地放其实很多,大概都是类似,找不到哪个方法了: 类似于: Error starting ApplicationContext. To display the conditio…

    Java 2023年5月30日
    079
  • SpringMVC 解析(五)URI链接处理

    URI在网络请求中必不可少,Spring提供了一些工具类用于解析或者生成URL,比如根据参数生成GET的URL等。本文会对Spring MVC中的URI工具进行介绍,本文主要参考S…

    Java 2023年6月8日
    081
  • 机器学习(5)特征值的处理总结和缺失值的处理

    数值型数据处理的方式:1,归一化 2,标准化 3,缺失值处理(pandas处理) 类别型数据:on-hot编码 时间类型数据:时间切分 posted @2018-11-19 16:…

    Java 2023年6月8日
    095
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球