C++中的原生类型系统是否包含字符串类型?
没有,通过标准库。
String.h
String.cpp
string.h
string.cpp
思考:
如何在目标字符串中查找是否存在指定的子串?
如何在目标字符串S中,查找是否存在子串P ?
示例: ABCDABD
如何在目标字符串中查 找是否存在指定的子串?
string.h
string.cpp
第一轮,从主串的首位开始,把主串和模式串的字符逐个比较:
显然,主串的首位字符是a,模式串的首位字符是b,两者并不匹配。
第二轮,把模式串后移一位,从主串的第二位开始,把主串和模式串的字符逐个比较:
主串的第二位字符是b,模式串的第二位字符也是b,两者匹配,继续比较:
主串的第三位字符是b,模式串的第三位字符也是c,两者并不匹配。
第三轮,把模式串再次后移一位,从主串的第三位开始,把主串和模式串的字符逐个比较:
主串的第三位字符是b,模式串的第三位字符也是b,两者匹配,继续比较:
主串的第四位字符是c,模式串的第四位字符也是c,两者匹配,继续比较:
主串的第五位字符是e,模式串的第五位字符也是e,两者匹配,比较完成!
由此得到结果,模式串 bce 是主串 abbcefgh 的子串,在主串第一次出现的位置下标是 2:
假设主串的长度是m,模式串的长度是n,那么在这种极端情况下,BF算法的最坏时间复杂度是O(mn)。
① RK 算法的核心
BF算法只是简单粗暴地对两个字符串的所有字符依次比较,而RK算法比较的是两个字符串的[哈希值]。
每一个字符串都可以通过某种哈希算法,转换成一个整型数,这个整型数就是hashcode:
hashcode = hash(string)
② RK 算法的推演
给定主串和模式串如下(假定字符串只包含26个小写字母):
第一步,需要生成模式串的hashcode。
生成hashcode的算法多种多样,比如:
方法一:按位相加
这是最简单的方法,可以把a当做1,b当做2,c当做3......然后把字符串的所有字符相加,相加结果就是它的hashcode。
bce = 2 + 3 + 5 = 10
但是,这个算法虽然简单,却很可能产生hash冲突,比如bce、bec、cbe的hashcode是一样的。
方法二:转换成26进制数
既然字符串只包含26个小写字母,那么可以把每一个字符串当成一个26进制数来计算。
bce = 2*(26^2) + 3*26 + 5 = 1435
这样做的好处是大幅减少了hash冲突,缺点是计算量较大,而且有可能出现超出整型范围的情况,需要对计算结果进行取模。
后续采用的是按位相加的hash算法,所以bce的hashcode是10:
第二步,生成主串当中第一个等长子串的hashcode。
由于主串通常要长于模式串,把整个主串转化成hashcode是没有意义的,只有比较主串当中和模式串等长的子串才有意义。
因此,首先生成主串中第一个和模式串等长的子串hashcode,
即abb = 1 + 2 + 2 = 5:
第三步,比较两个hashcode。
显然,5!=10,说明模式串和第一个子串不匹配,继续下一轮比较。
第四步,生成主串当中第二个等长子串的hashcode。
bbc = 2 + 2 + 3 = 7:
第五步,比较两个hashcode。
显然,7!=10,说明模式串和第二个子串不匹配,继续下一轮比较。
第六步,生成主串当中第三个等长子串的hashcode。
bce= 2 + 3 + 5 = 10:
第七步,比较两个hashcode。
显然,10 ==10,两个hash值相等!这是否说明两个字符串也相等呢?
别高兴的太早,由于存在hash冲突的可能,还需要进一步验证。
第八步,逐个字符比较两字符串。
hashcode的比较只是初步验证,之后还需要像BF算法那样,对两个字符串逐个字符比较,最终判断出两个字符串匹配。
最后得出结论,模式串bce是主串abbcefgh的子串,第一次出现的下标是2。
③ RK 算法的优化
每次hash的时间复杂度是0(n),如果把全部子串都进行hash,总的时间复杂度是0(mn)。
解决方法:对子串的hash计算并不是独立的,从第二个子串开始,每一个子串的 hash都可以由上一个子串进行简单的增量计算来得到:
上图中,我已知子串abbcefg的hashcode是26,那么如何计算下一个子串,也就是bbcefgd的hashcode呢?
由于新子串的前面少了一个a,后面多了一个d,所以:
新hashcode = 旧hashcode - 1 + 4 = 26-1+4 = 29
再下一个子串bcefgde的计算也是同理:
新hashcode = 旧hashcode - 2 + 5 = 29-2+5 = 32
④ RK 算法代码实现
⑤ RK 算法时间复杂度
RK算法计算单个子串hash 的时间复杂度是0 (n),但由于后续的子串hash是增量计算,所以总的时间复杂度仍然是0 (n)。
⑥ RK 算法缺点
RK算法的缺点在于哈希冲突。每一次哈希冲突的时候,RK算法都要对子串和模式串进行逐个字符的比较,如果冲突太多了,算法就退化成了BF算法。
BM算法的名字取自于它的两位发明者,计算机科学家Bob Boyer和JStrother Moore。
① BM 算法的核心
采用字符比较的思路,并且尽量减少无谓的比较,这就是BM算法的努力方向。
为了能减少比较,BM算法制定了两条规则,一个是[坏字符规则],一个是[好后缀规则]。
坏字符规则
② BM 算法 - 坏字符规则 推演
“坏字符” 是什么意思?就是指模式串和子串当中不匹配的字符。
当模式串和主串的第一个等长子串比较时,子串的最后一个字符T就是坏字符:
为什么坏字符不是主串第2位的字符T呢?那个位置不是最先被检测到的吗?
BM算法的检测顺序相反,是从字符串的最右侧向最左侧检测。
当检测到第一个坏字符之后,只有模式串与坏字符T对齐的位置也是字符T的情况下,两者才有匹配的可能。
模式串的第1位字符也是T,直接把模式串当中的字符T和主串的坏字符对齐,进行下一轮的比较:
坏字符的位置越靠右,下一轮模式串的挪动跨度就可能越长,节省的比较次数也就越多。这就是BM算法从右向左检测的好处。
接下来,继续逐个字符比较,发现右侧的G、C、G都是一致的,但主串当中的字符A,是又一个坏字符:
按照刚才的方式,找到模式串的第2位字符也是A,于是我们式串的字符A和主串中的坏字符对齐,进行下一轮比较:
接下来,继续逐个字符比较,这次发现全部字符都是匹配的,比较公正完成:
如果坏字符在模式串中不存在,又该如何挪动呢?
直接把模式串挪到主串坏字符的下一位:
③ BM 算法 - 坏字符规则 代码实现
BM算法的[阉割版](坏字符规则只是BM算法的两大法宝之一,除此之外它还具备另一件法宝:[好后缀规则]。)
好后缀规则
④ BM 算法 - 好后缀规则 推演
“好后缀” 又是什么意思?就是指模式串和子串当中相匹配的后缀。
对于上面的例子,如何继续使用“坏字符规则”,会有怎样的效果呢?
从后向前比对字符,发现后面三个字符都是匹配的,到了第四个字符的时候,发现坏字符G:
接下来在模式串找到了对应的字符G,但是按照坏字符规则,模式串仅仅能够向后挪动一位:
这时候坏字符规则显然并没有起到作用,为了能真正减少比较次数,轮到好缀规则出场了。
回到第一轮的比较过程,发现主串和模式串都有共同的后缀“GCG”,这就是所谓的“好后缀”。
如果模式串其他位置也包含与“GCG”相同的片段,那么就可以挪动模式串,让这个片段和好后缀对齐,进行下一轮的比较:
显然,在这个例子中,采用好后缀规则能够让模式串向后移动更多位,节省了更多无谓的比较。
那么,如果模式串当中并不存在其他与好后缀相同的片段,该怎么处理?是不是可以直接把模式串挪到好后缀之后?
不能直接挪动模式串到好后缀的后面,还要先判断一种特殊情况:
模式串的前缀是否和好后缀的后缀相匹配,免得挪过头了。
⑤ 在什么情况下应该使用坏字符规则,什么情况下应该使用好后缀规则呢?
举个例子,假设坏字符规则可以让模式串在下一轮挪动3位,好后缀规则可以让模式串在下一轮挪动5位,那就采用好后缀规则。
在每一轮的字符比较之后,按照坏字符和好后缀规则分别计算相应的挪动距离,哪一种距离更长,就把模式串挪动对应的长度。
⑥ BM 算法 - 好后缀规则 代码实现
unsigned int sum(unsigned int n);
数列自身递归定义:1,1,2,3,5,8,13,21,...
unsigned int Fibonacci(unsigned int n);
unsigned int strlen(const char* s);
Node* merge(Node* list1, Node* list2)
void HanoiTower(int n, char a, char b, char c)
递归还能用于需要回溯穷举的场合。
void r_print_even(Node* list)
在一个8×8的国际象棋棋盘上,有8个皇后,每个皇后占一格;要求皇后间不会出现相互“攻击”的现象
(不能有两个皇后处在同一行、同一列或同一对角线上)。
1.初始化:j = 1
2.初始化:i = 1
3.从第j行开始,恢复 i 的有效值(通过函数调用栈进行回溯),判断第i个位置
a. 位置i可放入皇后:标记位置(i,j), j++,转步骤2
b. 位置i不可放入皇后:i++,转步骤a
c. 当i>8时,j--,转步骤3
结束:
第8行有位置可放入皇后