基于kmp字符串模式配算法的病毒感染检测问题

本文介绍了如何利用KMP算法解决环状病毒DNA序列在人类DNA中检测的问题。通过KMP算法实现高效的字符串匹配,避免了暴力匹配的低效率。文章详细讲解了KMP算法的next数组计算和代码实现,并提供了相关视频资源。

本文记录了数据结构习题解析与实验指导(李冬梅)的实验4。
以下是实验内容

1 问题描述

医学研究者最近发现了某些新病毒,通过对这些病毒的分析,得知它们的DNA序列都是环状的。现在研究者已收集了大量的病毒DNA和人的DNA数据,想快速检测出这些人是否感染了相应的病毒。为了方便研究,研究者将人的DNA和病毒DNA均表示成由一些字母组成的字符序列,然后检测某种病毒DNA序列是否在患者的DNA序列中出现过。如果出现过,则此人感染了该病毒,否则没有感染。例如,假设病毒的DNA序列为baa,患者1的DNA序列为aaabbba,则感染;患者2的DNA序列为babbba,则未感染。(注意,人的DNA序列是线性的,而病毒的DNA序列是环状的。)

输入要求
多组数据,每组数据一行,对应一个算术表达式,每个表达式均以“=”结尾。当表达式只有一个“=”时,输入结東。

输出要求
多组数据,每组数据有1行,为序列A和B,A对应病毒的DNA序列,B对应人的DNA序列。A和B都为“0”时输入结束。

输入样例
abbab abbabaab
baa cacdvcabacsd
abc def
0 0

输出样例
YES
YES
NO

2 基本思想

因为是环状的,所以可以将字符串A复制一遍。然后每次取原A长度的子串。之后进行字符串匹配,这里的子串匹配算法,我所知道的有两种,一种是BF(简单,但是效率很低),另一种是KMP算法(较难理解,但效率高)。本文是利用KMP算法进行求解。

3 KMP算法

具体的思想就不讲了,讲一讲代码。
首先是求next数组的代码,(这里我的next数组是从下标0开始,第一个元素为-1的数组,另一种方法是从下标1开始存储,第一个元素为0的数组。两种方法很相似,这里介绍的是第一种方法。)

	private int[] next;
	private String parent;
	private String son;
	
	public Kmp(String parent, String son) {
		this.parent = parent;
		this.son = son;
		next = new int[son.length()];
	}
	
	public void getNext() {
		int len = son.length();
		int i = -1;
		int j = 0;
		next[0] = -1;
		while (j < len - 1) {
			if (i == -1 || son.charAt(i) == son.charAt(j)) {
				i++;
				j++;
				next[j] = i;
			} else {
				i = next[i];
			}
		}
	}

基本的数据结构就不讲了,讲一讲,关键的while循环。
首先有两个下标i,j分别表示最长前缀的末尾+1和最长后缀的末尾+1.如果相等,则各自加一,这应该很好懂。放一张图解释。

(k就相当于代码中的i,j相当于代码中的j),首先看第一行红色的。如果k和j位置的元素相等,自然而然地+1,然后看下一元素是否相等。
那么如果不等怎么办呢。这时看图的第二行,首先我们可以证明黄色的部分都是相同的。那么很明显只要让k=next[k]就可以再比较了。如果相等,接着各自加1.不过这里可能你会有个疑问,为啥k要跳到next[k],才是最长的前缀呢。这里我们就要了解下next[k]的含义next[k]的含义是,k位置前的字符串最大前缀的末尾的下一个。仔细看一下图,你可能就会略懂了。而第三行蓝色的则是next[k]位置的元素和j位置的元素还不相等,那么k = next[next[k]]
的了。

最极端的时候,如果k=next[k] = -1时怎么办,那么就要再if的判断条件加上一句,关于k==-1时的判断。也就是各自都加一,也就是next[j+1]=0,然后k跳到0位置在比较。(当然这条判断也解决了初始时k==-1的问题)

最后可能会对小于len-1产生疑问。为啥不是len呢。因为我们比较的是j,但是我们实际填的是next[j+1].这里如果不懂就需要看一下手推next数组的教程了(注意这里用的是首位是-1的next数组求解方法)。

这里推荐一个视频,我觉得这个视频很好。看过之后有种恍然大悟的感觉😀。视频传送门

4 Kmp代码主体部分

public int getPosition() {
		int i = 0;
		int j = 0;
		while (i < parent.length() && j < son.length()) {
			if (j == -1 || son.charAt(j) == parent.charAt(i)) {
				i++;
				j++;
			} else {
				j = next[j];
			}
		}
		if (j == son.length()) {
			return i - j;
		} else {
			return -1;
		}
	}

非常简单,就不讲了。

5 全部代码

class Kmp {
	private int[] next;
	private String parent;
	private String son;
	
	public Kmp(String parent, String son) {
		this.parent = parent;
		this.son = son;
		next = new int[son.length()];
	}
	
	public void getNext() {
		int len = son.length();
		int i = -1;
		int j = 0;
		next[0] = -1;
		while (j < len - 1) {
			if (i == -1 || son.charAt(i) == son.charAt(j)) {
				i++;
				j++;
				next[j] = i;
			} else {
				i = next[i];
			}
		}
	}
	
	public void setSon(String son) {
		this.son = son;
	}
	
	public int getPosition() {
		int i = 0;
		int j = 0;
		while (i < parent.length() && j < son.length()) {
			if (j == -1 || son.charAt(j) == parent.charAt(i)) {
				i++;
				j++;
			} else {
				j = next[j];
			}
		}
		if (j == son.length()) {
			return i - j;
		} else {
			return -1;
		}
	}
}

public class Test14 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int flag = 0;
		String parent = "cacdvcabacsd";
		String son = "baa";
		int len = son.length();
		Kmp test = new Kmp(parent, son);
		son = son.repeat(2);
		for (int i = 0; i < son.length() - len; ++i) {
			String newSon = son.substring(i, i + len);
			test.setSon(newSon);
			test.getNext();
			if (test.getPosition() != -1) {
				System.out.println("YES");
				flag = 1;
				break;
			}
		}
		if (flag == 0) {
			System.out.println("NO");
		}
	}

}

6 nextval数组

我们先来看这种情况,主串为"aaaaaxbcd",字串为"aaaaab",我们发现进行b的确认时,b与x不等,然后我们就要跳到倒数第一个a,倒数第二个a…这些过程似乎有些多余,所以我们发现如果后面的4个a,与首位的a相同的话,就完全可以用next[0]的值替代后面的四个a所对应位置的next数组的值。

具体代码如下:

	public void getNextval() {
		int len = son.length();
		int i = -1;
		int j = 0;
		nextval[0] = -1;
		while (j < len - 1) {
			if (i == -1 || son.charAt(i) == son.charAt(j)) {
				i++;
				j++;
				if (son.charAt(i) != son.charAt(j)) {
					nextval[j] = i;
				} else {
					nextval[j] = nextval[i];
				}
			} else {
				i = nextval[i];
			}
		}
	}

这就是全部的内容。如果有错误,可以在底下评论,或者私聊我😉,我会及时进行修改的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值