最近一直在优化项目中字符串匹配的问题,于是就想起了自动机,之前也看过一些文章,一直没有实现,现在项目中要用,然后又看了一些关于AC自动机的文章,这里实现了一个简单的AC自动机的小接口,我是实现自动机状态结构采用了trie树,实现起来简单一些,但在一定程度上造成空间复杂度的增加,欢迎大家纠错和一起交流经验,下面是代码。

 #include <stdio.h>
#include <stdlib.h>
#include <string.h> #define CHAR_EXTERN 26 //trie的next指针的最大数目,全字符时应设置为256然后和0xff进行&运算 typedef struct ac_node ac_node;
typedef struct ac_node* ac_node_p; #define QUEUE_TYPE ac_node_p //进行此宏定义,就可以把queue封装成接口使用
#define FREE_QUEUE_VALUE //queue->value为动态申请内存需要做此操作 int queue_node_num = ; //定义队列结构用于计算失效指针
typedef struct queue_node
{
QUEUE_TYPE value;
struct queue_node *next;
}queue_node; typedef struct queue
{
queue_node *head;
queue_node *tail;
}queue; /*
* queue创建结点
*/
queue_node *queue_build_node(QUEUE_TYPE value)
{
queue_node *node = (queue_node *)malloc(sizeof(queue_node)); if(node == NULL)
{
#ifdef AC_DEBUG
printf("queue node bulid error memory is full !!\n\n");
#endif
return NULL;
} node->value = value; node->next = NULL; return node;
} /*
* queue初始化
* return -1 失败 else 成功
*/
queue *queue_init()
{
queue *ac_queue = (queue *)malloc(sizeof(queue)); if(ac_queue == NULL)
{
#ifdef AC_DEBUG
printf("queue build failed memory is full\n\n");
#endif
return NULL;
} ac_queue->head = ac_queue->tail = queue_build_node(NULL); if(ac_queue->head == NULL)
{
#ifdef AC_DEBUG
printf("queue build head error memory is full\n\n");
#endif
return NULL;
} //ac_queue->head->next = ac_queue->tail; return ac_queue;
} /*
*queue 为空判断
*return 1 为空 0 不为空
*/
int queue_is_empty(queue *ac_queue)
{
if(ac_queue->head == ac_queue->tail)
{
return ;
} return ;
} /*
* queue 向队尾添加结点
*/
void queue_insert(queue *ac_queue,queue_node *node)
{
ac_queue->tail->next = node;
ac_queue->tail = node;
queue_node_num++;
#ifdef AC_DEBUG
printf("after insert the queue node num is %d :\n",queue_node_num);
#endif
} /*
*queue 提取队首结点value值
*/
QUEUE_TYPE queue_first(queue *ac_queue)
{
if(queue_is_empty(ac_queue))
{
#ifdef AC_DEBUG
printf("the queue is empty can not return head!!\n");
#endif
return NULL;
} return ac_queue->head->next->value; //队首不存值,从队首的下一个结点开始取值
} /*
*queue 队首结点出队列
*/
void queue_delete(queue *ac_queue)
{ if(queue_is_empty(ac_queue))
{
#ifdef AC_DEBUG
printf("the queue is empty we can not delete head\n\n");
#endif
return;
} queue_node *head = ac_queue->head->next; //队首不存值,从队首的下一个结点开始出队列 ac_queue->head->next = head->next; if(head == ac_queue->tail)
{//出队列为最后一个元素时将队列置空
ac_queue->tail = ac_queue->head;
} free(head); //释放队首结点内存 #ifdef AC_DEBUG
queue_node_num--;
printf("after delete the queue node num is %d :\n",queue_node_num);
#endif
} /*
*queue 释放queue内存
*/
void queue_destroy(queue *ac_queue)
{
queue_node *p = NULL;
p = ac_queue->head; while(p != NULL)
{ #ifdef FREE_QUEUE_VALUE
if(p->value != NULL)
free(p->value); //value为动态申请内存的情况下做此操作
#endif
queue_node *tmp = p->next; if(p != NULL)
free(p); p = tmp;
}
} //ac状态节点
struct ac_node
{
int final; //是否为一个模式串结尾的表示
int model; //标识该模式串为哪个模式串(如果考虑后缀子模式,此处应该改为整型链表)
ac_node *fail; //该状态节点的失效指针 struct ac_node *next[CHAR_EXTERN];
}; /*
* 创建状态节点
*/
ac_node *ac_node_build()
{
int i;
ac_node *node = (ac_node *)malloc(sizeof(ac_node)); if(node == NULL)
{
#ifdef AC_DEBUG
printf("bulid node error the memory is full !! \n\n");
#endif
return NULL;
} node->final = ;
node->model = -;
node->fail = NULL; for(i = ; i < CHAR_EXTERN; i++)
{
node->next[i] = NULL;
} return node;
} /*
* 创建trie树
* return -1 失败 else 成功
*/
int ac_trie_build(ac_node *root,char *str,int len,int model)
{
int i;
ac_node *tmp = root; if(tmp == NULL)
{
#ifdef AC_DEBUG
printf("root has not been init!!! \n\n");
#endif
return -;
} for(i = ; i < len; i++)
{ /*
ac_node *next_node = tmp->next[str[i] - 'a'];
这样写然后对next_node操作会造成trie树建立失败,由于next_node一直不为NULL
*/ int index = str[i] - 'a'; // if CHAR_EXTERN=256 index = str[i]&0xff
if(tmp->next[index] == NULL)
{
tmp->next[index] = ac_node_build(); if(tmp->next[index] == NULL)
{
#ifdef AC_DEBUG
printf("build node error in ac_trie_build !!\n");
#endif
return -;
} } tmp = tmp->next[index];
} tmp->final = ;
tmp->model = model; return ;
} /*
* 创建失效指针函数
*/ void ac_build_fail(ac_node *root,queue *ac_queue)
{
if(root == NULL || ac_queue == NULL)
{
#ifdef AC_DEBUG
printf("build ac fail pointer error -- input\n");
#endif
return ;
} int i;
queue_node *q_node = NULL;
ac_node *tmp_node = NULL;
ac_node *fail_node = NULL; q_node = queue_build_node(root);
queue_insert(ac_queue,q_node); while(!queue_is_empty(ac_queue))
{
tmp_node = queue_first(ac_queue);
#ifdef AC_DEBUG
printf("out the queue the ac node pointer is %p \n",tmp_node);
#endif
queue_delete(ac_queue);//队首元素出队列 for(i = ; i < CHAR_EXTERN; i++)
{//通过队列采用BFS(广度优先)的遍历顺序来计算当前状态每个字符的失效函数 if(tmp_node->next[i] != NULL) // if CHART_EXTERN=255 tmpnode->next[i&0xff]
{
if(tmp_node == root)
{
tmp_node->next[i]->fail = root; //第一层节点的失效指针指向根结点
}
else
{
fail_node = tmp_node->fail; //父结点的失效指针 while(fail_node != NULL)
{//当直到回到根结点时
if(fail_node->next[i] != NULL)
{//在父结点的失效指针中找到当前字符的外向边
tmp_node->next[i]->fail = fail_node->next[i];
break;
} fail_node = fail_node->fail; //继续递归
} if(fail_node == NULL)
{//找不到失效指针,则失效指针为根结点
tmp_node->next[i]->fail = root;
} } q_node = queue_build_node(tmp_node->next[i]);
queue_insert(ac_queue,q_node); //将当前层的结点插入队列继续进行广度优先遍历
#ifdef AC_DEBUG
printf("insert into a ac node into queue the state is : %c \n\n", i + 'a');
printf("insert the ac node pointer is %p\n",tmp_node->next[i]);
#endif
}
}
}
} /*
*模式匹配函数
*return -1 未匹配到任何模式 else 匹配到的当前的模式串的值
*/
int ac_query(ac_node *root,char *str,int len)
{
if(root == NULL || str == NULL)
{
return -;
} int i,match_num = ;
int index = ; ac_node *tmp_node = NULL;
ac_node *p = root; for(i = ; i < len; i++)
{
index = str[i] - 'a'; // if CHAR_EXTERN=256 index = str[i]&0xff while(p->next[index] == NULL && p != root)
{//状态在当前字符不进行转移以后的失效转移
p = p->fail;
} p = p->next[index]; //将状态进行下移 if(p == NULL)
{//未找到失效指针,从根结点开始继续
p = root;
} tmp_node = p; //计算当前状态下的匹配情况(在局部定义指针型变量经常出现内存指向问题) while(tmp_node != root)
{
if(tmp_node->final == )
{
match_num++; //tmp_node = tmp_node->fail; //匹配子模式串 return tmp_node->model; //此处不进行return 可以计算多个模式串
} tmp_node = tmp_node->fail; //匹配子模式串
} } return -;
} /*
*打印trie树
*/
void ac_trie_print(ac_node *root)
{//利用队列进行广度优先遍历并打印trie树
ac_node *ac_queue[];
int i,head,tail;
head = tail = ; memset(ac_queue,,sizeof(ac_queue)); ac_node *tmp = root; ac_queue[tail++] = tmp;
while(head != tail)
{
tmp = ac_queue[head++]; //出队列
for(i = ; i < CHAR_EXTERN; i++)
{
if(tmp->next[i] != NULL)
{
printf("%c ",i+'a');
ac_queue[tail++] = tmp->next[i]; //将当前层的所有节点入队列
}
} printf("\n"); }
} #define LIB_MODEL_NUM 10 int main()
{
#if 1
char *lib_model_str[] = {"wwwgooglecom",\
"wwwbaiducom",\
"www",\
"googlecom",\
"guoxin",\
"she",\
"her",\
"shr",\
"yes",\
"say"}; #endif #if 0 char *lib_model_str[LIB_MODEL_NUM] = {
"she",\
"he",\
"say",\
"shr",\
"her"\
};
#endif int i;
char str[];
queue *ac_queue = NULL;
ac_node *root = NULL; ac_queue = queue_init();
root = ac_node_build(); for(i = ; i < LIB_MODEL_NUM; i++)
{
ac_trie_build(root,lib_model_str[i],strlen(lib_model_str[i]),i);
} ac_trie_print(root); ac_build_fail(root,ac_queue); while()
{
printf("input the match string:\n\n"); scanf("%s",str); if(strcmp(str,"quit") == )
{
return -;
} int model = ac_query(root,str,strlen(str)); if(model == -)
{
printf("not match!!\n\n");
}
else
{
printf("match the model '%d' and the model string is %s \n\n",model,lib_model_str[model]);
}
} return ;
}

一个自己编写的简单AC自动机代码-----AC automata get √的更多相关文章

  1. 【AC自动机】AC自动机

    Definition & Solution AC自动机是一种多模式串的字符串匹配数据结构,核心在于利用 fail 指针在失配时将节点跳转到当前节点代表字符串的最长后缀子串. 首先对 模式串 建 ...

  2. HDU4758 Walk Through Squares AC自动机&&dp

    这道题当时做的时候觉得是数论题,包含两个01串什么的,但是算重复的时候又很蛋疼,赛后听说是字符串,然后就觉得很有可能.昨天队友问到这一题,在学了AC自动机之后就觉得简单了许多.那个时候不懂AC自动机, ...

  3. 字符串处理-AC自动机

    估计在OJ上刷过题的都会对AC自动机这个名词很感兴趣,同样,记得去年ACM暑期集训的时候,在最后讲到字符串部分,听说了这个算法的名字之后就对于它心向往之,AC正好是Accept的简称,字面意义上的理解 ...

  4. Aho-Corasick automaton(AC自动机)解析及其在算法竞赛中的典型应用举例

    摘要: 本文主要讲述了AC自动机的基本思想和实现原理,如何构造AC自动机,着重讲解AC自动机在算法竞赛中的一些典型应用. 什么是AC自动机? 如何构造一个AC自动机? AC自动机在算法竞赛中的典型应用 ...

  5. AC自动机学习笔记-1(怎么造一台AC自动机?)

    月更博主又来送温暖啦QwQ 今天我们学习的算法是AC自动机.AC自动机是解决字符串多模匹配问题的利器,而且代码也十分好打=w= 在这一篇博客里,我将讲解AC自动机是什么,以及怎么构建一个最朴素的AC自 ...

  6. [专题总结]AC自动机

    其实前面的模板也不是1A,我在题库里提前做过,也不必在意罚时,刚开始我在做别的专题 裸模板我就不说了,各个博客讲解的很明白 void insert(string s){ ,len=s.size(); ...

  7. 浅析 AC 自动机

    目录 简述 AC 自动机是什么 AC 自动机有什么用 AC 自动机·初探 AC 自动机·原理分析 AC 自动机·代码实现 AC 自动机·更进一步 第一题 第二题 第三题 从 AC 自动机到 fail ...

  8. AC 自动机学习笔记

    虽然 NOIp 原地爆炸了,目前进入 AFO 状态,但感觉省选还是要冲一把,所以现在又来开始颓字符串辣 首先先复习一个很早很早就学过但忘记的算法--自动 AC AC自动机. AC 自动机能够在 \(\ ...

  9. AC自动机

    AC自动机,全称Aho-Corasick自动机.如果没记错的话好像就是前缀自动机. 其实AC自动机就是KMP上树的产物.理解了KMP,那AC自动机应该也是很好理解的. 与KMP类似,AC自动机也是扔一 ...

随机推荐

  1. [LeetCode] 243. Shortest Word Distance 最短单词距离

    Given a list of words and two words word1 and word2, return the shortest distance between these two ...

  2. 流程图软件Microsoft Visio

    简介 Visio是一款能处理复杂信息.系统和流程进行可视化.分析和交流的软件,从“office 2003”以后,Visio作为一个单独软件发行,不再集成于office办公软件. 下载安装 官方下载最新 ...

  3. os.environ模块

    os.environ是用来获取当前操作系统的一些基本信息的模块 import osos.environ.setdefault("DJANGO_SETTINGS_MODULE", & ...

  4. (生鲜项目)03. xadmin的配置

    步骤1. 下载xadmin源码(git_hub上的源码已经不支持py3了,需要从其它老手那里获取),将本文件夹全部放置于合适的目录下(这里放到extra_apps内)2. 在settings的INST ...

  5. .Net - 线程本地变量(存储)的使用

    关于C#多线程的文章,大部分都在讨论线程的开始与停止或者是多线程同步问题.多线程同步就是在不同线程中访问同一个变量或共享资源,众所周知在不使用线程同步的机制下,由于竞争的存在会使某些线程产生脏读或者是 ...

  6. Spark源码(1) Spark配置

    写熟悉的第一句代码 val conf = new SparkConf().setAppName("WordCount")点击SparkConf() ,发现 private val ...

  7. SQL——LIKE操作符

    一.LIKE操作符的基本用法 LIKE操作符用于在WHERE子句中,搜索相似.类似的数据. LIKE操作符语法: SELECT 列名1,列名2... FROM 表名 WHERE 列名 LIKE xxx ...

  8. yum安装k8s集群

    k8s的安装有多种方式,如yum安装,kubeadm安装,二进制安装等.本文是入门系列,只是为了快速了解k8s的原理和工作过程,对k8s有一个快速的了解,这里直接采用yum安装 的1.5.2为案例进行 ...

  9. ssh免秘钥

    用过好几次免秘钥,但是每次都会忘了应该把copy谁的公钥到另外用户的.ssh文件夹 这里专门记录一次 注意点: A要使用ssh免密登录到B用户下(可以使远程服务器),就把A的用户下的.ssh文件的id ...

  10. kubernetes第一章--介绍