最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Java中AC自動(dòng)機(jī)的原理與實(shí)現(xiàn)

 更新時(shí)間:2022年05月14日 09:16:03   作者:Carol  
AC自動(dòng)機(jī)是一個(gè)多模式匹配算法,在模式匹配領(lǐng)域被廣泛應(yīng)用。本文將詳細(xì)為大家介紹AC自動(dòng)機(jī)的原理與實(shí)現(xiàn)方法,感興趣的可以了解一下

簡(jiǎn)介

AC自動(dòng)機(jī)是一個(gè)多模式匹配算法,在模式匹配領(lǐng)域被廣泛應(yīng)用,舉一個(gè)經(jīng)典的例子,違禁詞查找并替換為***。AC自動(dòng)機(jī)其實(shí)是Trie樹(shù)和KMP 算法的結(jié)合,首先將多模式串建立一個(gè)Tire樹(shù),然后結(jié)合KMP算法前綴與后綴匹配可以減少不必要比較的思想達(dá)到高效找到字符串中出現(xiàn)的匹配串。

如果不知道什么是Tire樹(shù),可以先查看:詳解Java中字典樹(shù)(Trie樹(shù))的圖解與實(shí)現(xiàn)

如果不知道KMP算法,可以先查看:詳解Java中KMP算法的圖解與實(shí)現(xiàn)

工作過(guò)程

首先看一下AC自動(dòng)機(jī)的結(jié)構(gòu),從造型上看,跟我們之前講Tire樹(shù)幾乎一樣,但是多了紅色線條(這里因?yàn)楫?huà)完太亂,沒(méi)有畫(huà)完),這個(gè)紅色線條我們稱(chēng)為失敗指針。其匹配規(guī)則與KMP一致,后綴和前綴的匹配,不一樣的是,KMP是同一個(gè)模式串的前綴和后綴進(jìn)行匹配,而這里是當(dāng)前模式串的后綴,與另一個(gè)模式串的前綴進(jìn)行匹配。如果能夠匹配上,因?yàn)檫@兩個(gè)模式串的前綴一定不同(相同的前綴已經(jīng)聚合),將當(dāng)前已匹配的后綴拿出來(lái),比如abo,后綴為o,bo,abo,這時(shí)候我們?cè)僬伊硪粋€(gè)模式串的最長(zhǎng)前綴與當(dāng)前后綴匹配上(對(duì)應(yīng)kmp中的最長(zhǎng)前綴后綴子串),這時(shí)候我們可以找到out的o,則about中的o節(jié)點(diǎn)的失敗指針指向out的o節(jié)點(diǎn),這么做的意義就是主串可以一直往后比較,不用往前回溯(比如ab,之前匹配過(guò)能匹配上,但是到o是失敗了,其他匹配串不可能出現(xiàn)ab前綴,所以不必再匹配,一定失?。?。

構(gòu)建過(guò)程:建立一棵Tire樹(shù),結(jié)尾節(jié)點(diǎn)需要標(biāo)志當(dāng)前模式串的長(zhǎng)度,構(gòu)建失敗指針。

查找過(guò)程:從根節(jié)點(diǎn)出發(fā),查找當(dāng)前節(jié)點(diǎn)的孩子節(jié)點(diǎn)是否有與當(dāng)前字符匹配的字符,匹配則判斷是否為尾節(jié)點(diǎn),是則匹配成功,記錄。不是尾節(jié)點(diǎn)繼續(xù)匹配。如果孩子節(jié)點(diǎn)沒(méi)有與字符匹配的,則直接轉(zhuǎn)到失敗指針繼續(xù)操作。

數(shù)據(jù)結(jié)構(gòu)

一個(gè)value記錄當(dāng)前節(jié)點(diǎn)的值,childNode記錄當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)(假設(shè)僅出現(xiàn)26個(gè)小寫(xiě)字母,空間存在浪費(fèi),可使用hash表,有序二分,跳表進(jìn)行優(yōu)化),isTail標(biāo)志當(dāng)前節(jié)點(diǎn)是否為尾節(jié)點(diǎn),failNode表示失敗指針,即當(dāng)前節(jié)點(diǎn)的孩子節(jié)點(diǎn)與當(dāng)前字符均不匹配的時(shí)候,轉(zhuǎn)到哪個(gè)節(jié)點(diǎn)接續(xù)進(jìn)行匹配,tailLength,記錄模式串的長(zhǎng)度,方便快速拿出模式串的值(根據(jù)長(zhǎng)度以及匹配的index,從主串中拿)。

public?static?class?Node{
? ? ? ?//當(dāng)前節(jié)點(diǎn)值
? ? ? ?private?char?value;
? ? ? ?//當(dāng)前節(jié)點(diǎn)的孩子節(jié)點(diǎn)
? ? ? ?private?Node[]?childNode;
? ? ? ?//標(biāo)志當(dāng)前節(jié)點(diǎn)是否是某單詞結(jié)尾
? ? ? ?private?boolean?isTail;
? ? ? ?//失敗指針
? ? ? ?private?Node?failNode;
? ? ? ?//匹配串長(zhǎng)度,當(dāng)isTail==true時(shí),表示從root當(dāng)當(dāng)前位置是一個(gè)完整的匹配串,記錄這個(gè)匹配串的長(zhǎng)度,便于之后快速找到匹配串
? ? ? ?private?Integer?tailLength;
? ? ? ?public?Node(char?value) {
? ? ? ? ? ?this.value?=?value;
? ? ? }
? }

初始化

初始化一棵僅存在root的根節(jié)點(diǎn),root的失敗指針以及長(zhǎng)度均為null。

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

構(gòu)建字典樹(shù)

這個(gè)過(guò)程之前Tire樹(shù)中已經(jīng)講過(guò),不再贅述,唯一的區(qū)別是需要在結(jié)尾節(jié)點(diǎn)上標(biāo)志當(dāng)前模式串的長(zhǎng)度。

public?void?insertStr(char[]?chars) {
? ? ? ?//首先判斷首字符是否已經(jīng)在字典樹(shù)中,然后判斷第二字符,依次往下進(jìn)行判斷,找到第一個(gè)不存在的字符進(jìn)行插入孩節(jié)點(diǎn)
? ? ? ?Node?p?=?root;
? ? ? ?//表明當(dāng)前處理到了第幾個(gè)字符
? ? ? ?int?chIndex?=?0;
? ? ? ?while?(chIndex?<?chars.length) {
? ? ? ? ? ?while?(chIndex?<?chars.length?&&?null?!=?p) {
? ? ? ? ? ? ? ?Node[]?children?=?p.childNode;
? ? ? ? ? ? ? ?boolean?find?=?false;
? ? ? ? ? ? ? ?for?(Node?child?:?children) {
? ? ? ? ? ? ? ? ? ?if?(null?==?child) {continue;}
? ? ? ? ? ? ? ? ? ?if?(child.value?==?chars[chIndex]) {
? ? ? ? ? ? ? ? ? ? ? ?//當(dāng)前字符已經(jīng)存在,不需要再進(jìn)行存儲(chǔ)
? ? ? ? ? ? ? ? ? ? ? ?//從當(dāng)前節(jié)點(diǎn)出發(fā),存儲(chǔ)下一個(gè)字符
? ? ? ? ? ? ? ? ? ? ? ?p?=?child;
? ? ? ? ? ? ? ? ? ? ? ?++?chIndex;
? ? ? ? ? ? ? ? ? ? ? ?find?=?true;
? ? ? ? ? ? ? ? ? ? ? ?break;
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?if?(Boolean.TRUE.equals(find)) {
? ? ? ? ? ? ? ? ? ?//在孩子中找到了 不用再次存儲(chǔ)
? ? ? ? ? ? ? ? ? ?break;
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?//如果把孩子節(jié)點(diǎn)都找遍了,還沒(méi)有找到這個(gè)字符,直接將這個(gè)字符加入當(dāng)前節(jié)點(diǎn)的孩子節(jié)點(diǎn)
? ? ? ? ? ? ? ?Node?node?=?new?Node(chars[chIndex]);
? ? ? ? ? ? ? ?node.childNode?=?new?Node[26];
? ? ? ? ? ? ? ?children[chars[chIndex]?-?'a']?=?node;
? ? ? ? ? ? ? ?p?=?node;
? ? ? ? ? ? ? ?++?chIndex;
? ? ? ? ? }
? ? ? }
? ? ? ?//字符串中字符全部進(jìn)入tire樹(shù)中后,將最后一個(gè)字符所在節(jié)點(diǎn)標(biāo)志為結(jié)尾節(jié)點(diǎn)
? ? ? ?p.isTail?=?true;
? ? ? ?p.tailLength?=?chars.length;
? }

構(gòu)建失敗指針

從根節(jié)點(diǎn)開(kāi)始層序遍歷樹(shù)結(jié)構(gòu),構(gòu)建失敗指針。一個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)的失敗指針可以根據(jù)當(dāng)前節(jié)點(diǎn)的失敗指針得到,因?yàn)槲覀兪怯煤缶Y去與前綴匹配,所以如果我們采用層序遍歷,與當(dāng)前后綴的前綴一定在上層,已經(jīng)匹配出來(lái)了。那么當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)的失敗指針則可以根據(jù)當(dāng)前節(jié)點(diǎn)的失敗指針,查找失敗指針指向的節(jié)點(diǎn)的子節(jié)點(diǎn)是否有與當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)相等的,相等則這個(gè)子節(jié)點(diǎn)的失敗指針直接指向,不相等則繼續(xù)找,找不到直接指向root。根據(jù)上面的圖,我們來(lái)舉一個(gè)例子,我們已經(jīng)找到about中o節(jié)點(diǎn)(o1)的失敗指針是out中的o節(jié)點(diǎn)(o2),接下來(lái)我們?cè)趺凑襲(u1)的失敗指針呢?首先根據(jù)o1的失敗指針我們找到了o2,o2的子節(jié)點(diǎn)為u(u2),恰好與我們u1的值相等,此時(shí)我們就可以將u1的失敗指針指向u2。以此類(lèi)推,如果訪問(wèn)到最后為空(root的失敗指針為空),則直接將失敗指針指向root。

public?void?madeFailNext() {
? ? ? ?//層序遍歷,為了保證求解這個(gè)節(jié)點(diǎn)失敗指針的時(shí)候,它的父節(jié)點(diǎn)的失敗指針以及失敗指針的失敗指針。。。。已經(jīng)求得,可以完全根據(jù)這個(gè)找
? ? ? ?Deque<Node>?nodes?=?new?LinkedList<>();
? ? ? ?nodes.add(root);
? ? ? ?while?(!nodes.isEmpty()) {
? ? ? ? ? ?Node?current?=?nodes.poll();
? ? ? ? ? ?Node[]?children?=?current.childNode;
? ? ? ? ? ?for?(Node?child?:?children) {
? ? ? ? ? ? ? ?if?(null?==?child) {
? ? ? ? ? ? ? ? ? ?continue;
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?Node?failNode?=?current.failNode;
? ? ? ? ? ? ? ?while?(null?!=?failNode) {
? ? ? ? ? ? ? ? ? ?//找到當(dāng)前節(jié)點(diǎn)的失敗指針,查看失敗指針子節(jié)點(diǎn)是否有==
? ? ? ? ? ? ? ? ? ?Node[]?failChildren?=?failNode.childNode;
? ? ? ? ? ? ? ? ? ?Node?node?=?failChildren[child.value?-?'a'];
? ? ? ? ? ? ? ? ? ?if?(null?==?node) {
? ? ? ? ? ? ? ? ? ? ? ?//找當(dāng)前指針的下一個(gè)指針
? ? ? ? ? ? ? ? ? ? ? ?failNode?=?failNode.failNode;
? ? ? ? ? ? ? ? ? ? ? ?continue;
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ?//已經(jīng)找到匹配的
? ? ? ? ? ? ? ? ? ?//將失敗指針指向node
? ? ? ? ? ? ? ? ? ?child.failNode?=?node;
? ? ? ? ? ? ? ? ? ?break;
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?//如果找完還沒(méi)有找到,指向root
? ? ? ? ? ? ? ?if?(null?==?failNode) {
? ? ? ? ? ? ? ? ? ?child.failNode?=?root;
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?nodes.add(child);
? ? ? ? ? }
? ? ? }
? }

匹配

從首字符,字典樹(shù)從root節(jié)點(diǎn)開(kāi)始進(jìn)行匹配,如果字符與節(jié)點(diǎn)值匹配,則判斷是否為尾字符,如果是匹配上一個(gè)違禁詞,記錄下來(lái),如果不匹配則轉(zhuǎn)移到失敗指針繼續(xù)進(jìn)行匹配。

/**
? ??* 匹配出str中所有出現(xiàn)的關(guān)鍵詞
? ??* @param str
? ??* @return
? ??*/
? ?public?List<String>?match(String?str) {
? ? ? ?//遍歷當(dāng)前子串串,從根節(jié)點(diǎn)出發(fā),如果匹配就一直往下進(jìn)行匹配,同時(shí)需要看匹配的節(jié)點(diǎn)是否為結(jié)尾節(jié)點(diǎn),如果是,匹配上一個(gè)
? ? ? ?//如果不匹配則通過(guò)失敗指針轉(zhuǎn)移到下一個(gè)節(jié)點(diǎn)
? ? ? ?this.dfs(root,?0,?str);
? ? ? ?return?machStr;
? }

? ?//abcdeasdabcebcd
? ?List<String>?machStr?=?new?ArrayList<>();
? ?private?void?dfs(Node?node,?int?chIndex,?String?chars) {
? ? ? ?if?(chIndex?>=?chars.length()) {
? ? ? ? ? ?return;
? ? ? }
? ? ? ?//從將當(dāng)前字符與當(dāng)前node的孩子節(jié)點(diǎn)進(jìn)行匹配,如果當(dāng)前字符與node的孩子節(jié)點(diǎn).value匹配,判斷當(dāng)前字符是否為尾節(jié)點(diǎn),是,則記錄,匹配到了一個(gè)
? ? ? ?//繼續(xù)匹配(子節(jié)點(diǎn),與下一個(gè)元素進(jìn)行匹配)
? ? ? ?//如果不匹配,則轉(zhuǎn)到失敗指針
? ? ? ?Node[]?children?=?node.childNode;
? ? ? ?Node?child?=?children[chars.charAt(chIndex)?-?'a'];
? ? ? ?if?(null?==?child) {
? ? ? ? ? ?//不匹配,轉(zhuǎn)到失敗指針
? ? ? ? ? ?//如果當(dāng)前node==root,從root匹配,root的失敗指針是null
? ? ? ? ? ?if?(node?==?root) {
? ? ? ? ? ? ? ?dfs(root,?++?chIndex,?chars);
? ? ? ? ? }?else?{
? ? ? ? ? ? ? ?dfs(node.failNode,?chIndex,?chars);
? ? ? ? ? }
? ? ? }?else?{
? ? ? ? ? ?//匹配到了
? ? ? ? ? ?if?(child.isTail) {
? ? ? ? ? ? ? ?//并且是結(jié)尾節(jié)點(diǎn),取從child.value到child.tailLength的字符
? ? ? ? ? ? ? ?machStr.add(chars.substring(chIndex?-?child.tailLength??+?1,?chIndex?+?1));
? ? ? ? ? }
? ? ? ? ? ?dfs(child,?++?chIndex,?chars);
? ? ? }

? }

執(zhí)行結(jié)果

public?static?void?main(String[]?args) {
? ? ? ?ACAutomaton?acAutomaton?=?new?ACAutomaton();
? ? ? ?//初始化一個(gè)僅有根節(jié)點(diǎn)的字典樹(shù)
? ? ? ?acAutomaton.init();
? ? ? ?//構(gòu)建Tire樹(shù)
? ? ? ?acAutomaton.insertStr("out".toCharArray());
? ? ? ?acAutomaton.insertStr("about".toCharArray());
? ? ? ?acAutomaton.insertStr("act".toCharArray());
? ? ? ?//構(gòu)建失敗指針
? ? ? ?acAutomaton.madeFailNext();
? ? ? ?System.out.println("ces");
? ? ? ?//匹配
? ? ? ?List<String>?result?=?acAutomaton.match("abcdeasactdaboutcebcd");
? }

到此這篇關(guān)于詳解Java中AC自動(dòng)機(jī)的原理與實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Java AC自動(dòng)機(jī)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring Boot與Kotlin定時(shí)任務(wù)的示例(Scheduling Tasks)

    Spring Boot與Kotlin定時(shí)任務(wù)的示例(Scheduling Tasks)

    這篇文章主要介紹了Spring Boot與Kotlin定時(shí)任務(wù)的示例(Scheduling Tasks),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • Java設(shè)計(jì)模式中的橋接模式

    Java設(shè)計(jì)模式中的橋接模式

    這篇文章主要介紹了Java設(shè)計(jì)模式中的橋接模式,其是一種結(jié)構(gòu)型設(shè)計(jì)模式,是指將實(shí)現(xiàn)與抽象放在兩個(gè)不同的類(lèi)層次中,使兩個(gè)層次可以獨(dú)立改變
    2022-07-07
  • Java實(shí)現(xiàn)添加條形碼到PDF表格的方法詳解

    Java實(shí)現(xiàn)添加條形碼到PDF表格的方法詳解

    條碼的應(yīng)用已深入生活和工作的方方面面。本文以操作PDF文件為例,介紹如何利用Java語(yǔ)言在編輯表格時(shí),向單元格中添加條形碼,感興趣的可以學(xué)習(xí)一下
    2022-06-06
  • SpringBoot統(tǒng)一處理功能實(shí)現(xiàn)的全過(guò)程

    SpringBoot統(tǒng)一處理功能實(shí)現(xiàn)的全過(guò)程

    最近在做項(xiàng)目時(shí)需要對(duì)異常進(jìn)行全局統(tǒng)一處理,主要是一些分類(lèi)入庫(kù)以及記錄日志等,下面這篇文章主要給大家介紹了關(guān)于SpringBoot統(tǒng)一功能處理實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • Java替換字符串replace和replaceAll方法舉例詳解

    Java替換字符串replace和replaceAll方法舉例詳解

    這篇文章主要介紹了Java中替換字符串的幾種方法,包括String類(lèi)的replace()、replaceAll()、replaceFirst()方法,以及StringBuilder和StringBuffer類(lèi)的replace()方法,還提到了一些第三方庫(kù),如Hutool,它們提供了更豐富的字符串處理功能,需要的朋友可以參考下
    2025-02-02
  • JVM 心得分享(加載 鏈接 初始化)

    JVM 心得分享(加載 鏈接 初始化)

    下面小編就為大家?guī)?lái)一篇JVM 心得分享(加載 鏈接 初始化)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-10-10
  • Java批量操作文件系統(tǒng)的實(shí)現(xiàn)示例

    Java批量操作文件系統(tǒng)的實(shí)現(xiàn)示例

    文件上傳和下載是java web中常見(jiàn)的操作,本文主要介紹了Java批量操作文件系統(tǒng)的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-03-03
  • Java中EasyPoi導(dǎo)出復(fù)雜合并單元格的方法

    Java中EasyPoi導(dǎo)出復(fù)雜合并單元格的方法

    這篇文章主要介紹了Java中EasyPoi導(dǎo)出復(fù)雜合并單元格的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • IDEA插件指南之Mybatis?log插件安裝及使用方法

    IDEA插件指南之Mybatis?log插件安裝及使用方法

    這篇文章主要給大家介紹了關(guān)于IDEA插件指南之Mybatis?log插件安裝及使用的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-02-02
  • Mybatis模糊查詢之三種定義參數(shù)方法和聚合查詢、主鍵回填實(shí)現(xiàn)方法

    Mybatis模糊查詢之三種定義參數(shù)方法和聚合查詢、主鍵回填實(shí)現(xiàn)方法

    這篇文章主要介紹了Mybatis模糊查詢之三種定義參數(shù)方法和聚合查詢、主鍵回填實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03

最新評(píng)論

剑河县| 淅川县| 苍梧县| 巴林右旗| 富裕县| 洮南市| 临沂市| 宣武区| 盐津县| 兖州市| 金湖县| 且末县| 南皮县| 茂名市| 五寨县| 江山市| 定日县| 海安县| 商城县| 唐海县| 金山区| 磐石市| 玛曲县| 沧州市| 佛教| 德令哈市| 虹口区| 忻州市| 和田县| 葫芦岛市| 吕梁市| 周至县| 镇巴县| 彭泽县| 杭锦旗| 灵山县| 万荣县| 天等县| 略阳县| 运城市| 高阳县|