注:以下习题参考 计算机网络(第八版)谢希仁 编著,数据结构与算法 王曙燕 主编。
一、计算机网络---数据链路层(上)习题与解答
3-01 数据链路(即逻辑链路)与链路(即物理链路)有何区别?"链路接通了"与"数据链路接通了"的区别何在?
答案:
数据链路与链路的区别:
-
链路:物理链路,指相邻节点间的物理线路(如双绞线、光纤),是传输的"通道",仅提供比特流传输能力。
-
数据链路:逻辑链路,在物理链路基础上增加了通信协议(如PPP、以太网),实现数据的可靠传输,包括帧的封装、差错检测、流量控制等功能。
"链路接通了"与"数据链路接通了"的区别:
-
链路接通了:物理连接建立,信号可以在上面传输,但还不能进行有效的数据交换。
-
数据链路接通了:在物理链路基础上,通过协议建立了逻辑连接,可以进行帧的收发和差错控制,数据通信功能就绪。
3-02 数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。
答案:
链路控制功能:
-
封装成帧(确定帧边界)
-
透明传输(解决数据中特殊字符问题)
-
差错检测(CRC、校验和)
-
流量控制(协调收发速率)
-
链路管理(建立、维持、释放连接)
可靠链路层的优点:
-
减少上层重传负担
-
提高传输效率,适应高误码率环境(如无线网络)
-
可在底层及早发现并纠正错误
可靠链路层的缺点:
-
增加协议开销(确认、序号、重传机制)
-
降低实时性(等待确认)
-
复杂度和成本提高
-
对于误码率极低的光纤网络,可靠性不必要
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种):
-
链路静止(物理层未就绪)
-
链路建立(LCP协商)
-
鉴别(可选,验证身份)
-
网络层协议(NCP协商)
-
链路打开(数据传输)
-
链路终止(断开连接)
需要建立的连接:
| 连接类型 | 解决的问题 |
|---|---|
| 物理连接 | 解决信号传输问题,有线/无线物理链路建立 |
| 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) 两个字符串相等的充要条件是什么?
答案:
-
长度相等
-
各个对应位置上的字符相等
(3) 已知s='(xyz)+**',t='(x+z)**y'。利用连接、求子串和置换等基本运算,将s转化为t。
答案:
设基本操作:
-
SubString(str, s, pos, len):取子串 -
StrCat(str1, str2):连接 -
StrReplace(str, substr1, substr2):替换子串
使用替换操作:
-
StrReplace(s, 'xyz', 'x+z')→'(x+z)+*' -
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)。
核心思路:
-
遍历S,记录所有T出现的位置
-
计算新串长度 = lenS + 匹配次数×(lenV - lenT)
-
分配新空间,再次遍历S,遇到匹配位置则复制V,否则复制原字符
-
更新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++代码:逻辑相同,使用类封装。
注:以上习题的解答基于作者自己的理解和计算,如果有任何错误,希望各位读者和大佬指出改正,非常感谢!!!