算法拾遗三十五indexTree和AC自动机

news2024/10/7 8:08:07

算法拾遗三十五indexTree和AC自动机

      • indexTree(树状数组)
        • indexTree规则
      • IndexTree二维
      • AC自动机

indexTree(树状数组)

给定数组下标统一从1开始
在这里插入图片描述
如果要求L。。R范围上任意区间的和,我们通常的解法是定义一个help(前缀和数组)数组,然后记录它的累加和
在这里插入图片描述
如果把arr中的某个数字改一下,则需要重新维护help数组。
如果我想同时支持arr中某个数字修改,也要快速查询出L。。R范围上的区间和,那么如上结构是不适用的。
indexTree不像线段树一样可以实现范围更新,indexTree适合单点更新查区间范围和的情况且时间复杂度相对于线段树更优

indexTree规则

在这里插入图片描述

有如下数组,下标从1开始:
在这里插入图片描述
我想生成另一个help数组:
当前来到1位置,我前面没有和我长度为1的凑一对,所以help的1位置填入3,当来到2位置的时候,前面有和我长度为1的凑一对,2位置填入4。当来到3位置的时候,前面没有单独和我长度为1的凑一对,3位置填入-2,4位置前面有和我长度为1的凑一对,然后再往前看前面有没有长度为2的和我凑一对,发现有的所以4位置填5,然后依次类推下去。
在这里插入图片描述
上图中规律:
如果index=8,它应该管1-8范围的累加和,
8的二进制位01000,它管的范围为将二进制中最后一个1拆散之后再加1的第一个数到自己
00001-01000(就是1-8)
再来一个例子当index=12的时候,应该管9-12范围的和 12的二进制为01100,则管的范围可以化简为01001-01100【9-12】

已知了如上规律,有help数组之后,那么如何利用help数组求前缀和,如果要求33位置的前缀和,
则就只需要求0100001+0100000【抹掉最后一个1的位置】,一直抹完所有最后一个1的位置。
在这里插入图片描述
比如:
help【0110100】,原始arr中想要累加到它
help[0110100] = arr[0110001-0110100]
第二步
help[0110000]=arr[0100001-0110000]
分段求得的解的和就是0110100区间范围的累加和。

时间复杂度是logN水平,因为动的是位数

public class IndexTree {

	// 下标从1开始!
	public static class IndexTree {

		private int[] tree;
		private int N;

		// 0位置弃而不用!
		public IndexTree(int size) {
			N = size;
			tree = new int[N + 1];
		}

		// 1~index 累加和是多少?
		public int sum(int index) {
			int ret = 0;
			while (index > 0) {
				ret += tree[index];
				index -= index & -index;
			}
			return ret;
		}

		// index & -index : 提取出index最右侧的1出来
		// index :           0011001000
		// index & -index :  0000001000
		// -index = index取反加1
		public void add(int index, int d) {
			//如果要把index位置增加一个d
			while (index <= N) {
				tree[index] += d;
				//index变化是当前index加上最右侧的1组成的数
				index += index & -index;
			}
		}
		//求影响的时候则由最右侧的1怼上去,累加就由最右侧的一减下来
	}

	public static class Right {
		private int[] nums;
		private int N;

		public Right(int size) {
			N = size + 1;
			nums = new int[N + 1];
		}

		public int sum(int index) {
			int ret = 0;
			for (int i = 1; i <= index; i++) {
				ret += nums[i];
			}
			return ret;
		}

		public void add(int index, int d) {
			nums[index] += d;
		}

	}

	public static void main(String[] args) {
		int N = 100;
		int V = 100;
		int testTime = 2000000;
		IndexTree tree = new IndexTree(N);
		Right test = new Right(N);
		System.out.println("test begin");
		for (int i = 0; i < testTime; i++) {
			int index = (int) (Math.random() * N) + 1;
			if (Math.random() <= 0.5) {
				int add = (int) (Math.random() * V);
				tree.add(index, add);
				test.add(index, add);
			} else {
				if (tree.sum(index) != test.sum(index)) {
					System.out.println("Oops!");
				}
			}
		}
		System.out.println("test finish");
	}

}

IndexTree二维

二维数组从(1,1)位置到(i,j)位置的整块累加和填入help(i,j)的格子里面
假设原数组图中?位置的值被改变了,那么help数组变化范围?
行管的是0110001-0110100
列管的是0110001-0111000
行列管的范围内所有组合都受影响
在这里插入图片描述

// 测试链接:https://leetcode.com/problems/range-sum-query-2d-mutable
// 但这个题是付费题目
// 提交时把类名、构造函数名从Code02_IndexTree2D改成NumMatrix
public class IndexTree2D {
	private int[][] tree;
	private int[][] nums;
	private int N;
	private int M;

	public IndexTree2D(int[][] matrix) {
		if (matrix.length == 0 || matrix[0].length == 0) {
			return;
		}
		N = matrix.length;
		M = matrix[0].length;
		tree = new int[N + 1][M + 1];
		nums = new int[N][M];
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				update(i, j, matrix[i][j]);
			}
		}
	}

	private int sum(int row, int col) {
		int sum = 0;
		for (int i = row + 1; i > 0; i -= i & (-i)) {
			for (int j = col + 1; j > 0; j -= j & (-j)) {
				sum += tree[i][j];
			}
		}
		return sum;
	}

	public void update(int row, int col, int val) {
		if (N == 0 || M == 0) {
			return;
		}
		int add = val - nums[row][col];
		nums[row][col] = val;
		for (int i = row + 1; i <= N; i += i & (-i)) {
			for (int j = col + 1; j <= M; j += j & (-j)) {
				tree[i][j] += add;
			}
		}
	}

	public int sumRegion(int row1, int col1, int row2, int col2) {
		if (N == 0 || M == 0) {
			return 0;
		}
		return sum(row2, col2) + sum(row1 - 1, col1 - 1) - sum(row1 - 1, col2) - sum(row2, col1 - 1);
	}

}

AC自动机

(实质)前缀树+KMP
理解:假设有一个字典里面放着若干个敏感词,然后有一个大文章,AC自动机就是将大文章包含的每一个敏感词都收集到不能漏掉,并且将大文章包含了哪些敏感词都告诉我。
我们先将注意力放在敏感词上(“abc”,“bkf”,“abcd”),首先将敏感词的前缀树建立出来:
在这里插入图片描述
然后再对前缀树做升级,给前缀树做fail指针(做宽度优先遍历):
头节点的fail指针指向null,头节点的下一层的所有节点的fail指针指向头节点
在这里插入图片描述
假设b路径下面的节点为x节点,则需要找x的父节点的fail指针指向的节点(这里是头节点)有没有直接指向b方向的路【此处没有】再找头节点的fail指针看有没有指向b方向的路,发现也没有,则让x节点的fail指针指向头节点,一直宽度优先遍历找下去得到:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结:
1、头节点的fail指针指向null
2、头节点的子节点的fail指针都指向头节点
3、如果对于某个节点x它的父节点通过路径@指向x,然后父亲节点的fail指针为某个节点甲,如果甲有路径@则x的fail指针直接指向过去。
在这里插入图片描述
fail指针含义:
在这里插入图片描述
有如上图敏感词信息,首先知道c路径下的一个x节点的fail指针是指向【cde】路径下c下的y节点,x节点从上到下匹配到敏感词abcde中的abc,但是在匹配d的时候失败了,下面所有字符串中哪一个字符串的前缀,它一定跟我必须以c结尾的后缀的一样,且是最长的,如下图abcd中d的fail指针是指向cde中的d节点,它没有去找de而是找的cde。
在这里插入图片描述
在这里插入图片描述

如上图没画虚线的fail指针都指向头节点,匹配大文章的过程中匹配到e位置后则匹配不上了,则需要通过e的fail指针来到必须以e结尾最长的前缀保留(cde),我准确的跳到有可能配到敏感词的下一个最可能的最近的位置,保证自己淘汰每一步都如此的小心,跳到第二个e位置,发现仍然匹配不上,然后再跳到第三个e位置。
第一层节点fail指针为什么指向头节点:
是因为如果a匹配到f的时候没找到任何节点可以匹配了,则可以通过fail指针跳到头节点重新找路径【相当于a不要了直接从f开始重新找】
在这里插入图片描述

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class AC {

	// 前缀树的节点
	public static class Node {
		// 如果一个node,end为空,不是结尾
		// 如果end不为空,表示这个点是某个字符串的结尾,end的值就是这个字符串
		public String end;
		// 只有在上面的end变量不为空的时候,endUse才有意义
		// 表示,这个字符串之前有没有加入过答案
		public boolean endUse;
		public Node fail;
		//前缀树都是字母表示
		public Node[] nexts;

		public Node() {
			endUse = false;
			end = null;
			fail = null;
			nexts = new Node[26];
		}
	}

	public static class ACAutomation {
		private Node root;
		//新建头节点
		public ACAutomation() {
			root = new Node();
		}

		//前缀树加字符串
		public void insert(String s) {
			char[] str = s.toCharArray();
			Node cur = root;
			int index = 0;
			for (int i = 0; i < str.length; i++) {
				index = str[i] - 'a';
				if (cur.nexts[index] == null) {
					cur.nexts[index] = new Node();
				}
				cur = cur.nexts[index];
			}
			cur.end = s;
		}

		//build方法连fail指针
		public void build() {
			//准备队列做宽度优先遍历
			Queue<Node> queue = new LinkedList<>();
			//将头节点加入队列
			queue.add(root);
			Node cur = null;
			Node cfail = null;
			//由父来设置所有子的fail指针,当父弹出时设置关联子的fail指针
			while (!queue.isEmpty()) {
				// 某个父亲,cur
				cur = queue.poll();
				//考察所有的路(所有儿子节点)
				for (int i = 0; i < 26; i++) { // 所有的路
					// cur -> 父亲  如果有i号儿子,必须把i号儿子的fail指针设置好!
					if (cur.nexts[i] != null) { // 如果真的有i号儿子
						//先将孩子的fail指针设置成root
						cur.nexts[i].fail = root;
						//父亲的fail指针指向cfail,第一步跳转
						cfail = cur.fail;
						//如果父亲的fail指针不为null
						while (cfail != null) {
							//如果父亲的fail指针不为空,且有i方向的儿子
							if (cfail.nexts[i] != null) {
								//则让当前节点的儿子的fail指针指过去
								cur.nexts[i].fail = cfail.nexts[i];
								break;
							}
							//如果没有i方向的儿子则再往fail指针方向挑
							cfail = cfail.fail;
						}
						queue.add(cur.nexts[i]);
					}
				}
			}
		}

		// 大文章:content,在遍历每个单词的时候都顺着fail指针走一遍看是否有end串,有则收集
		public List<String> containWords(String content) {
			char[] str = content.toCharArray();
			Node cur = root;
			Node follow = null;
			int index = 0;
			List<String> ans = new ArrayList<>();
			for (int i = 0; i < str.length; i++) {
				index = str[i] - 'a'; // 当前路
				// 如果当前字符在这条路上没配出来,就随着fail方向走向下条路径
				while (cur.nexts[index] == null && cur != root) {
					cur = cur.fail;
				}
				// 1) 现在来到的路径,是可以继续匹配的【来到的路正是我匹配期待的路,则继续往下走】
				// 2) 现在来到的节点,就是前缀树的根节点
				cur = cur.nexts[index] != null ? cur.nexts[index] : root;
				//注意此处cur是没动的,而是通过follow去捞取的数据
				follow = cur;
				//来到任何节点过一圈fail指针
				while (follow != root) {
					//沿途一直蹦
					if (follow.endUse) {
						//之前的敏感词已经记录过了
						break;
					}
					// 不同的需求,在这一段之间修改
					if (follow.end != null) {
						ans.add(follow.end);
						//标记使用了这个字符串
						follow.endUse = true;
					}
					// 不同的需求,在这一段之间修改
					follow = follow.fail;
				}
			}
			return ans;
		}

	}

	public static void main(String[] args) {
		ACAutomation ac = new ACAutomation();
		ac.insert("dhe");
		ac.insert("he");
		ac.insert("abcdheks");
		// 设置fail指针
		ac.build();

		List<String> contains = ac.containWords("abcdhekskdjfafhasldkflskdjhwqaeruv");
		for (String word : contains) {
			System.out.println(word);
		}
	}

}

首先大文章的长度是N,所有匹配串长度M

大文章一定会过一遍,时间复杂度O(N)。

所有匹配串被用来做了AC自动机,一个节点最多一条fail指针,并且根据代码来看:

		while (follow != root) {
				if (follow.end == -1) {
					break;
				}
				{ // 不同的需求,在这一段{ }之间修改
					ans += follow.end;
					follow.end = -1;
				} // 不同的需求,在这一段{ }之间修改
				follow = follow.fail;
			}

每一条fail指针最多走一遍,因为沿途的follow.end都会被设置为-1。

一旦被设置成-1,那么这条fail指针的链就不会继续跳转了。

ac自动机节点数:M

fail指针总数目:M

如果在ac自动机上往下走的代价被算进了遍历大文章的过程里。

所以时间复杂度O(N+M)。

如果想统计各匹配串的匹配次数,是不是不设置endUse来做次数累加。

如果是的话对均摊、最坏时间复杂度有没有影响。

如果统计匹配串匹配次数的话有没有AC自动机之外别的方法呢?

如果统计各匹配串的匹配次数,那么就不能让沿途的follow.endUse被设置为true。

因为一旦标记true,那么该匹配串就被命中过了,那么下次这个字符串可能就不会随着fail指针转圈被再次统计到了。

但是如果不让沿途的follow.endUse被设置为true,那么复杂度会因此升高的,不再是O(N+M),而是O(N*fail指针圈的平均长度)

“如果统计匹配串匹配次数的话有没有AC自动机之外别的方法呢?”

你可以继续用ac自动机。因为fail指针圈的平均长度,一般情况除非刻意构造,否则不会太长。

或者,你先用ac自动机,看看哪些串被匹配到了,但是不统计次数。

然后每一个串,开一个线程去跑kmp算法,单独统计这个串被大文章引用了多少次。利用多线程来搞。

但是用ac自动机,效率也不会低到哪去的。除非你刻意构造fail指针圈很长的例子。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/747487.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

msvcp140.dll重新安装的解决方法,msvcp140.dll丢失修复教程

计算提示msvcp140.dll丢失需要怎么重新安装呢&#xff1f;下面小编就把msvcp140.dll丢失重新安装的修复教程分享给大家。msvcp140.dll是Microsoft Visual C Redistributable文件的一部分&#xff0c;它是一个动态链接库文件。该文件包含了一些用于C程序开发的函数和类的定义&am…

【Vue2.0源码学习】模板编译篇-模板解析阶段(HTML解析器)

文章目录 1. 前言2. HTML解析器内部运行流程3. 如何解析不同的内容3.1 解析HTML注释3.2 解析条件注释3.3 解析DOCTYPE3.4 解析开始标签3.5 解析结束标签3.6 解析文本 4. 如何保证AST节点层级关系5. 回归源码5.1 HTML解析器源码5.2 parseEndTag函数源码 6. 总结 1. 前言 上篇文…

如何应对ChatGPT这一波AI浪潮

最近我在写一系列文章&#xff0c;其中包括《ChatGPT 实战系列》和《WPS Office AI实战系列》。想通过这些文章提供实践指导&#xff0c;既自己动手实践了&#xff0c;也能与大家分享我的实践结果&#xff0c;这是一个学习的过程。在实践过程中&#xff0c;我发现有些实用的方面…

基于springboot文学创作的社交论坛新闻文章系统vue

相比于传统的社交论坛管理方式&#xff0c;智能化的管理方式可以大幅提高社交论坛的管理效率&#xff0c;实现了社交论坛管理的标准化、制度化、程序化的管理&#xff0c;有效地防止了社交论坛信息的随意管理&#xff0c;提高了信息的处理速度和精确度&#xff0c;能够及时、准…

OnlyHome三代金属智能手环|健康、科技齐实现

近年来,人们越来越追求生活的品质与趣味,生活中的一点点小确幸、小惊喜最能让人感受到深深地愉悦。这不,Only&Home三代金属智能手环也带来了惊喜,有了它,健康、科技两手抓,享受生活更自由。 Only&Home三代金属智能手环给人的第一印象是它超高的颜值,延用了二代手环奢华…

Proton 推出开源密码管理器,兼身份管理器

导读Proton 是由来自欧洲核研究组织 (CERN) 的科学家于 2014 年在瑞士日内瓦创立的一家公司&#xff0c;其最知名的应该就是电子邮件服务 Proton Mail&#xff0c;主打端到端加密、安全和隐私保护。Proton 由科学家领导&#xff0c;其中包括万维网的发明者 Tim Berners-Lee。 …

高速入门知识02:降低串扰和维持信号完整性的布线方法

文章目录 前言一、单端走线布线1.1.带有短截线的菊花链布线1.2.没有短截线的菊花链布线1.3.星型布线1.4.蛇型布线 二、差分走线布线 前言 串扰是并行走线间不需要的信号耦合。微带线和带状线正确的布线和叠层布局能够降低串扰。 双带线布局有两个靠近的信号层&#xff0c;为降…

MQ的优劣势及RabbitMQ相关概念

一&#xff0c;MQ 1&#xff0c;MQ 的概念 MQ 全称 Message Queue&#xff08;消息队列&#xff09;&#xff0c;是用来存储消息数据的容器&#xff08;是一个中间件&#xff09;&#xff0c;一般用于分布式系统间的通信&#xff1b;MQ主要介于生产者和消费者之间&#xff0c…

lwip-2.1.3自带的httpd网页服务器使用教程(一)从SD卡读取网页文件并显示

概述 本教程使用的单片机是STM32F103ZE&#xff0c;有线网口芯片为ENC28J60。 本教程里面的网页由于需要兼容Windows XP系统的IE8浏览器&#xff0c;所以采用HTML 4.01编写&#xff0c;不使用任何前端框架。笔者使用的网页设计软件是Adobe Dreamweaver CS3。 开发板PCB文件是公…

推荐Selenium 自动化测试实战

你将获得 深入 Selenium 源码、原理、封装、技巧&#xff1b; unittest、pytest、DDT、POM 迭代测试方法&#xff1b; 大型项目分布式测试解决方案&#xff1b; Jenkins 持续集成和交付。 演示地址&#xff1a;www.runruncode.com/portal/article/index/id/19451/cid/85.html 课…

【无线通信专题】NFC基本原理

NFC定义 NFC(Near Field Communication)近场通信。 NFC早期应用 NFC最开始的应用主要用于金融领域,POS机(reader)通过非接触的方式与银行卡(带NFC接口的卡片)进行交互得到银行卡信息并完成支付。因为NFC的通信距离比较近,所以安全性较高。 后来随着手机支付的流行。…

使用STM32实现 蓝牙插座

硬件介绍 蓝牙模块HC-01&#xff0c;其实之前就用过&#xff0c;使用起来非常简单 继电器模块&#xff0c; (VCC 3.3V)当左侧IN输入低电平时&#xff0c;右侧的ON 和 COM会导通&#xff0c;左上的绿灯会亮&#xff0c;此处充当插座的角色 项目需求 通过蓝牙的串口发送open打开…

JMeter 中 3 种参数值的传递

目录 前言&#xff1a; (一) 从 CSV 文件读取要批量输入的变量 (二) 利用 Cookie 进行值的传递 (三) 利用正则匹配提取上一个接口的返回数据作为下个请求的输入 前言&#xff1a; 在JMeter中&#xff0c;参数值的传递是非常重要的&#xff0c;因为它允许你在测试过程中动态…

Spring 如何解决 Bean 的循环依赖(循环引用)

Component public class A {Autowiredprivate B b;}Component public class B {Autowiredprivate A a;}上面的情况就是 循环依赖 Bean的创建初始化过程如下 如果不采取措施&#xff0c;那么循环依赖就会进入死循环 但 Spring 已经帮我们解决了大部分循环依赖问题 具体是如何解…

RabbitMQ的使用详解

一、什么是MQ 1、什么是MQ MQ&#xff08;message queue&#xff09;&#xff0c;本质是个队列&#xff0c;FIFO先入先出。只不过队列中放的是message&#xff0c;是一种跨进程的通信机制&#xff0c;用于上下游传递消息。在互联网架构中&#xff0c;MQ是一种非常常见的上下游…

EMC案例-接地环路对传导骚扰测试的影响

EMC测试案例分析——接地环路对传导骚扰测试的影响 本文主要就接地环路对传导骚扰测试的影响进行简要举例分析&#xff0c;为我们以后的测试方法提供参考。 Part 1 现象描述 某电子设备在进行传导骚扰测试时&#xff0c;在3MHz左右的频率点出现了超限的情况&#xff0c;其测…

ELK-日志服务【es-安装使用】

目录 【1】安装-配置elasticsearch&#xff08;01、02、03相同&#xff09; 端口 【2】安装-配置-启动-Kibana 【3】浏览器访问测试&#xff08;10.0.0.21:5601&#xff09; 【4】使用kibana创建、更新、删除es索引、文档 【5】组es集群&#xff08;投票选举机制&#xf…

用户体验在APP开发中的关键性作用

在 APP开发过程中&#xff0c;如何设计才能让用户感到满意&#xff0c;是非常重要的一点&#xff0c; APP开发公司需要不断地学习新的 APP设计知识&#xff0c;因为只有这样才能设计出令人印象深刻的 APP。对于用户来说&#xff0c;产品的用户体验在很大程度上决定了产品的竞争…

引入头文件#include <iostream>的时候发生了什么?

<iostream> namespace std {extern istream cin;extern ostream cout;extern ostream cerr;extern ostream clog;extern wistream wcin;extern wostream wcout;extern wostream wcerr;extern wostream wclog;};cin是什么&#xff1f; cin extern istream cin; The objec…

elasticsearch集群部署搭建(一)

elasticsearch集群部署搭建&#xff08;一&#xff09; 部署信息JDK安装下载es安装包部署安装创建用户&#xff08;三台机器都执行&#xff09;解压安装包&#xff08;选择一台机器执行&#xff09;修改配置文件&#xff08;三台机器都执行&#xff09; 拷贝分发注册系统服务服…