CSP-S 2022 提高级 第一轮 阅读程序(1)

该程序实现了一个优化后的字符串模式匹配算法,通过shift数组存储字符对应的位移量以提高效率。当输入字符串s和t时,返回t在s中首次出现的位置,否则输出-1。文章分析了算法的时间复杂度、功能与C++标准库中的字符串函数的比较,并提供了实例分析。
摘要由CSDN通过智能技术生成

【题目】

CSP-S 2022 提高级 第一轮 阅读程序(1)

01 #include <iostream> 
02 #include <string> 
03 #include <vector> 
04 
05 using namespace std; 
06 
07 int f(const string &s, const string &t) 
08 { 
09     int n = s.length(), m = t.length(); 
10 
11     vector<int> shift(128, m + 1);
12 
13     int i, j;
14 
15     for (j = 0; j < m; j++) 
16         shift[t[j]] = m - j; 
17 
18     for (i = 0; i <= n - m; i += shift[s[i + m]]) { 
19         j = 0; 
20         while (j < m && s[i + j] == t[j]) j++; 
21         if (j == m) return i;
22     }
23 
24     return -1; 
25 } 
26 
27 int main() 
28 { 
29     string a, b;
30     cin >> a >> b;
31     cout << f(a, b) << endl;
32     return 0;
33 }

假设输入字符串由 ASCII 可见字符组成,完成下面的判断题和单选题:
判断题
16. 当输入为“abcde fg”时,输出为-1。( )
17. 当输入为“abbababbbab abab”时,输出为 4。( )
18. 当输入为“GoodLuckCsp2022 22”时,第 20 行的“j++”语句执行次数为 2。( )

单选题
19. 该算法最坏情况下的时间复杂度为( )。
A. O(n+m) B. O(n log m) C. O(m log n) D. O(nm)
20. f(a, b)与下列( )语句的功能最类似。
A. a.find(b) B. a.rfind(b) C. a.substr(b) D. a.compare(b)
21. 当输入为“baaabaaabaaabaaaa aaaa”,第 20 行的“j++”语句执行次数为( )。
A. 9 B. 10 C. 11 D. 12

【题目考点】

1. 字符串
  • 字符串模式匹配
2. vector

vector初始化:
vector<元素类型> 对象名(元素个数,初始值)

例:
vector<int> v(10, 3);
生成一个vector<int>类型的对象v,其中包含10个元素,每个元素都是3。也就是说v.size()为10,v[0]~v[9]都是3。

【解题思路】

27 int main() 
28 { 
29     string a, b;
30     cin >> a >> b;
31     cout << f(a, b) << endl;
32     return 0;
33 }

先看主函数,输入两个字符串,由f函数处理,输出函数返回的一个什么值。

07 int f(const string &s, const string &t) 
08 { 
09     int n = s.length(), m = t.length(); 
10 
11     vector<int> shift(128, m + 1);

再看函数f,传入两个字符串s,t,先求出字符串长度。s的长度是n,t的长度是m。
而后声明了一个vector,名字叫shift。shift这个词除了由“上档键”的意思,还有“转移,移位”的意思。(其实根据单词可以判断出很多信息,各位同学平时要注意多学习英文单词。)
shift后面的括号中传入两个参数,这是使用了vector的构造函数,传入的第1个参数表明初始化元素的个数,第二个参数是每个元素的值。也就是说,声明出来的shift的长度(元素个数)shift.size()为128,这128个元素,即shift[0]~shift[127]的值都是m+1。至于这个shift是做什么用的,接着往下看。

13     int i, j;
14 
15     for (j = 0; j < m; j++) 
16         shift[t[j]] = m - j; 

t[j]是字符,作为shift的下标,也就是以字符的ASCII码为下标,这也对应了shift中要有128个元素。shift的t[j]位置要赋值为m-j,暂时无法理解。如果无法理解就继续向下看,不要纠结于一处,要大处着眼。

18     for (i = 0; i <= n - m; i += shift[s[i + m]]) { 
19         j = 0; 
20         while (j < m && s[i + j] == t[j]) j++; 
21         if (j == m) return i;
22     }
23 
24     return -1; 

先看for循环,i从0到n-m,i每次增加shift[s[i + m]]这么一个东西,看不出是什么。
再看循环内部,j从0循环到m-1,每次判断s[i+j]t[j]是否相等。如果看到有不相等的字符,就跳出。如果j遍历到最后,j已经为m,就返回i。
大家应该能看出这一段在做什么(否则就要反思一下自己字符串一节学得如何)这里就是在判断字符串s[i]~s[i+m-1]与字符串t是否相同。如果相同,则返回i。
结合for循环,i从0到n-m,不断比较s[i]~s[i+m-1]与字符串t是否相同,最后一次比较的就应该是s[n-m]~s[n-1]是否与t相同。如果i每次增加1,这就是我们熟悉的判断一个字符串在另一个字符串中出现的位置的代码,也叫字符串的模式匹配。最后的return -1意味着在s中没有找到t,t不是s的子串。
而i每次增加的不是1,而是shift[s[i + m]],显然应该是进行了某种优化。每次i增加1复杂度太高了,可以多加一些,减少循环次数。
在这里插入图片描述

结合上面的shift[t[j]] = m-j,以及for循环中的增量表达式i += shift[s[i + m]],i每次增加的量是由s[i+m]决定的。

  • 如果s[i+m]不是t中的字符,那么接下来看的s的子串中只要包含s[i+m],s中的子串与t就一定不能相同(不能匹配)。因此i应该增加m+1,下一次循环从i+m+1开始,看m个字符,看是否与t相同。这也是vector<int> shift(128, m + 1)将shift中元素的初值设为m+1的原因。
  • 如果s[i+m]是t中的字符,那么应该让s[i+m]与t中最后一个该字符对齐,接下来看能否匹配。
    在这里插入图片描述

s[i+m]为c,字符串t中最后一个字符c出现的下标为j,那么当t[j]s[i+m]对应时,t[0]s[i+m-j]对应,也就是说i应该增加m-j。
再结合

15     for (j = 0; j < m; j++) 
16         shift[t[j]] = m - j; 

以及i += shift[s[i + m]]
可知shift[c]表示当s[i+m]为c时,为了进行下一次有效的匹配,i应该增加的量。
如果t[j]在字符串中重复出现,j更大时shift[t[j]]的值会更新,即shift[c]保存的是字符串t中最后一个c与s[i+m]对应时,i应该增加的量。

整个程序就是优化后的字符串模式匹配,输入字符串a, b

  • 如果b是a的子串,输出b在a中第一次出现的位置
  • 如果b不是a的子串,输出-1。
判断题

16. 当输入为“abcde fg”时,输出为-1。( )
答:T。
fg不是abcde的子串,输出-1,正确。
17. 当输入为“abbababbbab abab”时,输出为 4。( )
答:F。
abab在abbababbbab中第一次出现的位置为3,不是4。错误。
18. 当输入为“GoodLuckCsp2022 22”时,第 20 行的“j++”语句执行次数为 2。( )
答:T。
t字符串为"22",模式串长度m=2
shift['2']=m-j=2-1=1
i为0,s[0]为’G’,‘G’和2不同,s[i+2]为’o’,shift['o']为m+1即3,i增加3
i为3,s[3]为’d’,i增加3。
i为6,s[6]为’c’,i增加3。
i为9,s[9]为’s’,s[i+2]为’2’,shift['2']为1,i增加1。
i为10,s[10]为’p’,s[i+2]为’0’,i增加3。
i为13,s[13]为’2’,执行两次j++后,j==m,直接跳出,返回结果。

单选题

19. 该算法最坏情况下的时间复杂度为( )。
A. O(n+m) B. O(n log m) C. O(m log n) D. O(nm)

答:选D。
比如s是"aaaaaaaa",t是”bbbba”,那么shift['a']为1,i每次增加1,都不能匹配。整体复杂度会退化成没有优化的基本字符串模式匹配。每次匹配都要循环近m词,共进行(n-m)m次,当n >= m时, O ( ( n − m ) m ) = O ( n m ) O((n-m)m) = O(nm) O((nm)m)=O(nm)

20. f(a, b)与下列( )语句的功能最类似。
A. a.find(b) B. a.rfind(b) C. a.substr(b) D. a.compare(b)

答:选A。
f函数实现了字符串查找,如果b不是a的子串则返回-1。
string类的成员函数find也实现了相同的功能。
21. 当输入为“baaabaaabaaabaaaa aaaa”,第 20 行的“j++”语句执行次数为( )。
A. 9 B. 10 C. 11 D. 12

答:选B
手动运行,在纸上执行程序。
shift['a']为1。
i为0,baaa中的第一个b与aaaa中的第1个a不同,直接跳过。此时s[i+m]是b,shift['b']为m+1,i直接增加m+1,也就是5。
i为5,指向第2组baaa中的第1个a。匹配3个a,j++执行3次,遇到b与a不相等。此时s[i+m]是a,shift['a']为1,i增加1。
i为6,指向第2组baaa中的第2个a。匹配2个a,j++执行2次,遇到b与a不相等。此时s[i+m]是a,shift['a']为1,i增加1。
i为7,指向第2组baaa中的第3个a。匹配1个a,j++执行1次,遇到b与a不相等。此时s[i+m]是a,shift['a']为1,i增加1。
i为8,指向第3组baaa中的第1个b。此时s[i+m]是b,shift['b']为m+1,i直接增加m+1,变为13。
i为13,执行字符串最后aaaa中的第1个a,与模式串aaaa匹配4个a,j++执行4次。
j++总计执行10次。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值