注:以下习题参考 计算机网络(第八版)谢希仁 编著,数据结构与算法 王曙燕 主编。
计算机网络部分:
1-01 计算机网络可以向用户提供哪些服务?
答案:
计算机网络向用户提供两大类的服务:
-
连通性服务:使上网用户之间能够交换信息(如文件传输、电子邮件、远程登录),好像这些用户计算机之间直接连通一样。
-
资源共享服务:共享硬件资源(如打印机、存储设备)、软件资源(如应用软件)和数据资源(如数据库、文件)。
1-02 试简述分组交换的要点。
答案:
分组交换的要点:
-
存储转发:节点交换机先将收到的分组存储下来,再转发到下一段链路。
-
分组:把较长的报文划分成较小的等长数据段,每个段加上首部(包含地址等控制信息)后构成分组。
-
动态分配带宽:多个分组可以在同一条链路上以统计时分复用的方式传输,不预先占用链路。
-
独立选路:每个分组独立地在网络中选择路径(数据报方式)或属于同一报文的分组沿着同一条虚电路传送。
1-03 试从多个方面比较电路交换、报文交换和分组交换的主要优缺点。
答案:
| 交换方式 | 优点 | 缺点 |
|---|---|---|
| 电路交换 | 时延小、实时性好、传输无额外开销 | 线路利用率低、建立连接时间长、不具备差错控制能力 |
| 报文交换 | 线路利用率高、可进行差错控制和速率转换 | 时延大(存储--转发整个报文)、需要大容量缓冲区 |
| 分组交换 | 线路利用率高、时延比报文交换小、支持多个分组并行传输、适合突发数据 | 存在额外首部开销、可能产生失序和丢包、时延仍比电路交换大(轻载下) |
1-04 为什么说互联网是自印刷术发明以来人类在存储和交换信息领域的最大变革?
答案:
印刷术解决了信息的"复制"和"保存"问题,而互联网解决了信息的"全球范围快速交换"和"按需获取"问题。
-
互联网使得信息传播的成本极低、速度极快、范围无边界。
-
任何人都可以作为信息发布者,打破了传统媒体中心化的信息控制。
-
实现了远程协作、电子商务、社交媒体、在线教育等新型信息交互模式。
因此,互联网对人类社会信息生产和交换方式的改变,堪与印刷术相提并论。
1-05 互联网基础结构的发展大致分为哪几个阶段?请指出这几个阶段最主要的特点。
答案:
分为三个阶段:
-
第一阶段(1969 -- 1990)------从单个网络ARPANET向互联网发展
特点:分组交换网络、TCP/IP协议诞生、NSFNET取代ARPANET成为主干。 -
第二阶段(1985 -- 1993)------三级结构的互联网
特点:主干网、地区网、校园网/企业网三级结构。 -
第三阶段(1993 -- 至今)------多层次ISP结构的互联网
特点:商业化、出现互联网服务提供者(ISP),形成由主干ISP、地区ISP、本地ISP构成的多层次结构。
1-06 简述互联网标准制定的几个阶段。
答案:
互联网标准由IETF制定,经历三个阶段:
-
互联网草案(Internet Draft):有效期6个月,不能作为参考。
-
建议标准(Proposed Standard):功能基本稳定,可开始实现。
-
互联网标准(Internet Standard):经过充分实践验证,正式成为标准,并分配标准编号(STD xxx)。
此外还有历史标准 和实验标准等分类。
1-07 小写和大写开头的英文名字 internet 和 Internet 在意思上有何重要区别?
答案:
-
internet(小写):泛指由多个计算机网络互连而成的"互连网",是一个通用名词。
-
Internet(大写):特指全球最大的、开放的、由众多网络互连而成的"因特网",采用TCP/IP协议族,是一个专有名词。
1-08 计算机网络有哪些类别?各种类别的网络都有哪些特点?
答案:
按作用范围分类:
| 类别 | 特点 |
|---|---|
| 广域网(WAN) | 覆盖几十到几千公里,采用点对点链路或交换网络,速率较低 |
| 城域网(MAN) | 覆盖一个城市,多采用以太网技术,速率中等 |
| 局域网(LAN) | 覆盖几百米到几公里,高数据率(10Mbps~100Gbps),低时延,误码率低 |
| 个人区域网(PAN) | 覆盖10米左右,常用无线技术(蓝牙、Zigbee) |
1-09 计算机网络中的主干网和本地接入网的主要区别是什么?
答案:
-
主干网:提供长距离、高速的数据传输通道,连接多个分布较远的网络或路由器,不直接连接用户终端。
-
本地接入网:连接用户主机到边缘路由器(或ISP),覆盖范围小、速率多样(DSL、光纤、4G/5G等),是用户进入主干网的"最后一公里"。
1-10 试在下列条件下比较电路交换和分组交换。要传送的报文共x(bit)。从源点到终点共经过k段链路,每段链路的传播时延为d(s),数据率为b(bit/s)。在电路交换时电路的建立时间为S(S)。在分组交换时,分组长度为p(bit),每个分组所必须添加的首部都很短,对分组的发送时延的影响在本题中可以不考虑。此外,各节点的排队等待时间也可忽略不计。问在怎样的条件下,分组交换的时延比电路交换的要小?(提示:画一下草图观察k段链路共有几个节点。)
已知:
-
报文长度 x bit
-
链路数 k
-
每段链路传播时延 d s
-
数据率 b bit/s
-
电路建立时间 s s
-
分组长度 p bit(不含首部,忽略首部影响)
-
忽略节点排队时延
解答:
电路交换总时延:
T=s+xb+kd
分组交换总时延:
总分组数 n=⌈x/p⌉≈x/pn=⌈x/p⌉≈x/p(假设整除)。
-
最后一个分组从起点出发的时刻:(n−1)p/b
-
经过 kk 段链路传播需 kd
-
链路总传输还需 p/b(最后一段链路上分组传输时间)
T=(n−1)p/b+p/b+kd=np/b+kd=x/b+kd
对于分组交换:
T=x/b+(k−1)p/b+kd
其中 (k−1)p/b 是流水线带来的额外时延(非首分组在中间节点存储转发引入的等待)。
其实更常见的 正确公式(参考教材)为:
-
电路交换:T=s+x/b+kd
-
分组交换:T=x/b+(k−1)p/b+kd
令 T(分组交换)<T(电路交换)
x/b+(k−1)p/b+kd<s+x/b+kd
(k−1)p/b<s
p<sb/(k−1)
答案:
当分组长度 p<sb/(k−1) 时,分组交换时延小于电路交换。
1-11 在上题的分组交换网中,设报文长度和分组长度分别为x和(p+h)(bit),其中p为分组的数据部分的长度,而h为每个分组所添加的首部长度,与p的大小无关。通信的两端共经过k段链路。链路的数据率为b(bit/s),但传播时延和节点的排队时间均可忽略不计。若打算使总的时延为最小,问分组的数据部分长度p应取为多大?
(提示:参考图1-11的分组交换部分,观察总的时延由哪几部分组成。)
已知:
-
总报文长度 x bit
-
每个分组:数据部分 p,首部 h,总长 p+h
-
链路数 k,数据率 b bit/s,忽略传播时延和排队时延
-
分组数 n=x/p(假设整数)
总时延:
T=x/p⋅(p+h)/b+(k−1)(p+h)/b
T=x(p+h)/(pb)+(k−1)(p+h)/b
对 p 求导并令导数为零:
dT/dp=x/b(−h/p²)+(k−1)/b=0
−xh/p²+(k−1)=0
p=[xh/(k−1)]½
答案:
最佳分组数据部分长度p=[xh/(k−1)]½。
1-12 互联网的两大组成部分(边缘部分与核心部分)的特点是什么?它们的工作方式各
有什么特点?
答案:
-
边缘部分 :由所有连接在互联网上的主机组成,用户直接使用。工作方式有客户-服务器方式 和P2P方式。
-
核心部分 :由大量网络和连接这些网络的路由器组成,为边缘部分提供连通性。工作方式是分组交换(存储转发),关键设备是路由器。
1-13 客户-服务器方式与P2P对等通信方式的主要区别是什么?有没有相同的地方?
答案:
-
客户-服务器:服务是集中的,服务器提供服务,客户请求服务。服务器地址固定,服务器随时响应。
-
P2P:没有固定的服务器,每个节点既是客户也是服务器,平等通信。
相同点:都是通信方式,都依赖网络协议栈。
1-14 计算机网络有哪些常用的性能指标?
答案:
速率(数据率)、带宽、吞吐量、时延(发送/传播/处理/排队)、时延带宽积、往返时间RTT、利用率。
1-15 假定网络的利用率达到了90%。试估算一下现在的网络时延是它的最小值的多少倍?
公式:
D=D₀/(1−U)
其中 D₀ 为利用率0时的时延,U=0.9
D=D₀/(1−0.9)=10D₀
答案: 是原来最小值的 10倍。
1-16 计算机通信网有哪些非性能特征?非性能特征与性能指标有什么区别?
答案:
非性能特征:费用、质量、标准化、可靠性、可扩展性、可管理性等。
区别:性能指标是可量化测量的(如速率、时延),非性能特征是定性描述或与整体系统价值相关。
1-17 收发两端之间的传输距离为1000km,信号在媒体上的传播速率为2x108m/s。试计算以下两种情况的发送时延和传播时延:
(1)数据长度为10'bit,数据发送速率为 100kbit/s。
(2)数据长度为10%bit,数据发送速率为1Gbit/s。
从以上计算结果可得出什么结论?
公式:
发送时延 T(send)=数据长度/发送速率
传播时延 T(prop)=距离/传播速度
(1) 数据 10⁷ bit,速率 100 kbit/s
T(send)=10⁷/(100×10³)=100s
T(prop)=1000×10³/(2×10⁸)=0.005 s
(2) 数据 10³ bit,速率 1 Gbit/s
T(send)=10³/10⁹=(1/10)⁶ s
T(prop)=0.005s(同距离)
结论:
短报文/高速率下传播时延成为主要时延;长报文/低速率下发送时延占主导。
1-18 假设信号在媒体上的传播速率为 2.3x10m/s。媒体长度l分别为:
(1)10cm(网络接口卡)
(2)100m(局域网)
(3)100km(城域网)
(4)5000km(广域网)
现在连续传送数据,数据率分别为1Mbits和10Gbits。试计算每一种情况下在媒体中的比特数。(提示:媒体中的比特数实际上无法使用仪表测量。本题是假想我们能够看见媒体中正在传播的比特,能够给媒体中的比特拍个快照。媒体中的比特数取决于媒体的长度和数据率。)
公式: 比特数 = 数据率 × 传播时延 = R×l/v
以 v=2.3×10⁸ m/s,R1=1 Mbit/s,R2=10 Gbit/s 分别计算:
| 长度 l | 传播时延 | 1 Mbit/s时比特数 | 10 Gbit/s时比特数 |
|---|---|---|---|
| 10 cm | 0.435 ns | 0.000435 bit | 4.35 bit |
| 100 m | 435 ns | 0.435 bit | 4350 bit ≈ 4.35 kbit |
| 100 km | 0.435 ms | 435 bit | 4.35 Mbit |
| 5000 km | 21.7 ms | 21.7 kbit | 217 Mbit |
1-19 长度为 100 字节的应用层数据交给运输层传送,需加上20字节的TCP首部。再交网络层传送,需加上 20字节的P首部。最后交给数据链路层的以太网传送,加上首部和尾部共 18字节。试求数据的传输效率。数据的传输效率是指发送的应用层数除以所发送的总数据(即应用数据加上各种首部和尾部的额外开销)。若应用层数据长度为1000字节,数据的传输效率是多少?
(1) 数据长度 100 B
TCP首部 20B,IP首部 20B,以太网首尾 18B
总数据 = 100 + 20 + 20 + 18 = 158 B
效率 = 100/158 ≈ 63.3%
(2) 数据长度 1000 B
总数据 = 1000 + 20 + 20 + 18 = 1058 B
效率 = 1000/1058 ≈ 94.5%
1-20 网络体系结构为什么要采用分层次的结构?试举出一些与分层体系结构的思想相似的日常生活的例子。
答案:
分层可将大问题分解为小问题,每层独立实现,层次之间通过接口通信,便于标准化和维护。
日常例子:
-
快递系统:寄件人 → 包装 → 快递运输 → 分拣 → 派送 → 收件人
-
邮件系统:写信 → 装信封 → 邮局处理 → 运输 → 投递
1-21 协议与服务有何区别?有何关系?
答案:
-
协议:控制对等实体通信的规则(水平的)。
-
服务:下层向上层提供的功能(垂直的)。
关系:协议是"怎样实现"服务的基础,服务是协议"对外呈现"的能力。
1-22 网络协议的三个要素是什么?各有什么含义?
答案:
-
语法:数据与控制信息的结构和格式。
-
语义:需要发出何种控制信息、完成何种动作、做出何种响应。
-
同步:事件实现顺序的详细说明。
1-23 为什么协议必须考虑各种情况
答案:
网络环境复杂(丢包、乱序、延迟、重复、比特错误等),协议必须能够处理这些异常,保证可靠通信。
1-24 试述具有五层协议的网络体系结构的要点,包括各层的主要功能。
答案:
-
应用层:直接为用户应用提供交互(HTTP、SMTP、FTP)
-
运输层:端到端通信(TCP可靠、UDP不可靠)
-
网络层:路由选择、分组转发(IP协议)
-
数据链路层:相邻节点间可靠传输(以太网、PPP)
-
物理层:比特流在媒体上传输
1-25 日常生活中的"透明"例子
答案:
玻璃窗(玻璃本身对光线透明,你不注意玻璃,只看到窗外景色);
压缩软件(你只看到压缩后的文件,看不到内部的压缩算法)。
1-26 试解释以下名词:协议栈、实体、对等层、协议数据单元、服务访问点、客户、服
务器、客户-服务器方式。
答案:
-
协议栈:多层协议的集合。
-
实体:每一层中实现该层功能的硬件或软件。
-
对等层:不同机器上的同一层。
-
协议数据单元(PDU):对等层之间传输的数据单元。
-
服务访问点(SAP):邻层之间交互的接口(如端口号)。
-
客户/服务器:服务请求方/服务提供方。
-
客户端和服务方式:即C/S方式。
1-27 试解释 everything over IP 和 IP over everything 的含义。
答案:
-
everything over IP:IP网络可承载各种上层协议(HTTP、FTP、VoIP等)。
-
IP over everything:IP协议可以在各种物理网络上运行(以太网、光纤、卫星等)。
1-28 假定要在网络上传送1.5 MB的文件。设分组长度为1KB,往返时间RTT=80ms。传送数据之前还需要有建立 TCP连接的时间,这时间是2xRTT=160ms。试计算在以下几种情况下接收方收完该文件的最后一个比特所需的时间。
(1)数据发送速率为10 Mbit/s,数据分组可以连续发送。
(2)数据发送速率为 10 Mbit/s,但每发送完一个分组后要等待一个 RTT 时间才能再
发送下一个分组。
(3)数据发送速率极快,可以不考虑发送数据所需的时间。但规定在每一个 RTT往返时间内只能发送 20 个分组。
(4)数据发送速率极快,可以不考虑发送数据所需的时间。但在第一个 RTT 往返时间内只能发送一个分组,在第二个 RTT 内可发送两个分组,在第三个RTT内可发送个分组(即23-1=22=4个分组)(这种发送方式见教材第5章TCP的拥塞控制部分)。
文件大小 1.5MB = 1.5×1024×1024×8bit
分组长度 1KB = 8 kbit,总分组数 = 1500(1.5MB/1KB)
(1) 连续发送:
传输时间 = 数据量 / 速率 + 传播(忽略RTT影响时主要是传输时间)
实际上发送时间 = (1.5×1024×1024×8bit) / (10 ×10⁶bit/s) ≈ 1.26 s
(2) 每发一个分组等一个RTT(80ms)
总时间 = 传输时间 + 1500×RTT = 1.26s + 120s = 121.26s
(3) 速率极快,每RTT发20分组:需要 1500/20 = 75个RTT,总时间 ≈ 75×0.08 = 6s
(4) RTT内分组数指数增长:第1 RTT发1,第2 RTT发2,第3 RTT发4,... 总分组数达到1500需要的RTT数约为 log₂1500 ≈ 10.55,总时间 ≈ 0.08×11 = 0.88s
1-29 有一个点对点链路,长度为50km。若数据在此链路上的传播速率为2x10m/s,订问链路的带宽应为多少才能使传播时延和发送100字节的分组的发送时延一样大?如果发送的是 512 字节长的分组,结果又应如何?
传播时延 = 50×10³/(2×10⁸)=2.5×(1/10)⁴ s
令发送时延 = 传播时延:
100×8/R=2.5×(1/10)⁴⇒R=3.2×10⁶ bit/s=3.2 Mbit/s
对于512字节:
512×8/R=2.5×(1/10)⁴⇒R=16.384 Mbit/s
1-30 & 1-31 有一个点对点链路,长度为20000km。数据的发送速率是1kbits,要发送的数据有100 bit。数据在此链路上的传播速率为2x108m/s。假定我们可以看见在线路上传输的比特,试画出我们看到的线路上的比特(画两张图,一张是在100bit刚刚发送完时,另一张是再经过 0.05s后)。当数据的发送速率改为1Mbits。和上次的结果相比较,你可以得出什么结论?

第一张图:100个比特均匀分布在整条20000km链路上,每个比特间隔200km;
第二张图:前50个比特已到达接收,后50个比特分布在10000km到20000km处,每个比特间隔200km
结论:提高发送速率会减小发送时延,当发送速率增大到一定程度时,发送时延可能小于传播时延,此时信道利用率可能降低(或类似核心结论:发送速率变化影响发送时延与传播时延的相对大小,进而影响信道利用率)
1-32 以1 Gbits的速率发送数据。试问在以距离或时间为横坐标时,一个比特的宽度分别是多少?
-
时间上:1 bit 宽度 = (1/10)⁹ s
-
距离上:传播速率按 2×10⁸m/s,宽度 = 0.2 m
1-33 我们在互联网上传送数据经常是从某个源点传送到某个终点,而并非传送过去又再传送回来。那么为什么往返时间 RTT是个很重要的性能指标呢?
答案:
尽管数据单向传输,但很多协议需要确认(如TCP),RTT决定了确认的返回时间、流量控制、拥塞控制和超时重传的效率。
1-34 主机 A 向主机 B 发送一个长度为 10'比特的报文,中间要经过两个节点交换机,即一共经过三段链路。设每段链路的传输速率为2Mbits。忽略所有的传播、处理和排队时延。
(1)如果采用报文交换,即整个报文不分段,每台节点交换机收到整个的报文后再转发。问从主机 A 把报文传送到第一个节点交换机需要多少时间?从主机 A 把报文传送到主机 B 需要多少时间?
(2)如果采用分组交换。报文被划分为1000个等长的分组(这里忽略分组首部对本题计算的影响),并连续发送。节点交换机能够边接收边发送。问从主机A把第一个分组传送到第一个节点交换机需要多少时间?从主机A把第一个分组传送到主机B需要多少时间?从主机A把1000个分组传送到主机B需要多少时间?
(3)就一般情况而育,比较用整个报文来传送和划分多个分组来传送的优缺点。
(1) 报文交换
-
第一段链路:10⁷/(2×10⁶)=5 s
-
总时间:每段链路5s,共3段,需 3×5=15 s
(2) 分组交换(1000个分组,忽略首部)
-
第一分组传到第一节点:(10⁷/1000)/(2×10⁶)=(10⁴)/(2×10⁶)=0.005 s
-
第一分组传完三段:0.005+0.005+0.005=0.015 s
-
1000个分组总时间 = 最后分组开始发送时间 + 各段传输
= (1000−1)×0.005+0.015=4.995+0.015=5.01 s
(3) 分组交换时延更小、资源利用率高,但有首部开销。
1-35 主机 A 向主机 B连续传送一个600000 bit的文件。A和B之间有一条带宽为1Mbit/s的链路相连,距离为 5000km,在此链路上的传播速率为2.5x10%m/s。链路上的比特数目的最大值是多少?
链路上每比特的宽度(以米来计算)是多少?
若想把链路上每比特的宽度变为5000km(即整条链路的长度),这时应把发送速率调整到什么数值?
带宽 1Mbit/s,长度 5000km,传播速度 2.5×10⁸ m/s
传播时延 = 5000×10³/(2.5×10⁸)=0.02 s
链路中比特数 = 带宽 × 传播时延 = 10⁶×0.02=20000 bit = 20 kbit
每比特宽度=5×10⁶m/20000=250m/bit。
若想把链路上每比特的宽度变为5000km,则R=1/tp=1/0.02s=50bit/s
1-36 主机 A 到主机 B的路径上有三段链路,其速率分别为2Mbits,1Mbits和 500 kbits。现在 A 向 B发送一个大文件。试计算该文件传送的吞吐量。设文件长度为10MB,而网络上没有其他的流量。试问该文件从A传送到B大约需要多少时间?为什么这里只是计算大约的时间?
三段链路速率:2 M、1 M、0.5 Mbit/s
瓶颈 = 500 kbit/s
吞吐量 = 500 kbit/s
文件 10 MB = 80 Mbit=80×2²⁰bit
时间 :80×2²⁰/500×10³≈168 s
大约时间的原因:忽略协议开销、处理时延、传播时延、可能的重传等。
数据结构部分:
一、单项选择题
(1) 链表不具有的特点是 ___。
A. 插入、删除不需要移动元素
B. 可随机访问任一元素
C. 不必事先估计存储空间
D. 所需空间与线性长度成正比
答案:B(这是顺序表的特点,链表是顺序访问)
(2) 设单链表中结点的结构为(data,mext)。若在指针p所指结点后插人由指针s指向的结点,则应执行___操作。
A.p->next=s; s->next=p;
B.s->next=p->next; p->next=s;
C.s->next=p; s=p;
D.p->next=s; s->next=p->next;
答案:B
(3)在双向链表指针p的指针前插人一个指针q的结点,操作是___
注:双向链表的结点结构为(prior,data,next)。
A.p->prior=q; q->next=p; p->prior->next=q; q->prior=q;
B. p->prior=q; p->prior->next=q; q->next=p; q->prior=p->prior;
C.q->next=p; q->prior=p->prior; p->prior->next=q; p->prior=q;
D.q->prior=p->prior; q->next=q; p->prior=q; p->prior=q;
答案:C
(4) 对于一个具有n个结点的单链表,在已知的结点*p后插人一个新结点的时间复杂度,和在给定值为x的结点后插人一个新结点的时间复杂度分别为___
A. 0(n),0(n)
B. 0(1),0(n)
C.0(1),0(1)
D.0(n),0(1)
答案:B
(5) 以下错误的是:
① 静态链表既有顺序存储的优点,又有动态链表的优点。所以,它存取表中第:个元素的时间与i无关。 (要遍历,O(n))
② 静态链表中能容纳的元素个数在表定义时就确定了,以后不能增加。(正确)
③ 静态链表与动态链表在元素的插入、删除上类似,不需做元素的移动。 (正确)
A.①② B.① C.①②② D.②
答案:B
二、算法设计题
核心算法思路和代码(C语言风格(重点)/C++风格)。
(1)设有一线性表e=(e1,e2,...,en),其逆线性表定义为e'=(en,...,e2,e1)。请设计一个算法,将线性表逆置,要求逆线性表仍占用原线性表的空间,并且用顺序表和单链表两种方法来表示,写出不同的处理函数。
核心思路:
顺序表:双指针交换,首尾对称互换,遍历到中间位置即可。
单链表:三指针法(prev、curr、next),遍历链表逐个改变指针方向,将当前结点的 next 指向前驱。
顺序表逆置(C)
#include <stdio.h>
void ReverseSeq(int A[], int n) {
for (int i = 0; i < n / 2; i++) {
int temp = A[i];
A[i] = A[n - 1 - i];
A[n - 1 - i] = temp;
}
}
顺序表逆置(C++)
#include <algorithm>
#include <vector>
void ReverseSeq(std::vector<int>& vec) {
for (int i = 0; i < vec.size() / 2; i++) {
std::swap(vec[i], vec[vec.size() - 1 - i]);
}
}
单链表逆置(C)
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* ReverseList(Node* head) {
Node *prev = NULL, *curr = head, *next = NULL;
while (curr != NULL) {
next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
单链表逆置(C++)
struct Node {
int data;
Node* next;
Node(int val) : data(val), next(nullptr) {}
};
Node* ReverseList(Node* head) {
Node* prev = nullptr;
Node* curr = head;
while (curr) {
Node* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
(2) 已知长度为n的线性表A采用顺序存储结构,请设计一个算法,找出该线性表中值最小的数据元素。
核心思路:
顺序表:遍历比较,用一个变量记录当前最小值,逐个更新。
单链表:同上,遍历链表结点,比较 data 域。
C语言
int FindMin(int A[], int n) {
int min = A[0];
for (int i = 1; i < n; i++) {
if (A[i] < min) min = A[i];
}
return min;
}
C++
#include <vector>
#include <algorithm>
int FindMin(const std::vector<int>& vec) {
return *std::min_element(vec.begin(), vec.end());
}
// 或手动实现
int FindMinManual(const std::vector<int>& vec) {
int min = vec[0];
for (int x : vec) if (x < min) min = x;
return min;
}
(3) 已知线性表A的长度为n,并且采用顺序存储结构。请编写算法,删除线性表中所有值为x的元素。
核心思路:
双指针法:一个指针 i 遍历原数组,一个指针 k 记录新数组位置。当元素不等于 x 时,将其放到 k 位置并 k++。最终 k 即为新长度。
C语言
int DeleteAllX(int A[], int n, int x) {
int k = 0; // 新数组长度(等于不等于x的元素个数)
for (int i = 0; i < n; i++) {
if (A[i] != x) {
A[k++] = A[i];
}
}
return k; // 返回新的长度
}
C++
#include <vector>
void DeleteAllX(std::vector<int>& vec, int x) {
// 使用 erase-remove 惯用法
vec.erase(std::remove(vec.begin(), vec.end(), x), vec.end());
}
(4) 请设计算法,求线性表中第一个值为x的元素的前驱和后继的存储位置。要求采用顺序表和单链表两种方法来实现。
核心思路:
顺序表:遍历数组,找到第一个等于 x 的下标返回。
单链表:遍历链表,用计数器记录位置,找到第一个 data == x 的结点返回位置。
顺序表(C)
int FindFirst(int A[], int n, int x) {
for (int i = 0; i < n; i++) {
if (A[i] == x) return i;
}
return -1; // 未找到
}
顺序表(C++)
#include <vector>
#include <algorithm>
int FindFirst(const std::vector<int>& vec, int x) {
auto it = std::find(vec.begin(), vec.end(), x);
return (it != vec.end()) ? std::distance(vec.begin(), it) : -1;
}
单链表(C)
int FindFirst(Node* head, int x) {
int pos = 0;
Node* curr = head;
while (curr) {
if (curr->data == x) return pos;
curr = curr->next;
pos++;
}
return -1;
}
单链表(C++)
int FindFirst(Node* head, int x) {
int pos = 0;
Node* curr = head;
while (curr) {
if (curr->data == x) return pos;
curr = curr->next;
pos++;
}
return -1;
}
(5) 假设有一个循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某结点的指针,试编写算法,在链表中删除指针s所指结点的前驱结点。
核心思路:
循环链表特性:从头遍历找到 a 的前驱结点 p,再找到 p 的前驱结点 pre,将 pre->next 指向 p->next,释放 p。注意处理边界(单结点情况)。
C语言
void DeletePre(Node* a) {
if (!a) return;
Node* p = a;
// 找到 a 的前驱
while (p->next != a) p = p->next;
if (p == a) return; // 只有一个结点
// 找到 p 的前驱
Node* pre = a;
while (pre->next != p) pre = pre->next;
pre->next = p->next;
free(p);
}
C++
void DeletePre(Node* a) {
if (!a) return;
Node* p = a;
while (p->next != a) p = p->next;
if (p == a) return;
Node* pre = a;
while (pre->next != p) pre = pre->next;
pre->next = p->next;
delete p;
}
(6) 设A与B分别为两个带有头结点的有序循环链表(所谓有序是指链接点按数据域值大小链接,本题不妨设按数据域值从小到大排列),list1和list2分别为指向两个链表的表尾指针。请写出将这两个链表合并为一个带头结点的有序循环链表的算法。
核心思路:
归并思想:类似归并排序,分别从两个链表取较小值的结点尾插入新链表。最后将剩余部分连接,并形成循环。需注意循环链表的头尾处理。
C语言
Node* MergeCircular(Node* tail1, Node* tail2) {
if (!tail1) return tail2;
if (!tail2) return tail1;
Node* newTail = (Node*)malloc(sizeof(Node));
Node* p = tail1->next;
Node* q = tail2->next;
Node* tail = newTail;
while (p != tail1 && q != tail2) {
if (p->data < q->data) {
tail->next = p; p = p->next;
} else {
tail->next = q; q = q->next;
}
tail = tail->next;
}
tail->next = (p != tail1) ? p : q;
while (tail->next) tail = tail->next;
tail->next = newTail;
return newTail;
}
C++
Node* MergeCircular(Node* tail1, Node* tail2) {
if (!tail1) return tail2;
if (!tail2) return tail1;
Node* newHead = new Node(0);
Node* p = tail1->next;
Node* q = tail2->next;
Node* tail = newHead;
while (p != tail1 && q != tail2) {
if (p->data < q->data) {
tail->next = p; p = p->next;
} else {
tail->next = q; q = q->next;
}
tail = tail->next;
}
tail->next = (p != tail1) ? p : q;
while (tail->next) tail = tail->next;
tail->next = newHead;
Node* result = newHead->next;
delete newHead;
return result;
}
(7) 设 head 为一单链表的头指针,单链表的每个结点由一个整数域 data 和指针域 next 组成,整数在单链表中是无序的。编一函数,将 head 链中结点分成一个奇数链和一个偶数链,分别由p、q指向,每个链中的数据按由小到大排列。程序中不得使用 malloc申请空间。
核心思路:
遍历拆分:遍历原链表,根据 data % 2 判断奇偶,分别尾插入两个新链表。不使用 malloc,即直接移动原结点,不创建新结点。
C语言
void Split(Node* head, Node** odd, Node** even) {
Node oddDummy = {0, NULL};
Node evenDummy = {0, NULL};
Node* tailOdd = &oddDummy;
Node* tailEven = &evenDummy;
Node* curr = head;
while (curr) {
if (curr->data % 2 == 1) {
tailOdd->next = curr;
tailOdd = curr;
} else {
tailEven->next = curr;
tailEven = curr;
}
curr = curr->next;
}
tailOdd->next = NULL;
tailEven->next = NULL;
*odd = oddDummy.next;
*even = evenDummy.next;
}
C++
pair<Node*, Node*> Split(Node* head) {
Node oddDummy(0), evenDummy(0);
Node* tailOdd = &oddDummy;
Node* tailEven = &evenDummy;
Node* curr = head;
while (curr) {
if (curr->data % 2 == 1) {
tailOdd->next = curr;
tailOdd = curr;
} else {
tailEven->next = curr;
tailEven = curr;
}
curr = curr->next;
}
tailOdd->next = nullptr;
tailEven->next = nullptr;
return {oddDummy.next, evenDummy.next};
}
(8) 设指针 la 和 lb 分别指向两个无头结点单链表中的首元结点,试设计算法,从表 la 中删除自第 i 个元素起共 len 个元素,并将它们插入表 lb 的第 j 个元素之后。
核心思路:
三步走:
-
定位:找到 L 中第 i-1 个结点和第 i+len 个结点,截取子链表
-
删除:将 L 中被截取部分的前后连接
-
插入:找到 B 中第 j 个结点,将子链表插入其后
C语言
void MoveSubList(Node* L, int i, int len, Node* B, int j) {
// 找到 L 中第 i-1 个结点
Node* prevL = L;
for (int t = 1; t < i; t++) prevL = prevL->next;
Node* subHead = prevL->next;
Node* subTail = subHead;
for (int t = 1; t < len; t++) subTail = subTail->next;
// 从 L 中删除
prevL->next = subTail->next;
// 找到 B 中第 j 个结点
Node* prevB = B;
for (int t = 1; t < j; t++) prevB = prevB->next;
// 插入
subTail->next = prevB->next;
prevB->next = subHead;
}
C++
void MoveSubList(Node* L, int i, int len, Node* B, int j) {
Node* prevL = L;
for (int t = 1; t < i; t++) prevL = prevL->next;
Node* subHead = prevL->next;
Node* subTail = subHead;
for (int t = 1; t < len; t++) subTail = subTail->next;
prevL->next = subTail->next;
Node* prevB = B;
for (int t = 1; t < j; t++) prevB = prevB->next;
subTail->next = prevB->next;
prevB->next = subHead;
}
(9) 设带头结点的线性单链表 A=(a1,a2,...,an),B=(b1,b2,...,bn)。试编写算法按下列规则合并 A、B为线性单链表C,使得
C=(a1,b1,...,am,bm,bm+1,...,bn),m≤n
或者
C=(b1,a1,...,bn,an,an+1,...,am ),m>n
核心思路:
双指针交替:pa 指向 A 首结点,pb 指向 B 首结点。循环中交替将 pa 和 pb 尾插入 C,直到一方为空,再将剩余部分接上。
C语言
Node* MergeAlternate(Node* A, Node* B) {
Node* C = (Node*)malloc(sizeof(Node));
Node* tail = C;
Node* pa = A->next;
Node* pb = B->next;
while (pa && pb) {
tail->next = pa; pa = pa->next; tail = tail->next;
tail->next = pb; pb = pb->next; tail = tail->next;
}
tail->next = pa ? pa : pb;
return C;
}
C++
Node* MergeAlternate(Node* A, Node* B) {
Node* C = new Node(0);
Node* tail = C;
Node* pa = A->next;
Node* pb = B->next;
while (pa && pb) {
tail->next = pa; pa = pa->next; tail = tail->next;
tail->next = pb; pb = pb->next; tail = tail->next;
}
tail->next = pa ? pa : pb;
Node* result = C->next;
delete C;
return result;
}
(10) (2009 考研真题)已知一个带有表头结点的单链表,结点结构为(data, link),假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1:否则,只返回0。要求:① 描述算法的基本设计思想。② 描述算法的详细实现步骤。③ 根据设计思想和实现步骤,采用程序设计语言描述算法(使用C、C++或Java 语言实现),关键之处给出简要注释。
核心思路:
双指针法(快慢指针):
-
快指针先走 k 步
-
快慢指针同步后移
-
快指针到达尾部时,慢指针指向倒数第 k 个结点
C语言
int FindLastK(Node* head, int k) {
Node* p = head;
Node* q = head;
// p 先走 k 步
for (int i = 0; i < k; i++) {
if (!p) return 0;
p = p->next;
}
// 同步走
while (p) {
p = p->next;
q = q->next;
}
printf("%d\n", q->data);
return 1;
}
C++
int FindLastK(Node* head, int k) {
Node* p = head;
Node* q = head;
for (int i = 0; i < k; i++) {
if (!p) return 0;
p = p->next;
}
while (p) {
p = p->next;
q = q->next;
}
cout << q->data << endl;
return 1;
}
(11) Josephus 排列问题 2:编号为1,2,...,n的n个人按顺时针方向围坐在一张圆桌周围。给定一个正整数 m≤n,从第一个人开始按顺时针方向自1开始报数,每报到m 时就让其出列,且计数继续进行下去。如此下去,直至圆桌周围的人全部出列为止。最后出列者为优胜者。每个人的出列次序定义了整数1,2,3,....n的一个排列。这个排列称为一个"(n,m)Jogephus 排列"。例如,(7,3)Jogephus 排列为 3,6,2,7,5,1,4。对于给定的1,2,3,...,n中的k个数,Josephus 想知道是否存在一个正整数m,使得 Josophus(n,m)排列的最后k个数恰好为事先指定的k个数。
核心思路:
循环链表模拟:
-
创建 1~n 的循环链表
-
从当前结点开始报数,移动 m-1 步
-
删除当前结点,记录值
-
从下一个结点继续报数
-
重复直到链表为空
核心:循环链表 + 迭代器移动 + 删除结点
C语言
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
int* Josephus(int n, int m, int* returnSize) {
// 创建循环链表
Node* head = (Node*)malloc(sizeof(Node));
head->data = 1;
head->next = NULL;
Node* prev = head;
for (int i = 2; i <= n; i++) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = i;
node->next = NULL;
prev->next = node;
prev = node;
}
prev->next = head; // 形成循环
int* result = (int*)malloc(n * sizeof(int));
*returnSize = 0;
Node* it = head;
Node* pre = prev; // it的前驱
while (*returnSize < n) {
for (int count = 1; count < m; count++) {
pre = it;
it = it->next;
}
result[(*returnSize)++] = it->data;
pre->next = it->next;
free(it);
it = pre->next;
}
return result;
}
C++
#include <iostream>
#include <vector>
#include <list>
using namespace std;
// 生成 (n, m) Josephus 排列
vector<int> Josephus(int n, int m) {
list<int> people;
for (int i = 1; i <= n; i++) people.push_back(i);
vector<int> result;
auto it = people.begin();
while (!people.empty()) {
for (int count = 1; count < m; count++) {
it++;
if (it == people.end()) it = people.begin();
}
result.push_back(*it);
it = people.erase(it);
if (it == people.end()) it = people.begin();
}
return result;
}
注:以上习题的解答基于作者自己的理解和计算,如果有任何错误,希望各位读者和大佬指出改正,非常感谢!!!