当前位置:首页 » 操作系统 » 算法字符串

算法字符串

发布时间: 2023-03-31 09:44:21

① 数据结构与算法——字符串匹配问题(KMP算法)

KMP算法也是比较着名的模式匹配算法。是由 D.E.Knuth,J.H.Morrs VR.Pratt 发表的一个模式匹配算法。可以大大避免重复遍历的情况。

如果使用暴风算法的话,前面五个字母完全相等,直到第六个字母 "f" "x" 不相等。如下图:

T = “abcdex”
j 123456
模式串 abcdex
next[j] 011111

T = "abcabx"
j 123456
模式串T abcabx
next[j] 011123

T = "ababaaaba"
j———————123456789
模式串T——— ababaaaba
next[j]————011234223

T = "aaaaaaaab"
j———————123456789
模式串T——— aaaaaaaab
next[j]————012345678

next数组其实就是求解字符串要回溯的位置
假设,主串S= “abcababca”;模式串T=“abcdex”,由以上分析得出next数组为011111,next数组意味着当主串与模式串不匹配时,都需要从第一个的位置重新比较。

KMP算法也是有缺陷的,比如主串S=“aaaabcde”,模式串T= “aaaaax”。next的数组就是012345;

当开始匹配时,当i= 5,j = 5时,我们发现字符"b"与字符“a”不相等,如上图,j = next[5] = 4;

由于T串的第二、三、四、五位置的字符都与首位“a”相等,那么可以用首位next[1]的值去取代与它相等的后续字符的next[j],那么next数组为{0,0,0,0,0,5};

在求解nextVal数组的5种情况

② 字符串匹配算法的使用(未完待整理)

字符串的匹配在java中都知道使用indexOf函数来实现,那么其匹配算法是怎么样的呢?

单模式和多模式的区别就是一次遍历主串能否将多个模式的字符串都查找出来。

英文全称为Brute Force,暴力匹配算法,匹配字符串的方法比较暴力,也比较简单易懂。其大概的思路就是:

我们可以看到,在极端情况下,在主串 aaaa...aab 中寻找模式串 aab ,那么总共需要寻找(n-m+1)次,且每次都需要比对m次,那么时间复杂度将是 (n-m+1)*m ,即 O(n*m) ;但实际上并不会这么低效,因为我们的使用场景中主串和模式串都不会太长,而且在每个子串和模式串进行比对时,只要中途有一个不匹配,那么当前比对就会提前结束,因此大部分情况下,时间复杂度都会比 O(n*m) 要好。

我们在BF算法的基础上引入哈希算法,我们不需要将每个子串与模式串逐个字符地进行比较,而是计算得出每个子串的hash值,然后和模式串的hash值进行比较,如果有相等的,那就说明有子串和模式串匹配上了。

虽然我们只需要比对模式串和子串的hash值就能得到匹配结果,次数为(n-m+1),但是对每个子串进行hash计算的时候,是要遍历每个字符的,因此次数也是m,那么总的时间复杂度还是 O(n*m) ,并没有明显地提升。

那么我们该如何想出一个办法,使得每个子串hash值的计算时间得到提升呢?这就是RK算法的精髓,假设子串包含的字符集中元素个数为k,那么就用k进制数来代表这个子串,然后hash的过程就是将这个k进制的数转换为十进制的数,这个十进制的数就是该子串的hash值。

相邻子串的hash值计算是有规律的,我们只需要遍历一次主串就能得到所有子串的hash值,算法复杂度为O(n),而不是像原先一样,每个子串都需要O(m)的时间复杂度。

然后将模式串的hash值和所有子串的hash值进行比较,每次比较的时间复杂度是 O(1) ,总共比较(n-m+1)次,所以RK算法的总的时间开销为 O(n)+O(1)*O(n-m+1) ,即为 O(n) ,时间复杂度比BF算法更加高效。

当然,有hash的地方就有可能会存在hash冲突,有可能子串和hash值和模式串的hash值是一样的,但内容就是不一样,此时怎么办呢?其实很简单,对于hash值一样的子串,我们增加双保险,再比较一下这m个字符是否都一样即可,总的时间开销为 O(n)+O(1)*O(n-m+1)+O(m) ,即为 O(n) 。

如果极端情况下出现了很多hash冲突呢?我们对于每个和模式串相同hash值的子串都需要逐一再进行比较,那么总的时间开销就会为 O(n)+O(1)*O(n-m+1)+O(m)*O(n-m+1) ,即为 O(n*m) ,不过这种概率太小了,大部分情况下都不会这样。

在真正的文本编辑器中查找和替换某个字符串时,使用的算法既不是上述的BF算法,也不是RK算法;BF算法只适合不是很长的主串,RK算法则要设计一个冲突概率很低的hash算法,这个比较困难,所以实际使用的是BM算法,它是工程中非常常用的一种字符串匹配算法,效率也是最高的。

算法的思想和过程有些复杂,待以后整理。

KMP算法在本质上是和BM算法一样的。算法的思想和过程有些复杂,待以后整理。

浏览器输入框中的智能输入匹配是怎么实现的,它是怎么做动态字符串匹配查找的呢?这就用到了Trie树。

又名字典树,是一种专门用来快速查找字符串前缀匹配结果的树形结构,其本质就是将所有字符串的重复的前缀合并在一起,构造一个多叉树。

其中,根节点不包含任何信息,每个节点表示一个字符,从根节点到红色节点的一条路径表示存储的一个字符串。当我们在如上Trie树中查找"he"时,发现"he"并非是一个字符串,而是"hello"和"her"的公共前缀,那么就会找到这两个字符串返回。

Trie树在内存中是如何存储的呢?因为每一个节点都可能是包含所有字符的,所以每一个节点都是一个数组(或者散列表),用来存储每个字符及其后缀节点的指针。

使用Trie树,最开始构建的时候,时间复杂度为 O(n) ,其中n为所有字符串长度之和,但是一旦构建完成,频繁地查询某个字符串是非常高效的,时间复杂度为 O(k) ,其中k为查找字符串的长度。

Trie树虽然查询效率很高,但是比较浪费内存,每一个节点都必须维护一个数组存放所有可能的字符数据及其指向下一个节点的指针,因此在所有字符串公共前缀并不多的时候,内存空间浪费地就更多了。这种问题其实也有对应的解决办法,我们可以不使用数组,而是使用有序数组、散列表、红黑树来存放,可以相应地降低性能来节省内存空间。

Trie树除了可以实现浏览器动态输入内容查找候选项的功能外,还可以实现多模式地敏感词匹配功能。假设我们需要对用户输入的内容进行敏感词检查,将所有的敏感内容用***代替,那么该如何实现呢?

首先我们可以维护一个敏感词字典,使用上述四种单模式匹配算法也可以实现,但是需要遍历N次用户输入的内容,其中N是所有敏感词的模式串,显得非常低效。但是我们如果将敏感词字典维护为一个Trie树,然后将用户输入的内容从位置0开始在Trie树中进行查询,如果匹配到红色节点,那么说明有敏感词;如果没有匹配到红色节点,就从用户输入内容的下一个位置开始继续在Trie树中查询,直至将用户输入内容遍历完,因此我们只是遍历了一遍主串。

然而更高效的多模式字符串匹配使用地更多的是如下的AC自动机。

如果把Trie树比作BF算法,KMP算法是BF算法的改进,那么AC自动机就是利用同样的思想改进了Trie树。

算法的思想和过程有些复杂,待以后整理。

③ 面试必备——BM字符串查找算法

字符串的一种基本操作是子字符串查找:给定一端长度为N的文本字符串text和一个长度为M(M<N)的模式字符串pattern,在文本字符串中查找和该模式字符串相同的子字符串。在这互联网时代,字符串查找的需求在很多情景都需要,如在文本编辑器或浏览器查找某个单词、在通信内容中截取感兴趣的模式文本等等。

子字符串查找最简单的实现肯定是暴力查找:

可以看到,暴力查找的最坏时间复杂度为O(N*M),实际应用中往往文本字符串很长(成万上亿个字符),而模式字符串很短,这样暴力算法的时间复杂度是无法接受的。

为了改进查找时间,人们发明了很多字符串查找算法,而今天的主角 BM算法 (Bob Boyer和J Strother Moore发明,简称BM算法)就是其中的一种。

不同于暴力查找算法的逐个字符对比,BM算法充分使用 预处理模式字符串 的信息来尽可能跳过更多的字符。在暴力算法中,比较一个字符串都是从首字母开始,逐个比较下去。一旦发现有不同的字符,就需要从头开始进行下一次比较,就需要将字串中的所有字符一一比较。BM算法的核心思路在于,文本字符串从左到右检索,模式字符串从右到左检索,当模式字符串的一个字符pattern[j]和文本字符串的字符text[i+j]不匹配时,那么在模式字符串中查找字符text[i+j]是否存在索引k,使得pattern[k] == text[i+j],k若存在, k应该为满足条件的最右索引 。此时存在三种情景:

通过这种字符的移动方式来代替逐个比较,正是BM算法的高效的关键所在!那么我们怎么知道文本字符串的字符是否存在于模式字符串中?对的,预处理。我们在查找前,先建立一张包含文本字符串的所有字符的字母表, 这张表中记录着字母表中的每个字符在模式字符串中出现的最靠右的索引,如果在字符在模式字符串中不存在,那么值为-1。

有了这张表,我们在查找时就可以高效的移动i。构建这张表很简单:

构建好表,我们只需要按上面分析的情景,出现字符不匹配时,通过表,把i向右平移到具体位置即可。BM完整算法实现如下:

由于不匹配的情况属于大多数,所以一般情况下,BM算法的时间复杂度为O(N/M),是线性级别的!可以说是非常高效了。但它需要额外的空间字母表大小R,所以BM算法是以空间换时间的。

至此,BM字符串查找算法已经分析完了,其实算是一种比较简单的算法,学习起来很快就能搞懂~

面试必备——KMP字符串查找算法

④ 字符串匹配算法

boyermoore算法的sample程序

TCHAR * BoyerMooreSearch(TCHAR *sSrc, TCHAR *sFind)
{
//
// 声明:
// 该段代码只是BoyerMoore(名字也许不准确)的基本思想,当
// 然不是最优的,具体完善工作就留给你自己乐!嘻嘻。
// 该算法的本质就是从字符串的右端而不是左端开始比较,这
// 样,当查询不匹配时才有可能直接跃过多个字符(最多可以跃过
// strlen(sFind)个字符),如果最右边的字符匹配则回溯。比如:
//
// pain
// ^ 这是第一次比较n和空格比
// The rain in SpainThe rain in Spain
//
// pain
// ^ 这是第二次比较,好爽呀!
// The rain in SpainThe rain in Spain
//
// 当然,这样比较会产生一些问题,比如:
//
// pain
// ^ (图1)
// The rain in SpainThe rain in Spain
//
// 如果比较到这儿,大家都会看到,只需再向后移到两个字符
// 就匹配成功了,但如果接下去还按上面的方法跳strlen(sFind)的
// 话,就会错过一次匹配!!!!!
//
// pain
// ^
// The rain in SpainThe rain in Spain
//
// 怎么办?当然可以解决!大家回头看图1,当时a是pain的子
// 串,说明有可能在不移动strlen(sFind)的跨度就匹配成功,那就
// 人为地给它匹配成功的机会嘛!串一下pain串,直接让两个a对齐
// 再做比较!呵呵,如果要比较的字符不是pain的子串,当然就可
// 以直接跨过strlen(sFind)个字符了!不知我说明白没?
//
//

// 查询串的长度
int nLenOfFind = lstrlen(sFind);
// 被查询串的长度
int nLenOfSrc = lstrlen(sSrc);
// 指向查询串最后一个字符的指针
TCHAR * pEndOfFind = sFind + nLenOfFind -1;
// 指向被查询串最后一个字符的指针
TCHAR * pEndOfSrc = sSrc + nLenOfSrc -1;

// 在比较过程中要用到的两个指针
TCHAR * pSrc = sSrc;
TCHAR * pFind;

// 总不能一直让它比较到win.com文件的地址去吧?嘻嘻!
while ( pSrc <= pEndOfSrc ) {

// 每次匹配都是从右向左,这是本算法的核心。
pFind = pEndOfFind;

// 如果比较不成功,被查询串指针将向右串的字符数
int nMoveRightSrc;

// 比较被查询串的当前字符是否和查询串的最右边字
// 符匹配,如果匹配则回溯比较,如果全匹配了,该
// 干什么,我就不用说了吧?:-)
while ( pFind >= sFind ) {

// TNND,白废功夫比了!看看需要向右移动几个
// 字符吧(如果说从右到左是本算法的核心,则
// 判断向右移几个字符则是本算法的技巧)。
if ( *pSrc != *pFind ) {

// 被查询串的当前字符是否在查询串里?
TCHAR * p = strrchr( sFind, *pSrc );
// 没在,直接移lstrlen(sFind)个字符
if ( NULL == p )
nMoveRightSrc = nLenOfFind;
else
// 哇塞!真的在,那就只需...
nMoveRightSrc = pEndOfFind - p;

break;
}

// 哈!又匹配成功了一个!接着向左回溯...
pFind --;
pSrc --;
}

// 如果在上面的while循环里每一次比较都匹配了
// 那就对了呗!告诉用户找到了
if ( pFind < sFind )
return ( pSrc + 1 );

// 没匹配成功,nMoveRightSrc上面已经算好了
// 直接用就可以了。
pSrc += nMoveRightSrc;
}

// 程序运行到这儿肯定是没指望了!
return NULL;
}

行了,函数写完了,我们可以试一下了!

void CTNNDDlg::OnButton1()
{
TCHAR sSrc[] = "The rain in Spain";
TCHAR sFind[]= "pain";

TCHAR * pFound = BoyerMooreSearch( sSrc, sFind );
if ( pFound )
MessageBox(pFound);
else
MessageBox("没找到");
}

//另外一个
void preBmBc(char *x, int m, int bmBc[]) {
int i;

for (i = 0; i < ASIZE; ++i)
bmBc[i] = m;
for (i = 0; i < m - 1; ++i)
bmBc[x[i]] = m - i - 1;
}

void suffixes(char *x, int m, int *suff) {
int f, g, i;

suff[m - 1] = m;
g = m - 1;
for (i = m - 2; i >= 0; --i) {
if (i > g && suff[i + m - 1 - f] < i - g)
suff[i] = suff[i + m - 1 - f];
else {
if (i < g)
g = i;
f = i;
while (g >= 0 && x[g] == x[g + m - 1 - f])
--g;
suff[i] = f - g;
}
}
}

void preBmGs(char *x, int m, int bmGs[]) {
int i, j, suff[XSIZE];

suffixes(x, m, suff);

for (i = 0; i < m; ++i)
bmGs[i] = m;
j = 0;
for (i = m - 1; i >= -1; --i)
if (i == -1 || suff[i] == i + 1)
for (; j < m - 1 - i; ++j)
if (bmGs[j] == m)
bmGs[j] = m - 1 - i;
for (i = 0; i <= m - 2; ++i)
bmGs[m - 1 - suff[i]] = m - 1 - i;
}

void BM(char *x, int m, char *y, int n) {
int i, j, bmGs[XSIZE], bmBc[ASIZE];

/* Preprocessing */
preBmGs(x, m, bmGs);
preBmBc(x, m, bmBc);

/* Searching */
j = 0;
while (j <= n - m) {
for (i = m - 1; i >= 0 && x[i] == y[i + j]; --i);
if (i < 0) {
OUTPUT(j);
j += bmGs[0];
}
else
j += MAX(bmGs[i], bmBc[y[i + j]] - m + 1 + i);
}
}

⑤ 【算法笔记】字符串匹配

BF 算法中的 BF 是 Brute Force 的缩写,中文叫作暴力匹配算法,也叫朴素匹配算法:

主串和模式串:
在字符串 A 中查找字符串 B,那字符串 A 就是主串,字符串 B 就是模式串。我们把主串的长度记作 n,模式串的长度记作 m

我们在主串中,检查起始位置分别是 0、1、2…n-m 且长度为 m 的 n-m+1 个子串,看有没有跟模式串匹配的。

BF 算法的时间复杂度是 O(n*m)

等价于

比如匹配Google 和Goo 是最好时间复杂度,匹配Google 和ble是匹配失败的最好时间复杂度。

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth与J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特—莫里斯—普拉特算法。KMP算法主要分为两个步骤:字符串的自我匹配,目标串和模式串之间的匹配。

看来网上很多的文章,感觉很多的都没有说清楚,这里直接复制阮一峰的内容,讲的很清晰
内容来自 http://www.ruanyifeng.com/blog/

首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

因为B与A不匹配,搜索词再往后移。

就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

接着比较字符串和搜索词的下一个字符,还是相同。

直到字符串有一个字符,与搜索词对应的字符不相同为止。

这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。

怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。

已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:

因为 6 - 2 等于4,所以将搜索词向后移动4位。

因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。

因为空格与A不匹配,继续后移一位。

逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。

逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。

下面介绍《部分匹配表》是如何产生的。

首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,

"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。

BM(Boyer-Moore)算法。它是一种非常高效的字符串匹配算法,有实验统计,它的性能是着名的KMP 算法的 3 到 4 倍。

BM 算法包含两部分,分别是坏字符规则(bad character rule)和好后缀规则(good suffix shift)

未完待续

参考文章:
字符串匹配的Boyer-Moore算法

⑥ 【算法】字符串移位

问题:一个字符串可以由另一个字符串移位得到,例如 abcd ,可以由 bcad 移位得到。

这个问题表面上说的是字符串,但是其实更进一步可以理解为两个字符数组的元素是否一致。最简单和直白的方式,无异于用两层循环的方式来进行循环判断。这是常规方案一。

还有方案二,则是需要用到数据结构,例如,将一个字符串转换成一个set。循环另一个数组,往set中插入数据,如果一直是失败的,则true。有一次插入成功了,则false。

如果我们在转换一下思想,字符其实在计算机中的表现,它的实质上也是数字毕碧,比如ASCII码中,字符 a 是可以转换成数字97的,所以两个数衫物组其实只要求元素之间的差的和,如果等于0就可以判断是或数液否相等。
例如:
1,2,34 和 2,3,14
则:

差的和:

但是,这样只是判断了移位,并没有判断基准位置。比如, 13 和 22 是可以判断成立的,因此需要增加判断两个数组的乘积是否相等。来判断基准位置是否一致。

我所写实现依赖于ASCII码,当如果字符串是Unioncode编码的字符,则就会出现问题。容我有空去研究一下,相处通用得解决方案。 如有问题欢迎各位批评指正,不胜感激。

⑦ 字符串算法之模式字符删除

根据慧冲颤一个给定模式,删除模式中出现的字符,如模式为“aeiou”,则”google”,删除后变为ggl。

考虑最优效率,我们可以先建立一个哈希表,以可能出现的字符ASCII码作为索引,遍历模式中的字符,将相应位置赋值为1。判空再遍历源字符串,如果源字符串中的字符出现在哈希表中,则去掉该字符。这样时间复杂度为O(n)。

def patterndelete(astr,pstr):

    hashtable=[0]*256

    for i in range(len(pstr)):

        hashtable[ord(pstr[i])]=1

    retstr=''

    for i in range(len(astr)):

        if hashtable[ord(astr[i])]!=1:

            retstr=retstr+astr[i]

return retstr

    前败   测试用例

>>> patterndelete('weouthdgd','aeiou')

'wthdgd'

>>> patterndelete('weouthdgd','a')   

'weouthdgd'

>>> 

⑧ (算法)去除字符串中的重复字符(时间复杂度)

1.一般会想到遍历字符串,铅好辩去除重复的字符,这样时间复杂度是O(n²),时间复杂度太高。

static String sub(String str){

StringBuffer result =newStringBuffer();

List list =new ArrayList();

char[] cs = str.toCharArray();

for(int i=0; i<cs.length; i++){

if(!list.contains(cs[i])){

result.append(cs[i]);

list.add(cs[i]);

}

}

returnresult.toString();

}

2.再仔细想一想

用java的String的indexOf方法来达到字符串去重的目的,indexOf的功能是返回指定字符在此字符串中第一次出现处的索引:

public static String QuChong(String str){

        StringBuilder sb=new StringBuilder();

        for(int i=0;i<str.length();i++){

            if(str.indexOf(str.charAt(i))==i){

                //第一次出槐缺现

                sb.append(str.charAt(i));

            }

        }

        String result=sb.toString();

       袜毕 return result;

    }

⑨ 算法:判断两个字符串是否包含相同的字符

方坦模锋法一: 最笨的方法,循环遍历,可以把字符串转化为数组,然后排序,然后比较。function : compare1
方法二: 以空间换取时间, 把两个字符串分别转换为字符数组,然后另外i用一个数组str,每个元素初始化为0,然后遍历第一个字符数组,减字符‘0’可得到其对应的ASCII码从而转化为整数n,把str数组的第n个元素加1, 然后遍历第二个字符数组进行同样的操作,只是第n个元素不是加1而是减1, 这样若是str数组有元素为0,则说明两个字符串让晌有相同的字符。function : compare2
方法三: 方法二的延伸,利用map的特点,先把第一个字符串的每一个字符作为key插入,再插入第二个字符串的每一个字符,map的key是唯一的,如果不能插入,则表明此字符在第一个字符串中存在。
下面是方法一和方法码空二的java实现,方法三还在测试中。

热点内容
androidvr播放器 发布:2025-05-19 15:55:32 浏览:963
我的世界pc如何创建服务器 发布:2025-05-19 15:51:24 浏览:732
抢脚本 发布:2025-05-19 15:47:14 浏览:406
ct4哪个配置性价比最高 发布:2025-05-19 15:38:02 浏览:953
如何设置强缓存的失效时间 发布:2025-05-19 15:21:28 浏览:695
winxp无法访问 发布:2025-05-19 15:19:48 浏览:947
文件预编译 发布:2025-05-19 15:14:04 浏览:643
怎么在服务器上挂公网 发布:2025-05-19 15:14:02 浏览:272
济南平安e通如何找回密码 发布:2025-05-19 14:56:58 浏览:176
安卓手机如何找到iccid码 发布:2025-05-19 14:46:51 浏览:227