【有书共读】《算法与数据结构题目最优解》(在线编程题总结5)

该书第五章——《字符串问题》,该部分我已总结到个人博客:https://blog.csdn.net/zichen_ziqi/article/details/80848995。具体内容如下:

0、参考博客或网址:

(1)菜鸟教程——C++字符串

(2)https://blog.csdn.net/ylh1234/article/details/64929992

(3)https://blog.csdn.net/fenxinzi557/article/details/51457829

(4)官网教程:http://www.cplusplus.com/reference/string/string/

(5)教程中的注释:https://www.jb51.net/article/37410.htm

(6)详细的串定义与模式匹配算法:https://blog.csdn.net/smile_from_2015/article/details/61619658

1、串的定义

       串(字符串的简称)是由零个或多个字符组成的有限序列,一般记为s='a1a2a3…an',其中ai可以是字母,数字或者其他字符,零个字符的串称为空串

       串中任意个连续的字符组成的子序列称为该串的子串,包含子串的串相应的被称为主串,通常称字符在序列中的序号为该字符在串中的位置。子串在主串中的位置则以子串的第一个字符在主串中的位置来表示。两个串相等,当且仅当这两个串的值相等,即两串的长度相等,且各个对应位置的字符也相等。

       注意,空格串‘ ’与空串‘’不一样!!!为了清楚起见,书上用符号空集表示。


2、串的表示与实现

   2.1 定长顺序存储表示

       类似于线性表的顺序存储结构, 串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区。

       一般是用定长数组来定义。既然是定长数组,就存在一个预定义的最大串长度, 一般可以将实际的串长度值保存在数组的 0下标位置,有的书中也会定义存储在数组的最后一个下标位置。但也有些编程语言不想这么干,觉得存个数字占个空间麻烦。它规定在串值后面加一个不计入串长度的结束标记字符,比如"\0"来表示串值的终结,这个时候,你要想知道此时的串长度,就需要遍历计算一下才知道了,其实这还是需要占用一个空间,确实没必要。

       串的顺序存储方式其实是有问题的,因为字符串的操作,比如两串的连接Concat、新串的插入如Insert,以及字符串的替换 Replace,都有可能使得串序列的长度超过了数组的长度 MaxSize。因此为克服这个弊病,唯有不限定串长的最大长度,即动态分配串值的存储空间。

   2.2 堆分配存储表示

       这种存储表示的特点是,仍以一组地址连续的存储单元存放串值字符序列,但它们的存储空间是在程序执行过程中动态分配而得。

   2.3 串的块链存储表示

       和线性表的链式存储结构类似,也可以采用链表方式存储串值。由于串结构的特殊性——结构中每个数据元素是一个字符,则采用链表存储串值,存在一个“结点大小”的问题,即每个结点可以存放一个字符,也可以存放多个字符。因此,一个结点可以存放一个字符,也可以考虑存放多个字符,最后一个结点若是未被占满时,可以用"#"或其他非串值字符补全。


3、串的模式匹配算法

  3.1 求子串位置的定位函数 Index(S,T,pos)

      子串的定位操作通常称做串的模式匹配(T称为模式串,S称为主串)。

      其基本思想为:从主串S的第pos个字符起和模式串T中的第一个字符比较,若相等,则继续逐个比较后续字符;否则从主串的下一个字符起再重新和模式串T中的字符比较。依次类推,直至模式串T中的每个字符依次和主串S中的一个连续的字符序列相等,则称为匹配成功函数值为和模式串T中第一个字符相等的字符在主串S中的序号,否则称匹配不成功,函数值为0。该方法的时间复杂度为:O(n*m),但在实际情况下,其执行时间约为O(n+m),因此至今仍被采用。其具体函数为:
int Index(string S, string T, int pos){
    //返回子串T在主串S中第pos个字符之后的位置,若不存在,则函数值为0
    //其中,T为空,1<=pos<=S.length()
    int i = pos;
    int j = 1;
    while (i <= S[0] && j <= T[0]){
        if (S[i] == T[j]){
            ++i;++j;//继续比较后续字符
        }
        else{
            i = i - j + 2;//指针后退重新开始匹配
            j = 1;
        }
    }
    if (j > T[0])
        return i - T[0];
    else
        return 0;
}//Index

3.2 KMP算法

       KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息(已经匹配的部分中的对称信息),尽量减少模式串(待搜索词)与文本串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,该函数本身包含了模式串的局部匹配信息。其时间复杂度为O(n+m)。详细关于KMP算法的介绍见:https://blog.csdn.net/v_july_v/article/details/7041827

      为了便于理解这个next函数,举个简单的例子(牛客网字符串必考题)说明一下:

      题目:在KMP算法中,已知模式串为ADABCADADA,请写出模式串的next数组值?(A)

       A. 0,1,1,2,1,1,2,3,4,3

       B. 0,1,1,1,2,1,2,3,4,3

       C. 2,1,1,2,1,1,2,3,3,4

       D. 1,2,3,2,1,1,2,4,4,3

        next数组值的解法

          (1)求模式串中,前缀后缀最大的相同长度

                        A  D  A  B  C  A  D  A  D  A

                         0   0  1  0  0  1   2   3  2  3

          (2)将前缀数组后移一位,并将第一位,置-1

                         -1  0  0  1  0  0  1   2  3  2

          (3)将移位后的数组整体+1

                          0  1  1  2  1  1   2  3  4  3

      nextval数组值的解法(或者参考网址:解法):

          (1)在计算出next值得同时,如果a位字符与它next值指向的b位字符相等,则该a位的nextval就是指向b位的nextval值;

          (2)如果不等,则该a位的nextval值就是它自己a位的next的值。

            上面的nextval数组值为:

                          序号:                1    2   3   4   5   6   7   8   9  10

                          字符串:            A   D   A  B  C   A  D   A  D   A

                          next数组值:     0   1    1   2   1   1   2   3   4   3

                          nextval数组值:0   1    0   2    1   0  1   0   4    0


    C++代码为:

#include <iostream>
#include <vector>

using namespace std;

inline void NEXT(const string &T, vector<int> &next)
{
    //按模式串生成vector,next(T.size())
    next[0] = -1;
    for (int i = 1; i<T.size(); i++) {
        int j = next[i - 1];
        while (T[i] != T[j + 1] && j >= 0)
            j = next[j];//递推计算
        if (T[i] == T[j + 1])
            next[i] = j + 1;
        else
            next[i] = 0;
    }
}

inline string::size_type COUNT_KMP(const string &S, const string &T)
{
    //利用模式串T的next函数求T在主串S中的个数count的KMP算法
    //其中T非空,
    vector<int> next(T.size());
    NEXT(T, next);
    string::size_type index, count = 0;
    for (index = 0; index<S.size(); ++index){
        int pos = 0;
        string::size_type iter = index;
        while (pos<T.size() && iter<S.size()){
            if (S[iter] == T[pos]){
                ++iter;
                ++pos;
            }
            else {
                if (pos == 0)
                    ++iter;
                else
                    pos = next[pos - 1] + 1;
            }
        }//whileend
        if (pos == T.size() && (iter - index) == T.size())
            ++count;
    }//forend
    return count;
}

int main(void)
{
    string S = "abaabcacabaabcacabaabcacabaabcacabaabcac";
    string T = "ab";
    string::size_type count = COUNT_KMP(S, T);
    cout << "模式串T在主串S中出现的次数为:"<< count << endl;
    system("pause");
    return 0;
}// 模式串T在主串S中出现的次数为:10

4、实例分析

若使用C++标准库,则添加#include <string>,其主要的函数见文章:C、C++字符串的函数汇总

# include <iostream>
# include <string>
using namespace std;

int main()
{
    string str = "when i was young, i listen to radio.";
    string::size_type position;

    position = str.find("listen");

    if (position != str.npos) //npos是个很大的数,如果没找到就会返回npos的值给position
    {
        cout << "第一次出现的下标是:" << position << endl;
    }

    //反向查找子串在str中最后出现的位置
    string flag = "to";
    position = str.rfind(flag);
    cout << "str.rfind(flag):" << position << endl;
    getchar();
    return 0;
}

// 20
//27
#笔试题目##C++工程师##算法工程师##秋招#
全部评论

相关推荐

评论
点赞
5
分享
牛客网
牛客企业服务