數據結構之病毒感染檢測問題


問題描述:

醫學研究者最近發現了某新病毒,通過對這些病毒的分析,得知他們的DNA序列都是環狀的。現在研究者已收集了大量

病毒DNA和人的DNA數據,想快速檢測出這些人是否感染了相應的病毒。為了研究方便,研究者將人的DNA和病毒DNA

均表示成由一些字母組成的字符串序列,然后檢測眸中病毒DNA序列是否在患者的DNA序列中出現過,如果出現過,則

此人感染了該病毒,否則沒有感染。例如,假設病毒的DNA序列為baa,患者1的DNA序列為aaabbba,則感染;患者2的

DNA序列為babbba,則未感染。(注意,恩德DNA序列式線性的,而病毒的DNA序列為環狀的)。

Input:

abbab  abbabaab

baa      cacdvcabacsd

abc      def

0          0

Output:

YES

NO

NO

 

BF算法(即遍歷法):

 1 #include "stdafx.h"
 2 #include<iostream>
 3 #include<cstring>
 4 #include<string>
 5 using namespace std;
 6 
 7 int Index_BF(const string &str1, const string &str2, int pos)
 8 {
 9 
10     int i = pos;
11     int j = 0;
12     int len1 = str1.size();
13     int len2 = str2.size();
14     while (i<len1&&j<len2)
15     {
16         if (str1[i] == str2[j])
17         {
18             i++;
19             j++;
20         }
21         else
22         {
23             i = i - j + 1;
24             j = 0;
25         }
26     }
27     if (j >= len2)
28         return i - len2;
29     else
30         return -1;
31 }
32 
33 int main()
34 {
35     int pos = 0;
36     string str1;
37     string str2;
38     while (cin >> str2 >> str1)
39     {
40         if (str1 == "0"&&str2 == "0")break;
41         int result = Index_BF(str1, str2, pos);
42         if (result != -1)
43             cout << "YES" << endl;
44         else
45             cout << "NO" << endl;
46     }
47     return 0;
48 }

 

KMP算法:

 1 #include "stdafx.h"
 2 #include<iostream>
 3 #include<cstring>
 4 #include<string>
 5 #include<fstream>
 6 using namespace std;
 7 
 8 void get_next(string str2,int *next)
 9 {
10     int i,j;
11     i=1;
12     j=0;
13     next[1]=0;
14     while(i<str2.size())
15     {
16         if(j==0||str2[i]==str2[j])
17         {
18             ++i;
19             ++j;
20             if(str2[i]!=str2[j])
21                 next[i]=j;
22             else
23                 next[i]=next[j];
24         }
25         else
26             j=next[j];
27     }
28 }
29 
30 int kmp(string &str1,string &str2,int pos)
31 {
32 
33     int i=pos;
34     int j=0;
35     int next[255];
36     get_next(str2,next);
37     int len1=str1.size();
38     int len2=str2.size();
39     while(i<len1&&j<len2)
40     {
41         if(j==0||str1[i]==str2[j])
42         {
43             i++;
44             j++;
45         }
46         else
47         {
48             j=next[j];
49         }
50     }
51         if(j>=len2)
52             return i-len2;
53         else
54             return -1;
55 }
56 
57 int main()
58 {
59     int pos=0    ;
60     string str1;
61     string str2;
62     while(cin >>str2>>str1)
63     {
64         if(str1=="0"&&str2=="0")break;
65     int result=kmp(str1,str2,pos);
66     if(result!=-1)
67         cout<<"YES"<<endl;
68     else
69         cout<<"NO"<<endl;
70     }
71     return 0;
72 }

 

對於正常的字符串模式匹配,主串長度為m,子串為n,時間復雜度會到達O(m*n),而如果用KMP算法,復雜度將會減少線型時間O(m+n)。

 

設主串為ptr="ababaaababaa";,要比較的子串為a=“aab”;

 

KMP算法用到了next數組,然后利用next數組的值來提高匹配速度,我首先講一下next數組怎么求,之后再講匹配方式。

 

NEXT[]詳解:

next數組詳解

首先是理解KMP算法的第一個難關是next數組每個值的確定,這個問題困惱我很長時間,尤其是對照着代碼一行一行分析,很容易把自己繞進去。

定義一串字符串

ptr = "ababaaababaa";

 

next[i](i從1開始算)代表着,除去第i個數,在一個字符串里面從第一個數到第(i-1)字符串前綴與后綴最長重復的個數。

 

什么是前綴?

在“aba”中,前綴就是“ab”,除去最后一個字符的剩余字符串。

同理可以理解后綴。除去第一個字符的后面全部的字符串。

 

在“aba”中,前綴是“ab”,后綴是“ba”,那么兩者最長的子串就是“a”;

在“ababa”中,前綴是“abab”,后綴是“baba”,二者最長重復子串是“aba”;

在“abcabcdabc”中,前綴是“abcabcdab”,后綴是“bcabcdabc”,二者最長重復的子串是“abc”;

 

這里有一點要注意,前綴必須要從頭開始算,后綴要從最后一個數開始算,中間截一段相同字符串是不行的。

 

再回到next[i]的定義,對於字符串ptr = "ababaaababaa";

next[1] = -1,代表着除了第一個元素,之前前綴后綴最長的重復子串,這里是空 ,即"",沒有,我們記為-1,代表空。(0代表1位相同,1代表兩位相同,依次累加)。

next[2] = -1,即“a”,沒有前綴與后綴,故最長重復的子串是空,值為-1;

next[3] = -1,即“ab”,前綴是“a”,后綴是“b”,最長重復的子串“”;

next[4] = 1,即"aba",前綴是“ab”,后綴是“ba”,最長重復的子串“a”;next數組里面就是最長重復子串字符串的個數

next[5] = 2,即"abab",前綴是“aba”,后綴是“bab”,最長重復的子串“ab”;

next[6] = 3,即"ababa",前綴是“abab”,后綴是“baba”,最長重復的子串“aba”;

next[7] = 1,即"ababaa",前綴是“ababa”,后綴是“babaa”,最長重復的子串“a”;

next[8] = 1,即"ababaaa",前綴是“ababaa”,后綴是“babaaa”,最長重復的子串“a”;

next[9] = 2,即"ababaaab",前綴是“ababaaa”,后綴是“babaaab”,最長重復的子串“ab”;

next[10] = 3,即"ababaaaba",前綴是“ababaaab”,后綴是“babaaaba”,最長重復的子串“aba”;

next[11] = 4,即"ababaaabab",前綴是“ababaaaba”,后綴是“babaaabab”,最長重復的子串“abab”;

next[12] = 5,即"ababaaababa",前綴是“ababaaabab”,后綴是“babaaaababa”,最長重復的子串“ababa”;

 

還有另外一種方法,我看的有的書上寫着:

這里我們定義next[1] = 0 , next[1] = 1;

 

再分析ptr字符串,ptr = "ababaaababaa";

跟上一個的情況類似,

 

next[1] = 0 ,事先定義好的

next[2] = 1 ,事先定義好的

next[3] = 1 ,最長重復的子串“”;1代表沒有重復,2代表有一個字符重復。

next[4] = 2 ,最長重復的子串“a”;追償的長度加1,即為2.

next[5] = 3 ,以下都跟之前的一樣,這種方法是最長的長度再加上一就可以了。

next[6] = 4

next[7] = 2

next[8] = 2

next[9] = 3 

next[10] = 4 

next[11] = 5 

next[12] = 6 

 

以上是next數組的詳細解釋。next數組求值 是比較麻煩的,剩下的匹配方式就很簡單了。

next數組用於子串身上,根據上面的原理,我們能夠推出子串a=“aab”的next數組的值分別為0,1,2.(按照我說的第二種方式算的)。

 

首先開始計算主串與子串的字符,設置主串用i來表示,子串用j來表示,如果ptr[i]與a[i]相等,那么i與j就都加1:

prt[1]與a[1]相等,i++,j++:

用代碼實現就是

 

1 if( j==0 ||  ptr[i]==a[j])  
2 {  
3     ++i;  
4     ++j;  
5 }  

 

 

 

ptr[2]與a[2]不相等

此時ptr[2]!=a[2],那么令j = next[j],此時j=2,那么next[j] = next[2] = 1.那么此時j就等於1.這一段判斷用代碼解釋的話就是:

 

1 if( ptr[i]!=a[j])  
2 {  
3       j = next[j];  
4 }  

 

加上上面的代碼進行組合:

在對兩個數組進行比對時,各自的i,j取值代碼:

 


1 <span style="font-size:18px;">while( i<ptr.length && j< a.length)  
2 {  
3      if( j==0 || ptr[i]==a[i] )  
4     {  
5           ++i;  
6           ++j;</span>  

 

 
1 <span style="font-size:18px;">          next[i] = j;  
2     }  
3     else  
4     {  
5           j = next[j];  
6     }  
7 }</span>  

 

此時將a[j]置於j此時所處的位置,即a[1]放到j=2處,因為在j=2時出現不匹配的情況。

 

此時再次計算是否匹配,可以看出來a[1]!=ptr[2],那么j = next[j],即此時j = next[1] = 0;

根據上面的代碼,當j=0時,執行++i;++j;

此時就變為:

此時ptr[3] = a[1],繼續向下走,下一個又不相等了,然后“aab”向后挪一位,這里不再贅述了,主要的思想已經講明白了。到最后一直到i = 8,j=3時匹配成功,KMP算法結束。整個過程就結束了。

測試結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM