210 likes | 380 Views
字符串处理. 概要. 输入输出 字符串哈希 Trie树 朴素的串匹配算法 Rabin_Karp KMP 更多请查阅:AC自动机(Trie图)、后缀数组. 输入输出. C scanf("%c")能读入一个字符,不 跳过 空格(32)和回车(10) scanf("%s")首先跳过所有空格和回车,读入一串字符,遇到空格或回车结束,保留空格或回车的未读状态 C++ cin>>char跳过所有空格和回车,读入一个字符 cin>>char[]和cin>>string用法同scanf("%s")
E N D
概要 • 输入输出 • 字符串哈希 • Trie树 • 朴素的串匹配算法 • Rabin_Karp • KMP • 更多请查阅:AC自动机(Trie图)、后缀数组
输入输出 • C • scanf("%c")能读入一个字符,不跳过空格(\32)和回车(\10) • scanf("%s")首先跳过所有空格和回车,读入一串字符,遇到空格或回车结束,保留空格或回车的未读状态 • C++ • cin>>char跳过所有空格和回车,读入一个字符 • cin>>char[]和cin>>string用法同scanf("%s") • cin.getline(起始地址,接受个数,结束字符)接受一个字符串,可以接收空格并输出,第三个参数省略时,系统默认是'\n'。不跳过回车,也就是cin>>n后的回车也会当成一行字符来读取
输入输出 • 示例:(ZOJ 1109) • dog ogday • cat atcay • pig igpay • froot ootfray • loops oopslay • atcay • ittenkay • oopslay
字符串哈希 字符串的算法一般大公司都会考到,我们首先要想到高效的hash。如百度查找一组字符串是否出现在某个文本中,这个不是考什么kmp,他们想听到的是hash!! • 哈希(hash) • 常用字符串哈希函数 • ELFHash
哈希 • 有很多应用要用到仅支持字典操作insert,search和delete的动态集合 • insert(T,k) 在动态集合T中插入关键字为k的元素 • search(T,k) 查找动态集合T中是否已有关键字为k的元素 • delete(T,k) 删除动态集合T中关键字为k的元素
直接寻址表 • 当由所有关键字构成的域U比较小时,直接寻址是一种简单而有效的技术。 • 为表示动态集合,我们采用一个数组(或称直接寻址表)T[0..m-1],其中每个位置对应域U中的一个关键字。也就是把关键字直接作为数组下标进行操作 • searchT,k) • return T[k] • insert(T,k) • T[key[x]]=x • delete(T,k) • T[key[x]]=NIL 直接寻址技术存在一个问题:如果域U很大,则要在计算机中存储的大小为|U|的一张表T就有点不实际,或不可能,还有,实际要存储的关键字集合K相对U来说可能很小,则分配给T的大部分空间都要浪费掉。
在直接寻址方式下,具有关键字k的元素被存放在槽k中,元素与槽一一对应。在直接寻址方式下,具有关键字k的元素被存放在槽k中,元素与槽一一对应。 • 哈希表根据函数h计算关键字k出槽的位置,函数h将关键字域U映射到哈希表T{0..m-1}的槽位上:h:U->{0,1,……,m-1} • 采用哈希函数的目的就在于缩小需要处理的下标范围,这就不可避免的出现多对一的映射。当遇到两个关键字经函数h计算出同一个出槽位置时,我们称出现了冲突,解决冲突的方法是我们研究的主要方向。比赛中常用解决冲突的方法有拉链法和开放地址法。
开放地址法 • 在开放地址法中,所有的元素都存放在哈希表里,也就是,每个表项或包含动态集合的一个元素,或包含NIL int locate(int s) { int h; h=Hash(s); while (use[h]&&pst[h]!=s) { h++; if (h>=max) h-=max; } return h; }
void insert(int s) { int posi=locate(s); if (!use[posi]) { use[posi]=1; pst[posi]=s; } } void delete(int s) { int posi=locate(s); if (use[posi]&&psw[posi]==s) use[posi]=0; } bool search(int s) { int posi=locate(s); if (use[posi]&&psw[posi]==s) return true; else return false; }
ELFHash unsigned int ELFHash(char *str) { unsigned int hash = 0,x = 0; while (*str) { hash = (hash << 4) + (*str++); if ((x = hash & 0xF0000000L) != 0) { hash ^= (x >> 24); hash &= ~x; } } return (hash & 0x7FFFFFFF); }
Trie树 • Trie定义 • Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。 • Trie原理 • Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。 • Trie性质 • 字符的种数决定每个节点的出度,即branch数组(空间换时间思想) • branch数组的下标代表字符相对于a的相对位置 • 采用标记的方法确定是否为字符串。 • 插入、查找的复杂度均为O(len),len为字符串长度
Trie的示意图 • 右图存储了三个字符串:abc,da,dda
struct Trie{ • int next[26]; • bool isword; • void init() • { • memset(next,0,sizeof(next)); • isword=false; • } • }T[1000000]; • T[1].init(); • L=1;
void ins(char *s,int k,int idx) • { • if (!s[k]) • { • T[idx].isword=true; • return; • } • if (T[idx].next[s[k]-'a']==0) • { • T[idx].next[s[k]-'a']=++L; • T[L].init(); • ins(s,k+1,L); • } else ins(s,k+1,T[idx].next[s[k]-'a']); • }
bool find(char *s,int k,int idx) • { • if (!s[k]) return false; • if (T[idx].isword) return true; • return find(s,k+1,T[idx].next[s[k]-'a']); • } 模板题:POJ3630
朴素的串匹配算法 • int match(char *T,char *P) • { • int n=strlen(T+1),m=strlen(P+1),i,j; • for (i=1;i<=n-m+1;i++) • { • for (j=1;j<=m;j++) • if (T[i+j-1]!=P[j]) break; • if (j>m) return i; • } • return 0; • } • 时间复杂度:O(nm)
Rabin_Karp 考虑一种特殊的字符串匹配,要在T=“31415926”中查找是否存在P=“141592”,首先前6位“314159”!=P,因此我们将T中的窗口向右移一位,变成“141592”,这时“141592”==“141592”。算法结束。 如何快速判断出“314159”!=“141592”? 因为314159!=141592 如何快速从314159右移为141592? 314159-300000+2=141592
伪代码 • Rabin_Karp(char *T,char *P,int d,int q) • n<-length[T]; • m<-length[P]; • h<-pow(d,m-1) mod q; • p=0; • t0=0; • for i<-1 to m //Preprocessing • do p=(d*p+P[i]) mod q; • t0=(d*t0+T[i]) mod q; • for s<-0 to n-m //Matching • do if p=ts • then if P[1..m]=T[s+1..s+m] • then print"Pattern occures with shift"s • if s<n-m • then t(s+1)<-(d*(ts-T[s+1]*h)+T[s+m+1]) mod q
KMP • KMP算法是通过分析子串,预先计算每个位置发生不匹配的时候,所需GOTO的下一个比较位置,整理出来一个next数组,然后在上面的算法中使用。 • next[i]的含义是如果P[i+1]匹配失败,就尝试匹配P[next[i]+1] • next[i]=max{k,P[1..k]==P[i-k-1..i-1]}(k<i)
int match(char *T,char *P) { int n=strlen(T+1),m=strlen(P+1),k=0,q; getnext(P,next); for (int i=1;i<=m;i++) printf("%d\n",next[i]); for (q=1;q<=n;q++) { while (k>0&&P[k+1]!=T[q]) k=next[k]; if (P[k+1]==T[q]) k++; if (k==m) return q; } return 0; } void getnext(char *P,int *next) { int m=strlen(P+1),k=0,q; next[1]=0; for (q=2;q<=m;q++) { while (k>0&&P[k+1]!=P[q]) k=next[k]; if (P[k+1]==P[q]) k++; if (P[k+1]==P[q+1]) next[q]=next[k]; else next[q]=k; } }