這是一個數學建模的問題,非常考察編程能力。
我們選擇這個題目,就是因爲i這個題目可以轉化爲模式串匹配問題。
由於這個題目中的模式串爲k-mer,數量特別大,我們隊用了多模式匹配的AC自動機算法找位置。
但是存儲k-mer位置的索引表的數據量也很大,我們就用了哈希函數的直接定址法+鏈表的方式建立索引表。
雖然在海量數據下,建立索引的時間很長,但是一旦建立索引再去查找位置非常快。
題目如下:
這個問題來自 DNA序列的k-mer index問題。
給定一個DNA序列,這個系列只含有4個字母ATCG,如 S =“CTGTACTGTAT”。給定一個整數值k,從S的第一個位置開始,取一連續k個字母的短串,稱之爲k-mer(如k= 5,則此短串爲CTGTA), 然後從S的第二個位置, 取另一k-mer(如k= 5,則此短串爲TGTAC),這樣直至S的末端,就得一個集合,包含全部k-mer 。 如對序列S來說,所有5-mer爲
{CTGTA,TGTAC,GTACT,TACTG,ACTGT,TGTAT}
通常這些k-mer需一種數據索引方法,可被後面的操作快速訪問。例如,對5-mer來說,當查詢CTGTA,通過這種數據索引方法,可返回其在DNA序列S中的位置爲{1,6}。
問題
現在以文件形式給定 100萬個 DNA序列,序列編號爲1-1000000,每個基因序列長度爲100 。
(1)要求對給定k, 給出並實現一種數據索引方法,可返回任意一個k-mer所在的DNA序列編號和相應序列中出現的位置。每次建立索引,只需支持一個k值即可,不需要支持全部k值。
(2)要求索引一旦建立,查詢速度儘量快,所用內存儘量小。
(3)給出建立索引所用的計算複雜度,和空間複雜度分析。
(4)給出使用索引查詢的計算複雜度,和空間複雜度分析。
(5)假設內存限制爲8G,分析所設計索引方法所能支持的最大k值和相應數據查詢效率。
(6)按重要性由高到低排列,將依據以下幾點,來評價索引方法性能
· 索引查詢速度
· 索引內存使用
· 8G內存下,所能支持的k值範圍
· 建立索引時
我們隊高大上的代碼:
#include <algorithm>
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
using namespace std;
const long long maxn = 1048580;
const int INF = 0x3fffff;
const double EPS = 1e-6;
const double PI = 3.1415926;
typedef long long ll;
int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};
int k;
typedef struct node //儲值
{
int add;
struct node *next;
} node,*Rlink;
Rlink head[maxn];
void List_insert(int add,int data) //鏈表中插入地址
{
if(head[add]==NULL)
{
head[add] = (node *)malloc(sizeof(node));
head[add]->add = data;
head[add]->next = NULL;
}
else
{
node *p = head[add];
while(p->next)
p=p->next;
node *q = (node *)malloc(sizeof(node));
q->add = data;
q->next = NULL;
p->next = q;
}
}
char str[1048580][101];
char lstr[110000000];
int hash(char *strr);
struct Trie
{
int next[1048580][101],fail[1048580],end[1048580];
int root,L;
int newnode()
{
for(int i = 0; i < 101; i++)
next[L][i] = -1;
end[L++] = -1;
return L-1;
}
void init()
{
L = 0;
root = newnode();
}
void insert(char s[],int id)
{
int len = strlen(s);
int now = root;
for(int i = 0; i < len; i++)
{
if(next[now][s[i]] == -1)
next[now][s[i]] = newnode();
now = next[now][s[i]];
}
end[now] = id;
}
void build()
{
queue<int>Q;
fail[root] = root;
for(int i = 0; i < 101; i++)
if(next[root][i] == -1)
next[root][i] = root;
else
{
fail[next[root][i]] = root;
Q.push(next[root][i]);
}
while(!Q.empty())
{
int now = Q.front();
Q.pop();
for(int i = 0; i < 101; i++)
if(next[now][i] == -1)
next[now][i]=next[fail[now]][i];
else
{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
int num[1048580];//記錄個數
vector<int>post[1048580];//記錄位置
void query(char buf[],int n)
{
for(int i = 0; i < n; i++)
num[i] = 0,post[i].clear();
int len=strlen(buf);
int now=root;
for(int i=0; i<len; i++)
{
now=next[now][buf[i]];
int temp = now;
while( temp != root )
{
if(end[temp] != -1)
{
num[end[temp]]++;
post[end[temp]].push_back(i);
}
temp = fail[temp];
}
}
for(int i = 0; i < n; i++)
{
if(num[i] > 0)
{
int add = hash(str[i]); //存儲在數組中
for(int j=0; j<post[i].size(); j++)
{
List_insert(add,post[i][j]+1-k); //
}
}
}
}
};
Trie ac;
int hash(char *strr)
{
int len = strlen(strr);
int add = 0; //地址
int base = 1; //進制
for(int i=len-1; i>=0; i--)
{
switch(strr[i])
{
case 'A':
add += 0*base;
break;
case 'T':
add += 1*base;
break;
case 'G':
add += 2*base;
break;
case 'C':
add += 3*base;
break;
}
base*=4;
}
return add;
}
void substr(char *st,char *ss,int i,int j) //i開始截取j個字符,扔進st中
{
for(int k=0; k<j; k++)
st[k] = ss[k+i];
st[j] = '\0';
}
void Index(char *s)
{
int add = hash(s);
node *p = head[add];
while(p)
{
printf("模式串所在DNA序列號爲: %d,所在的下標爲: %d\n",p->add/101+1,p->add%101+1);
p=p->next;
}
}
int main()
{
printf("請輸入一個K值:\n");
scanf("%d",&k);
map <string,int> map; //去重
string ss;
char tmp[102];
ac.init();
int i=0;
int cnt=0; //記錄模式串個數
ifstream in("test.txt"); //文件讀取
if(in)
{
while(getline(in,ss))
{
ss.copy(tmp,100,0);
tmp[100] = '*'; //將不同行的字符串分隔開,避免跨行匹配成功
tmp[101] = '\0';
strcat(lstr,tmp);
for(int j=0; j<=100-k; j++)
{
string moshi = ss.substr(j,k);
if(map[moshi]==1) continue; //已經插入過的模式串不會再插入
substr(str[cnt],tmp,j,k);
ac.insert(str[cnt],cnt);
cnt++;
map[moshi] = 1; //插入標記
}
i++;
}
}
ac.build(); //ac自動機建立
ac.query(lstr,cnt);
char l[101];
printf("請輸入您要查詢的k-mer:\n");
while(~scanf("%s",l))
Index(l);
return 0;
}