考研复习 Day 25 | 习题--计算机网络第三章(数据链路层 上)、数据结构(串)

注:以下习题参考 计算机网络(第八版)谢希仁 编著,数据结构与算法 王曙燕 主编。

一、计算机网络---数据链路层(上)习题与解答

3-01 数据链路(即逻辑链路)与链路(即物理链路)有何区别?"链路接通了"与"数据链路接通了"的区别何在?

答案:

数据链路与链路的区别:

  • 链路:物理链路,指相邻节点间的物理线路(如双绞线、光纤),是传输的"通道",仅提供比特流传输能力。

  • 数据链路:逻辑链路,在物理链路基础上增加了通信协议(如PPP、以太网),实现数据的可靠传输,包括帧的封装、差错检测、流量控制等功能。

"链路接通了"与"数据链路接通了"的区别:

  • 链路接通了:物理连接建立,信号可以在上面传输,但还不能进行有效的数据交换。

  • 数据链路接通了:在物理链路基础上,通过协议建立了逻辑连接,可以进行帧的收发和差错控制,数据通信功能就绪。


3-02 数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。

答案:

链路控制功能:

  1. 封装成帧(确定帧边界)

  2. 透明传输(解决数据中特殊字符问题)

  3. 差错检测(CRC、校验和)

  4. 流量控制(协调收发速率)

  5. 链路管理(建立、维持、释放连接)

可靠链路层的优点:

  • 减少上层重传负担

  • 提高传输效率,适应高误码率环境(如无线网络)

  • 可在底层及早发现并纠正错误

可靠链路层的缺点:

  • 增加协议开销(确认、序号、重传机制)

  • 降低实时性(等待确认)

  • 复杂度和成本提高

  • 对于误码率极低的光纤网络,可靠性不必要


3-03 网络适配器的作用是什么?网络适配器工作在哪一层?

答案:

作用:

  • 实现数据串行/并行转换

  • 进行信号编码/解码(如曼彻斯特编码)

  • 实现CSMA/CD等媒体接入控制

  • 完成CRC生成与校验

  • 缓存收发数据帧

  • 识别MAC地址,过滤非本机帧

工作层次: 数据链路层 + 物理层(横跨两层)


3-04 数据链路层的三个基本问题(封装成帧、透明传输和差错检测)为什么都必须加以解决?

答案:

问题 为什么必须解决
封装成帧 区分比特流中的帧边界,否则接收端无法识别一个完整的数据单元
透明传输 防止数据中的特殊字符被误认为控制字符,保证任意二进制数据都能正确传输
差错检测 检测传输中的比特错误,避免将错误数据交给上层

3-05 如果在数据链路层不进行封装成帧,会发生什么问题?

答案:

接收端无法从比特流中提取出完整的数据单元,不知道数据从哪开始、到哪结束,导致数据无法正确解析。物理层传输的是原始比特流,没有帧边界信息,接收端无法知道哪些比特属于同一个数据包。


3-06 PPP协议的主要特点是什么?为什么PPP不使用编号?PPP适用于什么情况?为什么PPP协议不能使数据链路层实现可靠传输?

答案:

主要特点:

  • 简单,只提供检错不纠错

  • 支持多种网络层协议(IP、IPX等)

  • 支持同步/异步传输

  • 提供LCP(链路控制协议)和NCP(网络控制协议)

  • 支持身份认证(PAP、CHAP)

为什么不使用编号: PPP设计用于点对点链路,误码率低,假设传输可靠;使用编号会增加开销而不必要。差错恢复由上层TCP完成。

适用范围: 拨号上网、DSL、VPN隧道、路由器间点对点链路等。

为什么不能实现可靠传输: PPP只提供CRC检错,不提供确认机制、序号、重传机制。若数据出错,PPP仅丢弃错误帧,可靠传输需由上层(如TCP)提供。


3-07 要发送的数据为1101011011。采用CRC的生成多项式是P(X)=X⁴+X+1。试求应添加在数据后面的余数。

若要发送的数据在传输过程中最后一个1变成了0,即变成了1101011010,问接收端能否发现?

若要发送的数据在传输过程中最后两个1都变成了0,即变成了1101011000,问接收端能否发现?
采用CRC检验后,数据链路层的传输是否就变成了可靠的传输?

答案:

CRC计算过程:

  • 数据 M = 1101011011

  • P(X) = X⁴ + X + 1 = 10011(4次,余数4位)

  • M后加4个0:11010110110000

  • 除以10011(模2除法)

11010110110000 ÷ 10011 = 1100001010 余 1110

余数:1110(添加在数据后面)

错误检测:

错误情况 接收码字 除以10011余数 能否发现
最后一个1变0 1101011010 + 1110 非0
最后两个1变0 1101011000 + 1110 非0

CRC后传输是否可靠?
不是。CRC只能检测差错(不能纠错),且存在漏检可能(当错误能被生成多项式整除时)。可靠传输需要确认和重传机制。


3-08 要发送的数据为101110。采用CRC的生成多项式是P(X)=X³+1。试求应添加在数据后面的余数。

答案:

  • 数据 M = 101110

  • P(X) = X³ + 1 = 1001(3次,余数3位)

  • M后加3个0:101110000

  • 除以1001(模2除法)

101110000 ÷ 1001 = 101011 余 011

余数:011


3-09 一个PPP帧的数据部分(用十六进制写出)是7D 5E FE 27 7D 5D 7D 5D 65 7D 5E。试问真正的数据是什么(用十六进制写出)?

答案:

PPP转义规则:

  • 7D 5E → 7E(0x7D转义后的0x5E表示原0x7E)

  • 7D 5D → 7D(0x7D转义后的0x5D表示原0x7D)

还原过程:

  • 7D 5E → 7E

  • FE → FE

  • 27 → 27

  • 7D 5D → 7D

  • 7D 5D → 7D

  • 65 → 65

  • 7D 5E → 7E

真正数据:7E FE 27 7D 7D 65 7E


3-10 PPP协议使用同步传输技术传送比特串0110111111111100。试问经过零比特填充后变成怎样的比特串?若接收端收到的PPP帧的数据部分是0001110111110111110110,试问删除发送端加入的零比特后会变成怎样的比特串?

答案:

发送端填充(零比特填充法): 每遇到连续5个1,就在其后插入一个0。

原始比特串:0110111111111100

  • 第1次遇到5个1:011011111 → 插入0 → 0110111110

  • 剩余:1111100 → 其中11111后插入0 → 11111000

结果:011011111011111000

接收端删除:

接收:0001110111110111110110

删除连续5个1后的0:

  • 000111011111 0 11111 0 110 → 000111011111 11111 110

最终:00011101111111111110


3-11 试分别讨论以下各种情况在什么条件下是透明传输,在什么条件下不是透明传输。

(1) 普通的电话通信。
(2) 互联网提供的电子邮件服务。

答案:

场景 是否透明 条件/原因
普通电话通信 不透明 模拟信号频率受限(300-3400Hz),数字数据需调制,无法直接传输任意二进制数据
互联网电子邮件 透明 邮件经过Base64或QP编码,将任意二进制数据转换为可打印ASCII字符,保证透明传输

3-12 PPP协议的工作状态有哪几种?当用户要使用PPP协议和ISP建立连接进行通信时,需要建立哪几种连接?每一种连接解决什么问题?

答案:

PPP工作状态(6种):

  1. 链路静止(物理层未就绪)

  2. 链路建立(LCP协商)

  3. 鉴别(可选,验证身份)

  4. 网络层协议(NCP协商)

  5. 链路打开(数据传输)

  6. 链路终止(断开连接)

需要建立的连接:

连接类型 解决的问题
物理连接 解决信号传输问题,有线/无线物理链路建立
LCP连接 协商MRU(最大接收单元)、认证协议、链路质量监测
NCP连接 配置网络层协议(如IP地址分配、压缩协议)

3-13 局域网的主要特点是什么?为什么局域网采用广播通信方式而广域网不采用呢?

答案:

局域网主要特点:

  • 覆盖范围小(几公里内)

  • 高数据率(10Mbps~100Gbps)

  • 低时延、低误码率

  • 所有权自属(私有)

  • 采用广播/多路访问方式

广播原因:

  • 局域网地理范围小,广播开销小

  • 所有站点共享同一物理信道,天然适合广播

  • 广域网范围大(跨城市/国家),广播会造成网络风暴,严重影响性能

  • 广域网采用点对点交换方式,更高效可靠


3-14 常用的局域网的网络拓扑有哪些种类?现在最流行的是哪种结构?为什么早期的以太网选择总线拓扑结构而不使用星形拓扑结构,但现在却改为使用星形拓扑结构呢?

答案:

常用拓扑种类: 总线型、星型、环型、树型、网状

现在最流行: 星型(以太网)

总线→星型的原因:

时期 拓扑 原因
早期 总线型 成本低,安装简单,使用粗/细同轴电缆,无需中心设备
现在 星型 使用双绞线和交换机,故障隔离好(单点故障不影响全局),易扩展,性能高(全双工),管理方便

3-15 什么叫作传统以太网?以太网有哪两个主要标准?

答案:

传统以太网 :指IEEE 802.3标准的10 Mbit/s以太网,使用CSMA/CD协议,最早采用总线型拓扑。

两个主要标准:

  • DIX Ethernet V2(DEC-Intel-Xerox联合制定)

  • IEEE 802.3(国际标准化组织采纳)

两者兼容,主要区别是帧格式略有不同。


3-16 数据率为10 Mbit/s的以太网在物理媒体上的码元传输速率(即码元/秒)是多少?

答案:

10 Mbit/s以太网使用曼彻斯特编码,每个比特对应两个码元(一个比特周期内有一次电平跳变)。

码元速率 = 数据率 × 2 = 10 × 2 = 20 Mbaud(20×10⁶ 码元/秒)


3-17 为什么LLC子层的标准已制定出来了但现在却很少使用?

答案:

LLC(逻辑链路控制)子层原设计用于屏蔽不同MAC层的差异,提供统一的链路层服务接口。

很少使用的原因:

  • 以太网一统天下,MAC层基本统一(IEEE 802.3)

  • IP协议直接封装在MAC帧上,不需要LLC层

  • 以太网帧已足够简单高效,添加LLC增加开销

  • 仅在少数场合(如无线局域网)使用LLC


3-18 试说明10BASE-T中的"10""BASE"和"T"所代表的意思。

答案:

部分 含义
10 数据率 10 Mbit/s
BASE 基带传输(Baseband),信号占用整个介质带宽
T 双绞线(Twisted pair)

3-19 以太网使用的CSMA/CD协议是以争用方式接入到共享信道的,这与传统的时分复用TDM相比有何优缺点?

答案:

对比项 CSMA/CD TDM
信道利用率 动态,轻载时高(接近100%),重载时因冲突降低 固定,有浪费(空闲时隙无法被其他站使用)
时延 可变(冲突时重传) 固定(每个站有固定时隙)
实现复杂度 较低 较高(需全网同步)
适用场景 突发数据、局域网 恒定速率、实时业务(如电话)
可扩展性 受冲突域限制 可无限扩展(需同步)

3-20 假定1km长的CSMA/CD网络的数据率为1Gbit/s。设信号在网络上的传播速率为200000km/s。求能够使用此协议的最短帧长。

答案:

已知:

  • 长度 L = 1 km

  • 数据率 R = 1 Gbit/s = 10⁹ bit/s

  • 传播速度 v = 200000 km/s

计算过程:

  • 传播时延 τ = L / v = 1 / 200000 = 5×10⁻⁶ s = 5 μs

  • 端到端往返时延 2τ = 10 μs = 10⁻⁵ s

最短帧长 = R × 2τ = 10⁹ × 10⁻⁵ = 10⁴ bit = 10000 bit

换算为字节: 10000 ÷ 8 = 1250 字节

答案:1250 字节


二、数据结构------串(String)习题与解答

一、单项选择题

(1) 下面关于串的叙述中,______是不正确的。

A. 串是字符的有限序列
B. 空串是由空格构成的串
C. 模式匹配是串的一种重要运算
D. 串既可以采用顺序存储,也可以采用链式存储

答案:B

解析:空串是长度为0的串,不含任何字符;由空格构成的串是空格串(长度≥1)。B混淆了空串和空格串。


(2) 串是一种特殊的线性表,其特殊性体现在______。

A. 可以顺序存储
B. 数据元素是一个字符
C. 可以链式存储
D. 数据元素可以是多个字符

答案:B

解析 :线性表的数据元素可以是任意类型,而串的数据元素被限定为单个字符


(3) 串的长度是指______。

A. 串中所含不同字母的个数
B. 串中所含字符的个数
C. 串中所含不同字符的个数
D. 串中所含非空格字符的个数

答案:B

解析 :串的长度定义为串中字符的总个数(包括空格)。


(4) 设有两个串p和q,其中q是p的子串,求q在p中首次出现的位置的算法称为______。

A. 求子串
B. 连接
C. 匹配
D. 求串长

答案:C

解析:模式匹配(Pattern Matching)就是求子串在主串中首次出现的位置。


(5) 若串S="software",其子串的个数是______。

A. 8
B. 37
C. 36
D. 9

答案:C(36)

解析:长度为n的串,非空子串个数 = n(n+1)/2 = 8×9/2 = 36。


二、填空题

题目:

(1) 含零个字符的串称为______串。任何串中所含______的个数称为该串的长度。

(2) 空格串是指______,其长度等于______。

(3) 当且仅当两个串的______相等并且各个对应位置上的字符都______时,这两个串相等。
一个串中任意个连续字符组成的序列称为该串的______,该串称为它所有子串的______串。

(4) 设串S='ABCDEFG',t='EFG',g='XYZ',求下列操作的结果。

  • StrLength(s),结果是______

  • StrReplace(s,t,g),串s是______

  • StrIndex(s,t,3),结果是______

  • SubString(str,s,3,5),串str是______

  • StrCat(s,g),串s是______

(5) 设串s1='ABCDEFG',s2='PQRST',则执行下列操作后,求串s1,s2。

  • SubString(t1, s1, 2, StrLength(s2)),串t1是______

  • SubString(t2, s1, StrLength(s2), 2),串t2是______

  • StrCat(t1, t2),串t1是______

(6) 模式串s='abcabaa'的next函数值序列是______,nextval函数值序列是______;
模式串='abcaabbabcabaacbacba'的next函数值序列是______,nextval函数值序列是______。


解答

(1)

答案: 含零个字符的串称为空串 。任何串中所含字符的个数称为该串的长度。

解析:

  • 空串(Empty String):长度为0,不含任何字符,记作""。

  • 串的长度:串中字符的总个数(包括空格)。


(2)

答案: 空格串是指由空格字符构成的串(长度≥1) ,其长度等于空格字符的个数

解析:

  • 空格串(Blank String):全部由一个或多个空格字符组成的串。

  • 空串与空格串不同:空串长度为0,空格串长度≥1。


(3)

答案: 当且仅当两个串的长度 相等并且各个对应位置上的字符都相等 时,这两个串相等。
一个串中任意个连续字符组成的序列称为该串的子串 ,该串称为它所有子串的主串

解析:

  • 串相等条件:长度相等 + 对应位置字符相等。

  • 子串(Substring):原串中连续字符构成的序列。

  • 主串(Main String):包含子串的原串。


(4)

已知: S = 'ABCDEFG',t = 'EFG',g = 'XYZ'

操作 结果 解析
StrLength(s) 7 字符串'ABCDEFG'包含7个字符
StrReplace(s,t,g) 'ABCDXYZ' 将S中的"EFG"替换为"XYZ"
StrIndex(s,t,3) 5 从第3个字符(C)开始找"EFG","EFG"起始于位置5
SubString(str,s,3,5) 'CDEFG' 从位置3取长度为5的子串:C,D,E,F,G
StrCat(s,g) 'ABCDEFGXYZ' 将S和g连接:S + g

(5)

已知: s1 = 'ABCDEFG',s2 = 'PQRST'
注意: StrLength(s2) = 5

操作 结果 解析
SubString(t1, s1, 2, StrLength(s2)) 'BCDEF' 从s1的位置2取长度为5:B,C,D,E,F
SubString(t2, s1, StrLength(s2), 2) 'EF' StrLength(s2)=5,从位置5取长度为2:E,F
StrCat(t1, t2) 'BCDEFEF' 连接t1和t2:'BCDEF' + 'EF' = 'BCDEFEF'

(6)

模式串 s = 'abcabaa'(长度7,位置从1开始)

next数组计算(KMP原版定义):

位置j 字符 next[j] 计算过程
1 a 0 约定
2 b 1 无相同前后缀
3 c 1 无相同前后缀
4 a 1 无相同前后缀
5 b 2 前缀"a"与后缀"a"匹配,长度1+1=2
6 a 3 前缀"ab"与后缀"ab"匹配,长度2+1=3
7 a 2 前缀"a"与后缀"a"匹配,长度1+1=2

next数组答案: (0, 1, 1, 1, 2, 3, 2)

nextval数组计算(优化版):

  • nextval[1] = 0

  • j=2:s[2]=b,next[2]=1,s[1]=a≠b → nextval[2]=1

  • j=3:s[3]=c,next[3]=1,s[1]=a≠c → nextval[3]=1

  • j=4:s[4]=a,next[4]=1,s[1]=a匹配 → nextval[4]=nextval[1]=0

  • j=5:s[5]=b,next[5]=2,s[2]=b匹配 → nextval[5]=nextval[2]=1

  • j=6:s[6]=a,next[6]=3,s[3]=c≠a → nextval[6]=3

  • j=7:s[7]=a,next[7]=2,s[2]=b≠a → nextval[7]=2

nextval数组答案: (0, 1, 1, 0, 1, 3, 2)


模式串 s = 'abcaabbabcabaacbacba'

串长: 20个字符

字符位置与字符对照表:

位置j 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
字符 a b c a a b b a b c a b a a c b a c b a

next数组计算(逐位推导):

位置j 字符 最大相等前后缀长度 next[j]
1 a 0 0
2 b 0 1
3 c 0 1
4 a 0 1
5 a 1(前缀"a"=后缀"a") 2
6 b 0 1
7 b 0 1
8 a 0 1
9 b 1(前缀"a"=后缀"a") 2
10 c 2("ab"=前缀"ab") 3
11 a 3("abc"=前缀"abc") 4
12 b 4("abca"=前缀"abca") 5
13 a 5("abcab"=前缀"abcab") 6
14 a 6("abcaba"=前缀"abcaba") 7
15 c 4("abca"=后缀"abca") 5
16 b 5("abcab"=后缀"abcab") 6
17 a 6("abcaba"=后缀"abcaba") 7
18 c 7("abcabac"=后缀"abca"? 需谨慎) 8
19 b 8("abcabacb") 9
20 a 9("abcabacba") 10

next数组答案:
(0, 1, 1, 1, 2, 1, 1, 1, 2, 3, 4, 5, 6, 7, 5, 6, 7, 8, 9, 10)

nextval数组计算(优化):
当 s[j] = s[next[j]] 时,nextval[j] = nextval[next[j]],否则 nextval[j] = next[j]

位置j 字符 next[j] 比较 nextval[j]
1 a 0 - 0
2 b 1 s[2]=b, s[1]=a, 不等 1
3 c 1 s[3]=c, s[1]=a, 不等 1
4 a 1 s[4]=a, s[1]=a, 相等 → nextval[1]=0 0
5 a 2 s[5]=a, s[2]=b, 不等 2
6 b 1 s[6]=b, s[1]=a, 不等 1
7 b 1 s[7]=b, s[1]=a, 不等 1
8 a 1 s[8]=a, s[1]=a, 相等 → nextval[1]=0 0
9 b 2 s[9]=b, s[2]=b, 相等 → nextval[2]=1 1
10 c 3 s[10]=c, s[3]=c, 相等 → nextval[3]=1 1
11 a 4 s[11]=a, s[4]=a, 相等 → nextval[4]=0 0
12 b 5 s[12]=b, s[5]=a, 不等 5
13 a 6 s[13]=a, s[6]=b, 不等 6
14 a 7 s[14]=a, s[7]=b, 不等 7
15 c 5 s[15]=c, s[5]=a, 不等 5
16 b 6 s[16]=b, s[6]=b, 相等 → nextval[6]=1 1
17 a 7 s[17]=a, s[7]=b, 不等 7
18 c 8 s[18]=c, s[8]=a, 不等 8
19 b 9 s[19]=b, s[9]=b, 相等 → nextval[9]=1 1
20 a 10 s[20]=a, s[10]=c, 不等 10

nextval数组答案:
(0, 1, 1, 0, 2, 1, 1, 0, 1, 1, 0, 5, 6, 7, 5, 1, 7, 8, 1, 10)


三、简答题

(1) 空串与空格串有何区别?

答案:

空串 空格串
定义 长度为0,不含任何字符 长度≥1,全部由空格字符组成
表示 "" " " 或 " "
是否相等 所有空串相等 长度不同则不等

(2) 两个字符串相等的充要条件是什么?

答案:

  1. 长度相等

  2. 各个对应位置上的字符相等


(3) 已知s='(xyz)+**',t='(x+z)**y'。利用连接、求子串和置换等基本运算,将s转化为t。

答案:

设基本操作:

  • SubString(str, s, pos, len):取子串

  • StrCat(str1, str2):连接

  • StrReplace(str, substr1, substr2):替换子串

使用替换操作:

  1. StrReplace(s, 'xyz', 'x+z')'(x+z)+*'

  2. StrReplace(s, '+*', '*y')'(x+z)*y'

使用子串拼接:

t = StrCat ( StrCat ( StrCat ( SubString (s,1,1), ' x+z ' ), SubString (s,5,1) ), ' *y ' )

注:还有很多其他方法,这里是比较简单的几种


(4) 已知主串s='adbadbbaabadabbadada',模式串t='adabbadada',写出模式串的nextval函数值,并由此画出KMP算法的匹配过程。

答案:

模式串t='adabbadada',长度10(位置1~10)

nextval计算过程:

位置j 字符 next[j] nextval[j]
1 a 0 0
2 d 1 1(a≠d)
3 a 1 1(a=a,取nextval[1]=0)
4 b 2 2(a≠b)
5 b 2 2(a≠b)
6 a 3 3(d≠a)
7 d 4 4(b≠d)
8 a 5 5(b≠a)
9 d 6 6(a≠d)
10 a 7 7(d≠a)

KMP匹配过程(略,需画图展示字符比较和指针移动)


四、算法设计题(核心思路)

(1) 假定下面所有的串均是顺序串,参数ch、ch1和ch2均是字符型,编写下列算法。

① 将串r中所有其值为ch1的字符换成ch2的字符。

核心思路:遍历串的每个字符,若当前字符等于ch1,则将其赋值为ch2。一次遍历即可完成。

C语言代码

复制代码
void ReplaceChar(SString* r, char ch1, char ch2) {
    for (int i = 0; i < r->length; i++) {
        if (r->ch[i] == ch1) r->ch[i] = ch2;
    }
}

C++代码

复制代码
void ReplaceChar(string& r, char ch1, char ch2) {
    for (char& c : r) {
        if (c == ch1) c = ch2;
    }
}

② 将串r中所有字符按照相反的次序仍存放在r中。

核心思路:双指针法。i指向串首,j指向串尾,交换i和j指向的字符,然后i++、j--,直到i≥j。原地逆置。

C语言代码

复制代码
void Reverse(SString* r) {
    for (int i = 0; i < r->length / 2; i++) {
        char temp = r->ch[i];
        r->ch[i] = r->ch[r->length - 1 - i];
        r->ch[r->length - 1 - i] = temp;
    }
}

C++代码

复制代码
void Reverse(string& r) {
    int i = 0, j = r.length() - 1;
    while (i < j) {
        swap(r[i++], r[j--]);
    }
}

③ 从串r中删除其值等于ch的所有字符。

核心思路:双指针覆盖法。慢指针k指向新串末尾,遍历原串,若字符不等于ch则复制到k位置并k++;若等于则跳过。最后更新串长度为k。

C语言代码

复制代码
void DeleteChar(SString* r, char ch) {
    int k = 0;
    for (int i = 0; i < r->length; i++) {
        if (r->ch[i] != ch) r->ch[k++] = r->ch[i];
    }
    r->length = k;
}

C++代码

复制代码
void DeleteChar(string& r, char ch) {
    int k = 0;
    for (int i = 0; i < r.length(); i++) {
        if (r[i] != ch) r[k++] = r[i];
    }
    r.resize(k);
}

④ 从串r1中第index个字符起求出首次与串r2相同的子串的起始位置。

核心思路:朴素模式匹配。从r1的第index-1位置开始,对每个可能的起始位置i,逐个字符比较与r2是否相等。若完全匹配则返回i+1,否则继续。

C语言代码

复制代码
int Index(SString r1, SString r2, int index) {
    if (index < 1 || index > r1.length) return -1;
    for (int i = index - 1; i <= r1.length - r2.length; i++) {
        int j = 0;
        while (j < r2.length && r1.ch[i + j] == r2.ch[j]) j++;
        if (j == r2.length) return i + 1;
    }
    return -1;
}

C++代码

复制代码
int Index(const string& r1, const string& r2, int index) {
    if (index < 1 || index > r1.length()) return -1;
    size_t pos = r1.find(r2, index - 1);
    return (pos != string::npos) ? pos + 1 : -1;
}

(2) 编写算法,实现顺序串的基本操作StrCompare(S, T)。

核心思路:逐个字符比较。找到第一个不相等的位置,返回两字符ASCII码差。若一个串是另一个串的前缀,则返回长度差。0表示相等。

C语言代码

复制代码
int StrCompare(SString S, SString T) {
    int i = 0;
    while (i < S.length && i < T.length) {
        if (S.ch[i] != T.ch[i]) return S.ch[i] - T.ch[i];
        i++;
    }
    return S.length - T.length;
}

C++代码

复制代码
int StrCompare(const string& S, const string& T) {
    return S.compare(T);
}

(3) 编写算法,实现顺序串的基本操作StrReplace(S, T, V)。

核心思路

  1. 遍历S,记录所有T出现的位置

  2. 计算新串长度 = lenS + 匹配次数×(lenV - lenT)

  3. 分配新空间,再次遍历S,遇到匹配位置则复制V,否则复制原字符

  4. 更新S为新串

C语言代码

复制代码
void StrReplace(SString* S, SString T, SString V) {
    // 计算匹配次数
    int count = 0;
    for (int i = 0; i <= S->length - T.length; i++) {
        int j = 0;
        while (j < T.length && S->ch[i + j] == T.ch[j]) j++;
        if (j == T.length) {
            count++;
            i += T.length - 1;
        }
    }
    
    // 计算新长度
    int newLen = S->length + count * (V.length - T.length);
    char newCh[MAXLEN];
    
    // 替换
    int p = 0, i = 0;
    while (i < S->length) {
        int j = 0;
        while (j < T.length && i + j < S->length && S->ch[i + j] == T.ch[j]) j++;
        if (j == T.length) {
            for (int k = 0; k < V.length; k++) newCh[p++] = V.ch[k];
            i += T.length;
        } else {
            newCh[p++] = S->ch[i++];
        }
    }
    
    // 更新S
    for (int i = 0; i < newLen; i++) S->ch[i] = newCh[i];
    S->length = newLen;
}

C++代码

复制代码
string StrReplace(const string& S, const string& T, const string& V) {
    string result;
    size_t i = 0;
    while (i < S.length()) {
        if (S.substr(i, T.length()) == T) {
            result += V;
            i += T.length();
        } else {
            result += S[i++];
        }
    }
    return result;
}

(4) 编写算法,实现顺序串的求逆串操作StrReverse(S, T)。

核心思路:遍历S,将S的第i个字符复制到T的第lenS-1-i个位置。

C语言代码

复制代码
void StrReverse(SString S, SString* T) {
    T->length = S.length;
    for (int i = 0; i < S.length; i++) {
        T->ch[i] = S.ch[S.length - 1 - i];
    }
}

C++代码

复制代码
string StrReverse(const string& S) {
    string T = S;
    reverse(T.begin(), T.end());
    return T;
}

(5) 假定S和T均是顺序串,编写算法,求串S和串T的一个最长公共子串。

核心思路:动态规划。定义dp[i][j]表示以S[i-1]和T[j-1]结尾的最长公共子串长度。若S[i-1]==T[j-1],则dp[i][j]=dp[i-1][j-1]+1,否则dp[i][j]=0。遍历过程中记录最大值及其结束位置,最后根据位置提取子串。

C语言代码

复制代码
void LongestCommonSubstring(SString S, SString T, SString* LCS) {
    int maxLen = 0, endPos = 0;
    int dp[MAXLEN][MAXLEN] = {0};
    
    for (int i = 0; i < S.length; i++) {
        for (int j = 0; j < T.length; j++) {
            if (S.ch[i] == T.ch[j]) {
                dp[i+1][j+1] = (i == 0 || j == 0) ? 1 : dp[i][j] + 1;
                if (dp[i+1][j+1] > maxLen) {
                    maxLen = dp[i+1][j+1];
                    endPos = i;
                }
            }
        }
    }
    
    LCS->length = maxLen;
    for (int i = 0; i < maxLen; i++) {
        LCS->ch[i] = S.ch[endPos - maxLen + 1 + i];
    }
}

C++代码

复制代码
string LongestCommonSubstring(const string& S, const string& T) {
    int maxLen = 0, endPos = 0;
    vector<vector<int>> dp(S.length() + 1, vector<int>(T.length() + 1, 0));
    
    for (int i = 0; i < S.length(); i++) {
        for (int j = 0; j < T.length(); j++) {
            if (S[i] == T[j]) {
                dp[i+1][j+1] = dp[i][j] + 1;
                if (dp[i+1][j+1] > maxLen) {
                    maxLen = dp[i+1][j+1];
                    endPos = i;
                }
            }
        }
    }
    return S.substr(endPos - maxLen + 1, maxLen);
}

(6) 假定S和T均是顺序串,编写算法,求串T在串S中出现的次数。

核心思路:朴素匹配或KMP。遍历S的每个可能起始位置,若匹配成功则计数+1,i跳转到匹配后位置。

C语言代码

复制代码
int CountOccurrences(SString S, SString T) {
    if (T.length == 0) return 0;
    int count = 0;
    for (int i = 0; i <= S.length - T.length; i++) {
        int j = 0;
        while (j < T.length && S.ch[i + j] == T.ch[j]) j++;
        if (j == T.length) {
            count++;
            i += T.length - 1;
        }
    }
    return count;
}

C++代码

复制代码
int CountOccurrences(const string& S, const string& T) {
    if (T.empty()) return 0;
    int count = 0;
    size_t pos = 0;
    while ((pos = S.find(T, pos)) != string::npos) {
        count++;
        pos += T.length();
    }
    return count;
}

(7) 编写算法,实现堆串的基本操作StrCompare(S, T)。

核心思路:堆串是动态分配的字符串,比较方法与顺序串相同:逐个字符比较,直到找到差异或一个串结束,返回差值。

C语言代码

复制代码
typedef struct {
    char* ch;
    int length;
} HString;

int StrCompare(HString S, HString T) {
    int i = 0;
    while (i < S.length && i < T.length) {
        if (S.ch[i] != T.ch[i]) return S.ch[i] - T.ch[i];
        i++;
    }
    return S.length - T.length;
}

C++代码:直接使用string的compare即可。


(8) 编写算法,实现堆串的基本操作StrReplace(S, T, V)。

核心思路:先遍历S计算匹配次数,再计算新串长度,分配新内存,复制过程中遇到匹配则复制V,最后释放S原内存,指向新串。

C语言代码

复制代码
void StrReplace(HString* S, HString T, HString V) {
    // 计算匹配次数
    int count = 0;
    for (int i = 0; i <= S->length - T.length; i++) {
        int j = 0;
        while (j < T.length && S->ch[i + j] == T.ch[j]) j++;
        if (j == T.length) {
            count++;
            i += T.length - 1;
        }
    }
    
    // 计算新长度
    int newLen = S->length + count * (V.length - T.length);
    char* newCh = (char*)malloc(newLen * sizeof(char));
    
    // 替换
    int p = 0, i = 0;
    while (i < S->length) {
        int j = 0;
        while (j < T.length && i + j < S->length && S->ch[i + j] == T.ch[j]) j++;
        if (j == T.length) {
            for (int k = 0; k < V.length; k++) newCh[p++] = V.ch[k];
            i += T.length;
        } else {
            newCh[p++] = S->ch[i++];
        }
    }
    
    free(S->ch);
    S->ch = newCh;
    S->length = newLen;
}

C++代码:同顺序串的StrReplace。


(9) 编写算法,实现堆串的求逆串操作StrReverse(S, T)。

核心思路:为T分配与S等长的内存,将S的字符逆序复制到T中。

C语言代码

复制代码
void StrReverse(HString S, HString* T) {
    T->ch = (char*)realloc(T->ch, S.length * sizeof(char));
    T->length = S.length;
    for (int i = 0; i < S.length; i++) {
        T->ch[i] = S.ch[S.length - 1 - i];
    }
}

C++代码:同顺序串的StrReverse。


(10) 假定S和T均是结点大小为1的块链串,编写算法,求串S中第一个不在串T中出现的字符。

核心思路:遍历T,用哈希表(数组)记录T中出现的所有字符;然后遍历S,找到第一个未被记录的字符并返回。

C语言代码

复制代码
typedef struct Node {
    char data;
    struct Node* next;
} Node;

char FindFirstNotInT(Node* S, Node* T) {
    int hash[256] = {0};
    Node* p = T;
    while (p) {
        hash[(unsigned char)p->data] = 1;
        p = p->next;
    }
    
    p = S;
    while (p) {
        if (!hash[(unsigned char)p->data]) return p->data;
        p = p->next;
    }
    return '\0';
}

C++代码

复制代码
struct Node {
    char data;
    Node* next;
    Node(char c) : data(c), next(nullptr) {}
};

char FindFirstNotInT(Node* S, Node* T) {
    unordered_set<char> hash;
    for (Node* p = T; p; p = p->next) hash.insert(p->data);
    for (Node* p = S; p; p = p->next) {
        if (!hash.count(p->data)) return p->data;
    }
    return '\0';
}

(11) 假定S和T均是结点大小为1的块链串,编写算法,将串S中首次与串T匹配的子串逆置。

核心思路:先在S中找到T首次出现的位置(链表模式匹配),记录匹配段的头结点和前驱,将匹配段进行链表逆置,最后重新连接前后部分。

C语言代码

复制代码
void ReverseFirstMatch(Node** S, Node* T) {
    // 找匹配位置(朴素匹配)
    Node* prev = NULL;
    Node* start = *S;
    
    while (start) {
        Node* p = start, *q = T;
        while (p && q && p->data == q->data) {
            p = p->next;
            q = q->next;
        }
        if (!q) {  // 匹配成功
            // 逆置匹配段 [start, p)
            Node* curr = start;
            Node* tail = p;
            Node* prevMatch = prev;
            Node* nextMatch = p;
            
            // 逆置
            Node* rPrev = tail;
            Node* rCurr = curr;
            while (rCurr != tail) {
                Node* rNext = rCurr->next;
                rCurr->next = rPrev;
                rPrev = rCurr;
                rCurr = rNext;
            }
            
            // 连接
            if (prevMatch) prevMatch->next = rPrev;
            else *S = rPrev;
            return;
        }
        prev = start;
        start = start->next;
    }
}

C++代码:逻辑与C相同,可用指针引用简化。


(12) 假定S和T均是结点大小为4的块链串,编写算法,在串S的第k个字符后插入串T。

核心思路:找到第k个字符所在的块及块内位置。若插入位置在块中间,需要拆分该块。将T的整个链表插入到S中。

C语言代码

复制代码
typedef struct Block {
    char data[4];
    int len;
    struct Block* next;
} Block;

void InsertAfterK(Block** S, int k, Block* T) {
    if (k < 0) return;
    
    Block* curr = *S;
    Block* prev = NULL;
    int pos = 0;
    
    // 找到第k个字符的位置
    while (curr && pos + curr->len <= k) {
        pos += curr->len;
        prev = curr;
        curr = curr->next;
    }
    
    if (!curr) return;
    
    int offset = k - pos;  // 在当前块中的位置
    
    // 情况1:插入位置在块中间
    if (offset < curr->len - 1) {
        // 拆分当前块
        Block* newBlock = (Block*)malloc(sizeof(Block));
        newBlock->len = curr->len - offset - 1;
        for (int i = 0; i < newBlock->len; i++) {
            newBlock->data[i] = 0curr->data[offset + 1 + i];
        }
        newBlock->next = curr->next;
        
        curr->len = offset + 1;
        curr->next = newBlock;
        
        // 插入T
        Block* tail = T;
        while (tail->next) tail = tail->next;
        tail->next = newBlock;
        curr->next = T;
    }
    // 情况2:插入位置在块尾
    else {
        Block* tail = T;
        while (tail->next) tail = tail->next;
        tail->next = curr->next;
        curr->next = T;
    }
}

C++代码:逻辑相同,使用类封装。

注:以上习题的解答基于作者自己的理解和计算,如果有任何错误,希望各位读者和大佬指出改正,非常感谢!!!

相关推荐
夏末蝉未鸣011 小时前
Sort-Merge Join【排序连接算法】详解(python代码实现,以FULL JOIN为例)
数据结构·算法
_日拱一卒1 小时前
LeetCode:23合并K个升序链表
java·数据结构·算法·leetcode·链表·职场和发展
仍然.1 小时前
初识计算机网络
网络·计算机网络
南风微微吹1 小时前
【管综】考研199管理类综合联考历年真题及答案解析PDF电子版(2009-2026年)
考研·pdf
求学的小高3 小时前
数据结构Day10(ASL、二分查找、分块查找)
数据结构·笔记·考研
做cv的小昊3 小时前
【TJU】应用统计学——第一周作业(1.1 数理统计的基本内容、1.2 数理统计的基本概念)
人工智能·笔记·考研·机器学习·数学建模·概率论
算法鑫探3 小时前
算法与数据结构 以及算法复杂度
c语言·数据结构·算法·新人首发
迷途之人不知返3 小时前
List的学习
数据结构·c++·学习·list
啊哦呃咦唔鱼3 小时前
Leetcodehot100-215. 数组中的第K个最大元素
数据结构·算法·leetcode