ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

软考软件设计师数据结构考点:数组地址计算与矩阵压缩全解析

软考软件设计师数据结构考点:数组地址计算与矩阵压缩全解析 1. 这个考点为什么值得你停下来细看备考软考软件设计师的朋友应该都有这种体感上午题的知识点铺开看有一大片但真正到了考场上决定你能不能过线的那几分往往不是绕来绕去的算法设计而是“看起来简单、做起来全是坑”的数据结构小题。多维数据结构这块——数组存储、矩阵压缩、广义表就是典型的“公式一背就忘、一算就错、一对答案就拍大腿”的考点。先说个真实的复盘数据。我备考时把近五年的软件设计师中级真题过了一遍上午题里数据结构相关考点大概占6到8分其中数组和矩阵相关几乎每年必出广义表隔年出现一次。这个分数占比看着不高但它处在上午题的中段位置一旦卡住特别容易打乱后面做题的节奏。更关键的是这类题目的解题逻辑非常稳定是那种“只要你把原理吃透了题型怎么变都能做对”的确定性分数。算法题你可能还需要临场发挥但这种计算题完全可以靠考前系统梳理拿满。这篇文章不打算跟你从头到尾念教材。我会把数组地址计算、矩阵压缩存储、稀疏矩阵、广义表这四个方向拆开讲重点放在“考试怎么考”和“怎么算才对”上每个公式都会告诉你它的推导逻辑和考场上的坑在哪。尤其是矩阵压缩这类题目很多参考书只给公式不给过程导致你一换题型就不会套。这篇文章会让你把公式的来龙去脉搞清楚考场上哪怕忘公式也能自己推出来。2. 数组考点行优先和列优先的本质是一张“线性化”地图数组这块在软考里一般不直接问“什么是数组”而是考多维数组在内存中的地址计算。说白了就是内存是一维的数组是多维的你怎么把一个二维或三维数组放进一维的内存空间里并且能准确算出某个元素所在的位置。2.1 为什么会有行优先和列优先之分这个问题很多教材语焉不详我当年也迷惑过。先说结论行优先和列优先只是两种约定俗成的“展平规则”本身没有优劣之分就看语言设计者怎么选。C语言用的是行优先FORTRAN用的是列优先考试题干里都会明确告诉你用的是哪种你只要按规则展开就行。理解这一点有个很直观的生活类比想象你有一排书架每层能放固定数量的书。行优先就相当于先把第一层的书从左到右摆满再摆第二层列优先则是先从上到下把第一列摆满再摆第二列。数组的每一行就像一层书架一维内存就是那条连续的书架轨道。这个类比对你做题没有直接帮助但能帮你建立直觉——地址计算的本质就是在回答“我被展平之后到底排在第几个位置”。想明白这个后面所有公式都不用死背。2.2 二维数组地址计算的完整推导先给最基础的公式。设有一个二维数组a[m][n]按行优先存储每个元素占L个存储单元数组首地址是LOC(a[0][0])那么元素a[i][j]的地址是LOC(a[i][j]) LOC(a[0][0]) (i × n j) × L这个公式几乎所有教材都有但很多人会忽略“下标从0开始”这个默认前提。软考历年真题里数组下标基本都是从0开始的但偶尔会出现“如果下标从1开始”的变体届时要留意偏移量要相应调整。我们手动走一遍推导保证你理解而不是死记数组有 m 行 n 列行优先存储时先存第0行所有元素再存第1行所有元素。第 i 行之前一共有 i 个完整行每一行有 n 个元素所以第 i 行之前已经有 i × n 个元素。在第 i 行内第 j 列元素之前有 j 个元素。所以a[i][j]之前一共有 i × n j 个元素。每个元素占 L 个存储单元偏移量就是 (i × n j) × L加上首地址就是它的地址。列优先也很好理解把行和列的角色互换就行LOC(a[i][j]) LOC(a[0][0]) (j × m i) × L这里每一列有 m 个元素所以第 j 列之前的元素数是 j × m再加上本列内第 i 行之前的 i 个元素。2.3 三维数组别慌规律一模一样三维数组的地址计算看着吓人其实就是在二维公式上再套一层。设数组a[m][n][p]按行优先存储元素a[i][j][k]的地址是LOC(a[i][j][k]) LOC(a[0][0][0]) ((i × n j) × p k) × L推一下你就明白了前 i 个平面每个平面有 n × p 个元素当前平面内前 j 行每行有 p 个元素当前行内前 k 个元素合起来就是 (i × n j) × p k。核心心法不管几维数组你只需要回答一个问题——“这个元素前面有多少个元素”。一维算一个方向二维算两个方向三维算三个方向本质都是分层求和。2.4 软考常考题型与计算陷阱软考上午题里数组地址计算的典型题目长这样给出数组维度、首地址、每个元素大小和存储方式求指定元素的地址。给出某个元素的地址反推数组维度或元素大小。二维数组和指针关系的基础判断这个更多出现在C语言题里。做题时我建议你在草稿纸上画出数组的行列结构草图然后一步步列公式计算。不要心算尤其是元素大小不是1个字节的时候很容易漏乘或错位。注意题干里如果出现“每个元素占4个字节”“每个元素占8个字节”这类条件意味着地址计算的偏移量最后要乘以这个数。这属于白送分的条件千万别漏。另外如果给的是十进制地址算的是十六进制记得换算时要一步到位别在中间环节用除法取整容易出错。3. 矩阵压缩对称矩阵、三角矩阵和对角矩阵的“压缩密码”矩阵压缩是软考上午题的计算重灾区。很多人在这儿丢分根本原因不是不会算而是被那一堆下标换算公式绕晕了。这一节我会把每种特殊矩阵压缩的推导逻辑拆开让你在考场上即使忘记公式也能推出来。3.1 为什么矩阵要压缩存储矩阵在数学上是一个二维结构对应到程序里就是一个二维数组。但在实际工程和软考题目中有一类特殊矩阵——元素分布有规律——如果完整地用二维数组存会浪费大量空间。以对称矩阵为例一个 n × n 的对称矩阵满足a[i][j] a[j][i]也就是说主对角线上下是对称的。完整存储需要 n² 个元素但如果只存下三角含对角线只需要 n(n1)/2 个元素。当 n1000 时一个存 100 万个元素一个存 50 万个元素省一半空间。这就是压缩存储的意义。3.2 对称矩阵压缩的核心推导设对称矩阵 A 是一个 n × n 的方阵我们只存下三角区域。按行优先把下三角元素放进一维数组 SA 中存储顺序是a[0][0], a[1][0], a[1][1], a[2][0], a[2][1], a[2][2], ...现在的问题是对于下三角中的元素a[i][j]i ≥ j它在 SA 中的下标 k 是多少推导过程如下在第 i 行之前有第0行、第1行、……、第 i-1 行这些行都是完整的行前 i 行每行元素个数分别是 1, 2, ..., i所以前 i 行的元素总数是1 2 ... i i(i1)/2。在第 i 行内a[i][j] 是该行的第 j 个元素从第0个开始数所以它之前还要加上 j。因此k i(i1)/2 j下标从0开始。这个公式是软考高频考点几乎每年都会以某种形式出现。有一个变体容易让人栽跟头如果题目要求的是“存储位置从1开始编号”那答案就是 k i(i1)/2 j 1。下标从0开始还是从1开始永远是这类题目的第一个陷阱。上三角元素a[i][j]i j怎么算根据对称性a[i][j] a[j][i]直接用 j 和 i 代入公式计算 SA 中的位置也就是k j(j1)/2 i。3.3 三角矩阵比对称矩阵多了个“常数区”三角矩阵分两种下三角矩阵和上三角矩阵。下三角矩阵的元素在主对角线以下含对角线是非零数据主对角线以上全为同一个常数 C上三角矩阵则相反。存储思路是先存非零的三角区域再存一个常数。以 n 阶下三角矩阵为例下三角非零区域有 n(n1)/2 个元素按行优先存入一维数组前 n(n1)/2 个位置。最后一个位置存常数 C。所以总存储单元数是n(n1)/2 1比对三角矩阵多一个常数位。下三角元素a[i][j]i ≥ j的映射关系跟对称矩阵一样也是k i(i1)/2 j。问题在于常数区元素i j统一映射到最后一个位置k n(n1)/2。上三角矩阵则要换个思路。它的非零区域集中在主对角线以上按行优先存储时第 i 行不是固定 i1 个元素而是从第 i 行到第 n-1 行逐个减少。推导上三角元素a[i][j]i ≤ j的下标公式前 i 行的元素总数需要算一下第0行有 n 个元素第1行有 n-1 个元素……第 i-1 行有 n-(i-1)n-i1 个元素所以前 i 行元素总数是这些数之和。这个和可以写成等差数列求和化简后为i(2n - i 1)/2。在第 i 行内a[i][j]是该行第 j-i 个元素。所以k i(2n - i 1)/2 (j - i)。这个公式比对称矩阵的更复杂但只要你在草稿纸上画一个 5×5 的上三角矩阵按行标号看第2行第3列的元素排在第几个位置对着公式验证一遍就记住了。3.4 对角矩阵带状矩阵非零元素集中在主对角线附近对角矩阵在软考里常以“三对角矩阵”的形式出现也就是非零元素只出现在主对角线及其上下相邻的两条对角线上。行下标和列下标满足 |i - j| ≤ 1其余位置的全是0。以 n 阶三对角矩阵为例非零元素总数是3n - 2第0行和第n-1行各有2个非零元素中间 n-2 行每行有3个非零元素合计 2×2 3×(n-2) 3n - 2。按行优先存储时元素a[i][j]|i-j| ≤ 1在一维数组中的下标可以通过分析前面行的元素个数推导出来。第0行有2个元素中间行每行3个所以第 i 行之前的元素个数是第0行2个第1行到第i-1行每行3个共 (i-1) × 3 个合计2 3(i-1) 3i - 1 个在第 i 行内a[i][j]的位置需要分情况讨论j i-1该行第0个元素j i该行第1个元素j i1该行第2个元素所以映射关系为k 3i - 1 (j - i 1) 2i j下标从0开始。这个公式简洁得有点出乎意料但一定要结合画图去理解。3.5 考场上的快速检查法矩阵压缩这类题最容易犯的错是“公式记忆混淆”。对称矩阵公式、上三角公式、三对角公式长得像但又不一样考场上如果拿不准怎么办我的习惯是画小图验证。比如 n4把矩阵画出来按压缩存储顺序给每个非零元素编号然后随便取一个元素比如 a[2][1]数一数它前面有几个元素代入公式看是否一致。这个过程只要花30秒但能避免整道题因为公式错而全盘皆输。关于“下标从0开始还是从1开始”还有一个判断技巧如果题干里的矩阵下标写的是 a[1][1] 起那就是从1开始如果写的是 a[0][0] 或者直接说“下标从0开始”那就是从0开始。别用审美判断题干说啥就是啥。4. 稀疏矩阵三元组和十字链表考的是存储思路稀疏矩阵在软考里不像矩阵压缩那样考繁琐的计算更多是考存储结构的概念理解和基本操作。这块内容看着突兀其实它就是前面“矩阵压缩”思想的延伸——当一个矩阵大部分元素都是0时再完整存储就是浪费。4.1 什么是稀疏矩阵什么时候该用特殊存储稀疏矩阵没有严格的“零元素占比”标准但考试里常见的约定是当非零元素数量远小于矩阵元素总数时就把它当作稀疏矩阵来处理。比如一个 100×100 的矩阵有 10000 个元素但只有 50 个非零元素这就是典型稀疏矩阵。完整存储需要 10000 个单元用三元组存储只需要记录 50 个三元组每个三元组包含行号、列号、值加上一些辅助信息加起来也就几百个单元。省下来的空间非常可观。4.2 三元组表按行优先记录非零元素三元组表的定义很好理解把每个非零元素按照“值所在的行号、列号、值”三个信息记录下来形成一个线性表。行号(i)列号(j)值(v)013105237329这个表看起来简单但软考会考你两件事第一三元组表的存储顺序。默认按行优先排列也就是先按行号从小到大行号相同再按列号从小到大。上表就是按行号排好的。第二三元组表和原矩阵的相互转换。给你一个矩阵你能写出对应的三元组表反过来给你三元组表你能还原矩阵的稀疏结构。做题时注意三元组表可能会包含“行列数”信息也可能题目单独给出。还有一个进阶考点是三元组表的快速转置算法。普通矩阵转置就是把行列互换但三元组表转置不能简单互换行列号因为交换后行号顺序会被打乱还得重新排序。软考下午题偶尔会考这个算法的思想核心思路是先统计原矩阵每一列的非零元素个数计算每一列在转置后的起始位置然后一次扫描放入对应位置。这个算法的时间复杂度是 O(nu)比直接转置再排序快得多。上午题要是考到“快速转置算法相比普通转置改进在哪里”答案就是“通过预处理列元素个数和起始位置避免排序”。4.3 十字链表双向链接的非零元素网络十字链表比三元组表更灵活它让每一行和每一列都各自形成一个链表非零元素节点同时挂在行链表和列链表上。每个节点包含五个字段row行号col列号value值right指向同一行下一个非零元素down指向同一列下一个非零元素形象点说这个结构就是用两条链把稀疏矩阵的非零元素“编织”成一张网。好处是插入、删除节点比较方便适合矩阵在运算过程中非零元素位置频繁变化的场景。缺点是结构比三元组复杂实现起来代码量更大。软考在这个知识点上通常只考概念和选择题比如“十字链表中每个节点有几个指针”“非零元素节点挂在哪些链表上”。难度不大但容易记混——我见过不少考生把 right 和 down 的方向搞反复习时注意这两点right 管行方向down 管列方向。4.4 下午题里的稀疏矩阵应用软考下午题主要是算法设计和C语言编程稀疏矩阵很少单独作为一道大题出现但它会作为数据结构基础知识嵌入到算法题里。比如让你设计一个函数实现稀疏矩阵的加法或乘法。这时候如果你用三元组表示矩阵代码会比二维数组复杂一些但空间效率高。下午题如果碰到稀疏矩阵相关的题我的建议是优先用三元组表的思路来描述算法因为代码操作更直观而且方便说清楚时间复杂度。在答题纸上写伪代码时需要把“按行优先存储的规则”“查找两个矩阵非零元素位置”这些细节写清楚阅卷老师看的是你能不能把思路落到代码层面。5. 广义表递归定义下的长度、深度和表头表尾广义表是数据结构里概念性最强、也最“绕”的考点。很多考生觉得它难主要是因为它和自我递归、嵌套这些概念绑在一起。但软考上午题对广义表的考查其实非常规律你把以下三个计算点搞定分数就能拿到手。5.1 广义表的基本定义——为什么说它是“表中有表”广义表Generalized List是线性表的推广。线性表的元素都是原子单个数据元素广义表的元素则可以是原子也可以是另一个广义表子表。用符号表示LS (a1, a2, ..., an)其中 ai 可以是原子也可以是一个子表。注意广义表的括号必须有空表用 () 表示。给你几个例子感受一下A () —— 空表长度为0。B (a, b) —— 长度为2两个都是原子。C (a, (b, c)) —— 长度为2第一个是原子 a第二个是子表 (b, c)。D ((), a) —— 长度为2第一个是空表第二个是原子 a。第一眼看到会觉得有点乱但理解“每个元素要么是原子、要么是子表”这一句话就够了。5.2 表头和表尾最容易被绕晕的操作广义表的两个基本操作是取表头Head和取表尾Tail规则如下取表头 Head(LS)结果是表的第一个元素注意“第一个元素”是什么就是什么——原子就返回原子子表就返回子表。取表尾 Tail(LS)结果是除第一个元素外其余元素组成的表。这里的关键在于表尾永远是一个表不管里面有几个元素。上面这句话是好多考生掉坑的地方我重点展开一下。举例LS (a, b, c)表头是 a表尾是 (b, c)——注意表尾是一个包含 b 和 c 两个元素的表写的还是括号。 再来LS ((a, b), c)表头是 (a, b)——它是一个子表所以表头直接返回这个子表表尾是 (c)虽然只有一个元素 c但仍然是一个表所以写成 (c) 而不是 c。这就引出一个经典考点给定广义表连续取表头或取表尾若干次问最后得到什么。比如LS ((a, b), c, d)Head(LS) (a, b)Tail(LS) (c, d)继续 Tail(Tail(LS)) (d)继续 Head(Tail(LS)) c这类题目就是把“表头可能是原子也可能是个表”“表尾永远是个表”这两个规则反复使用每做一步都问自己一遍我拿到的是原子还是表写答案时该不该加括号做完再对照检查一次正确率会显著提高。5.3 长度和深度一个数元素一个数括号广义表的长度是表中最外层元素的个数。原子和子表都算一个元素。所以(a, b, c) 长度为3((a, b), c) 长度为2((), a, (b, c)) 长度为3这个计算最简单唯一的坑是别把子表内部展开来数。比如 ((a, b), c) 的长度是2不是3因为 (a, b) 整体算一个元素。广义表的深度是表中括号的最大嵌套层数也可以理解为“表的嵌套层数”。计算规则如下原子深度为0它不是表空表 () 深度为1一般表的深度 所有元素中最大深度 1举例LS (a, (b, (c, d)), e)原子 a 和 e 的深度为0(c, d) 的深度为1(b, (c, d)) 的深度是 1 1 2它包含深度为1的子表再加自身那层所以 LS 整体的深度是 2 1 3这里有个容易混淆的点非空表的深度至少是1因为最外层有一层括号。很多人在计算 (a) 的深度时会犹豫——它只有一个原子 a但包装 a 的那层括号就是一层深度所以 (a) 的深度为1。检验你掌握没有计算 L (a, (b, c), ((d, (e)))) 的深度。从内往外看e 深度0(e) 深度1(d, (e)) 深度 max(d的0, (e)的1) 1 2((d, (e))) 深度 2 1 3(b, c) 深度1最外层 L 的深度 max(0, 1, 3) 1 4所以在纸上一步一步标深度别跳步。这类题要认真仔细因为很小的“内部嵌套”差别会导致最终结果差出1甚至2。5.4 广义表的存储结构链式存储怎么理解软考上午题偶尔也考广义表的存储结构。广义表因为元素类型不统一原子和子表混着来用顺序存储不方便通常用链式存储。基本的节点结构有两种原子节点标志位值表节点标志位表头指针表尾指针这种设计很巧妙把广义表的“递归定义”直接映射到指针结构上。你想访问一个元素的表头或表尾顺着对应指针走就行。这也解释了为什么广义表的核心操作是取表头和表尾——因为存储结构本来就是按“头和尾”来组织的。真题里如果问“广义表最适合采用什么存储结构”答案是“链式存储”。如果问“为什么不用顺序存储”可以答“因为广义表中的元素类型不一致且子表长度动态变化用顺序存储浪费空间或难以管理”。6. 软考真题怎么考、怎么答题型分析和实战策略前面把每个知识点的原理讲透了这一节回到最实际的问题考试到底长什么样我怎么做才能拿分。6.1 上午题三个高频题型和对应解法上午题对这部分的考查集中在三块做题思路其实是模板化的。题型一数组地址计算。题干会给出数组维度、存储方式、基地址、元素大小求某元素的地址。解题流程判断是行优先还是列优先确认下标从0还是从1开始套公式算偏移量乘以元素大小加上基地址。每一步在草稿纸上写明别跳步。题型二矩阵压缩映射。给出特殊矩阵类型对称、三角、对角给一个矩阵元素坐标求它在压缩后一维数组的下标。解题流程画草图标号验证确认存储区域的排列顺序代入公式注意下标起始。如果公式忘了用穷举法画出前几行的排列规律也能推出来只是慢一点。题型三广义表深度、长度和表头表尾运算。解题流程长度数最外层元素个数深度逐层标注括号嵌套表头看第一个元素是什么表尾永远加括号。这类题建议你做完之后再反向验证一遍。从近五年真题的分布来看数组和矩阵计算出现频率最高广义表则是隔年考一次的节奏。但软考的知识点覆盖是波动的今年考什么很大程度上随机不能赌。6.2 下午题多维数据结构怎么嵌入算法大题下午题主要是算法设计你可能会在两类题里碰到多维数据结构第一类是C语言读程序题里出现二维数组的操作比如矩阵转置、矩阵乘法、求对角线元素和。这种题不用你写复杂算法只要看懂程序逻辑即可但注意二维数组在C语言里的存储方式行优先和指针的使用方式要理解清楚。第二类是算法设计题比如让你实现稀疏矩阵的加法或者实现一个基于二维数组的查找算法。这种题分值高答题时先说明数据结构的设计思路用什么结构存储数据为什么选这种结构再写算法步骤最后分析时间复杂度。阅卷老师看的是你的逻辑完整度所以即使代码写不完整把思路写清楚也能拿步骤分。6.3 备考实操我从真题里总结的做题顺序和取舍根据我备考软件设计师中级时的实际经验总结几点很实用的建议第一上午题做题顺序上数据结构部分不要放到最后。这类计算题需要清晰的思路如果先做其他模块被难住了再回头做计算题容易心浮气躁错误率会上升。第二计算题一定要在草稿纸上留下过程。不只是为了检查更是强制自己一步步走完计算链路。我见过太多人包括我自己早期备考时因为跳步在最后一个乘法或加减法上出错一道题白白丢分。第三矩阵压缩的公式需要自己推一遍而不是背一遍。你有意识地去推导每一个公式之后考场上就会更稳。第四别在广义表上耗费过多时间。它每年就考一两道题分值不高、内容也不难把表头表尾的两个规则记住、深度计算练三道题就够了把省下的时间投到其他薄弱环节。7. 个人备考经验和临场技巧最后说点课本上没有、但实际做题特别有用的东西。关于“公式记不住”的问题我的答案是不要强迫自己记公式要强迫自己画图。矩阵压缩的公式之所以容易忘是因为它只是一串符号但如果你每道题都在草稿纸上画出那个三角形的存储区域亲手给几个元素标上号你会在标注过程中自然理解公式为什么长这样。标了两三道题之后公式就在脑子里了而且比死记硬背牢固得多。这个方法在考试当天依然适用——你只需要在草稿纸上默写一个小例子公式就能自己“跑出来”。关于“计算老是错”的问题我建议你做两类检查单位检查和边界检查。单位检查是指地址计算时看清楚元素大小是几个字节偏移量要不要乘边界检查是指算到数组边界元素时比如第0个元素、最后一个元素看公式算出来的位置合不合理。这两个检查做完计算题基本不会出错。还有一个容易被忽略的点软考上午题是机器阅卷只认选项不认过程。但你在草稿纸上写下的过程是你自己的唯一依据。做题时把题干里的关键条件圈出来——存储方式是行优先还是列优先、下标从第几个开始、每个元素占几个字节、求的是地址还是下标。这四个条件只要有一个看错整道题就白做。多维数据结构这部分内容在整个软件设计师考纲里只占一小块但它是典型的“投入产出比极高”的考点。原理不复杂题型很固定只要花两三天时间把数组计算、矩阵压缩、广义表这三个方向梳理一遍上午题里这几分的把握性会非常高。希望这篇文章能帮你把这块的知识脉络理清楚考场上不再在这些“小题”上丢分。
返回列表