亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解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樹和KMP 算法的結(jié)合,首先將多模式串建立一個(gè)Tire樹,然后結(jié)合KMP算法前綴與后綴匹配可以減少不必要比較的思想達(dá)到高效找到字符串中出現(xiàn)的匹配串。

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

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

工作過(guò)程

首先看一下AC自動(dòng)機(jī)的結(jié)構(gòu),從造型上看,跟我們之前講Tire樹幾乎一樣,但是多了紅色線條(這里因?yàn)楫嬐晏珌y,沒(méi)有畫完),這個(gè)紅色線條我們稱為失敗指針。其匹配規(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樹,結(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è)小寫字母,空間存在浪費(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)建字典樹

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

public?void?insertStr(char[]?chars) {
? ? ? ?//首先判斷首字符是否已經(jīng)在字典樹中,然后判斷第二字符,依次往下進(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樹中后,將最后一個(gè)字符所在節(jié)點(diǎn)標(biāo)志為結(jié)尾節(jié)點(diǎn)
? ? ? ?p.isTail?=?true;
? ? ? ?p.tailLength?=?chars.length;
? }

構(gòu)建失敗指針

從根節(jié)點(diǎn)開始層序遍歷樹結(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。以此類推,如果訪問(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);
? ? ? ? ? }
? ? ? }
? }

匹配

從首字符,字典樹從root節(jié)點(diǎn)開始進(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)的字典樹
? ? ? ?acAutomaton.init();
? ? ? ?//構(gòu)建Tire樹
? ? ? ?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)文章

  • springmvc fastjson 反序列化時(shí)間格式化方法(推薦)

    springmvc fastjson 反序列化時(shí)間格式化方法(推薦)

    下面小編就為大家?guī)?lái)一篇springmvc fastjson 反序列化時(shí)間格式化方法(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-04-04
  • 詳解Java中LinkedHashMap

    詳解Java中LinkedHashMap

    本文主要介紹了Java中LinkedHashMap的相關(guān)知識(shí),具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧
    2017-05-05
  • 淺談java并發(fā)之計(jì)數(shù)器CountDownLatch

    淺談java并發(fā)之計(jì)數(shù)器CountDownLatch

    CountDownLatch是通過(guò)一個(gè)計(jì)數(shù)器來(lái)實(shí)現(xiàn)的,當(dāng)我們?cè)趎ew 一個(gè)CountDownLatch對(duì)象的時(shí)候需要帶入該計(jì)數(shù)器值,該值就表示了線程的數(shù)量。下面我們來(lái)深入了解一下吧
    2019-06-06
  • Spring Boot 中使用cache緩存的方法

    Spring Boot 中使用cache緩存的方法

    Spring Cache是Spring針對(duì)Spring應(yīng)用,給出的一整套應(yīng)用緩存解決方案。下面小編給大家?guī)?lái)了Spring Boot 中使用cache緩存的方法,感興趣的朋友參考下吧
    2018-01-01
  • Java實(shí)現(xiàn)Fibonacci(斐波那契)取余的示例代碼

    Java實(shí)現(xiàn)Fibonacci(斐波那契)取余的示例代碼

    這篇文章主要介紹了Java實(shí)現(xiàn)Fibonacci取余的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • 關(guān)于Java中的 JSP 詳解

    關(guān)于Java中的 JSP 詳解

    JSP 代表 Java 服務(wù)器頁(yè)面。它是一種在應(yīng)用服務(wù)器端使用的編程工具。JSP 基本上用于支持平臺(tái)–獨(dú)立和動(dòng)態(tài)的方法來(lái)構(gòu)建 Web 依賴的應(yīng)用程序。JSP 頁(yè)面類似于 ASP 頁(yè)面,因?yàn)樗鼈兪窃诜?wù)器上編譯的,而不是在用戶的 Web 瀏覽器上進(jìn)行編譯。下面來(lái)看看文章的詳細(xì)介紹內(nèi)容
    2021-11-11
  • SpringBoot配置默認(rèn)HikariCP數(shù)據(jù)源

    SpringBoot配置默認(rèn)HikariCP數(shù)據(jù)源

    咱們開發(fā)項(xiàng)目的過(guò)程中用到很多的開源數(shù)據(jù)庫(kù)鏈接池,比如druid、c3p0、BoneCP等等,本文主要介紹了SpringBoot配置默認(rèn)HikariCP數(shù)據(jù)源,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-11-11
  • Java下載遠(yuǎn)程服務(wù)器文件到本地(基于http協(xié)議和ssh2協(xié)議)

    Java下載遠(yuǎn)程服務(wù)器文件到本地(基于http協(xié)議和ssh2協(xié)議)

    這篇文章主要介紹了Java下載遠(yuǎn)程服務(wù)器文件到本地的方法(基于http協(xié)議和ssh2協(xié)議),幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2021-01-01
  • JAVA中SpringBoot啟動(dòng)流程分析

    JAVA中SpringBoot啟動(dòng)流程分析

    這篇文章主要介紹了JAVA中SpringBoot啟動(dòng)流程分析的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • 深入解析Java中的編碼轉(zhuǎn)換以及編碼和解碼操作

    深入解析Java中的編碼轉(zhuǎn)換以及編碼和解碼操作

    這篇文章主要介紹了Java中的編碼轉(zhuǎn)換以及編碼和解碼操作,文中詳細(xì)解讀了編碼解碼的相關(guān)IO操作以及內(nèi)存使用方面的知識(shí),需要的朋友可以參考下
    2016-02-02

最新評(píng)論