ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

软考多维数据结构全解:数组地址计算、矩阵压缩与广义表速成指南

软考多维数据结构全解:数组地址计算、矩阵压缩与广义表速成指南 1. 多维数据结构在软考中的位置与备考策略软考软件设计师中级的数据结构部分常被考生当成“背定义、记代码”的模块来处理但真题做多了就会发现数组、矩阵压缩、广义表这三个考点恰恰是上午题选择题中“看似简单实则陷阱密集”的区域。尤其是数组存储地址计算和矩阵压缩后的下标映射几乎每年都考而且年年都有不少人在这里丢分。先说清楚这几块内容到底解决什么问题。数组是最基础的内存连续存储结构要搞清楚的是“给定下标元素存在哪个地址”以及“给定地址反推下标”这两类计算题。矩阵压缩则是利用矩阵中相同元素或零元素的分布规律把二维数据“压”进一维数组省内存的同时必须能快速完成下标互换。广义表则是线性表的推广允许元素本身又是一个表考试重点在表头、表尾的递归定义、长度与深度的计算以及存储结构的理解。这三块内容为什么值得单独写一篇全解因为它们在软考大纲里属于“数据结构基础”但不是简单地背几个公式就能过关。你需要理解行优先、列优先的本质要会推导对称矩阵、三角矩阵的压缩映射公式还要能快速判断广义表的表头表尾。下面我会按真题的出题逻辑把每个考点的原理、公式、推演过程、易错点全部拆开讲顺带附上我自己刷题和复盘时整理出来的速记方法。2. 数组存储与地址计算行优先和列优先必须形成肌肉记忆2.1 数组内存分配的底层逻辑在讲地址计算之前先建立一个底层认知数组在内存中是一段连续空间。所谓连续意思是每个元素占用的存储单元长度相同且元素之间没有空隙。比如一个int数组每个元素占4字节那么第i个元素的地址就是首地址 i * 4。这个逻辑看起来简单但软考经常在里面加“干扰项”比如告诉你数组下标从1开始或者从0开始还告诉你每个元素占多少个存储单元。多维数组的存储方式只有两种行优先和列优先。行优先就是先存完第一行的所有元素再存第二行列优先则是先存完第一列的所有元素再存第二列。C语言默认行优先Fortran默认列优先。软考题目里通常会明确说明采用哪种方式但如果没说明默认按行优先处理。要理解这两种方式的本质区别可以用“铺地板”来类比。行优先像是一排一排地贴瓷砖贴完第一排再贴第二排列优先像是一列一列地贴贴完第一列再贴第二列。对于同一个二维数组两种方式下某个元素前面“已经贴了多少块砖”是不同的这就是地址计算公式的差别所在。2.2 一维数组与二维数组的地址公式推导一维数组的地址计算几乎没有难度但软考会把它和指针、下标范围结合起来考。设数组A[0..n-1]每个元素占L个存储单元首地址为LOC(a0)那么LOC(ai) LOC(a0) i * L。如果数组下标是从1开始则LOC(ai) LOC(a1) (i-1) * L。你需要做的就是把“下标从几开始”这个条件刻在脑子里因为它直接决定公式里是i还是i-1。二维数组稍微复杂一点。设数组A[0..m-1][0..n-1]行优先存储时元素A[i][j]的地址计算公式是LOC(A[i][j]) LOC(A[0][0]) (i * n j) * L其中n是每行的元素个数即列数。如果下标从1开始公式变成LOC(A[i][j]) LOC(A[1][1]) ((i-1) * n (j-1)) * L列优先存储时元素A[i][j]的地址是LOC(A[i][j]) LOC(A[0][0]) (j * m i) * L其中m是行数。注意列优先是要看“前面有多少列”每列有m个元素所以在j上乘m。这里有一个真题常考的变形不是直接给你两个下标而是给你一个一维数组的下标让你反推二维下标。比如按行优先存储的A[1..8][1..10]问第20个元素是哪个这种题的解法是先算偏移量即20-119然后做除法和取余。19 ÷ 10 1余9所以行偏移为1、列偏移为9对应A[2][10]。这个技巧叫“偏移量反推下标法”一旦会用几乎能秒杀所有同类题。2.3 地址计算题的5个常见陷阱地址计算题丢分很少是因为不会公式多半是栽在细节上。我复盘了近五年的真题发现高频陷阱集中在以下几个方面第一个陷阱是忽略数组下标起始值。题目写A[1..n]还是A[0..n-1]公式完全不同。很多考生习惯性地用从0开始的公式结果算出来的地址差了一个L。第二个陷阱是混淆行数和列数。行优先公式里乘的是列数不是行数。比如A[3][4]行优先的A[2][3]前面有2*4311个元素而不是2*3410。这个错误特别隐蔽因为一旦题目行列数相差不大算出来的结果可能“看起来差不多”。第三个陷阱是把数组元素大小和存储单元长度混为一谈。题目说“每个元素占2个存储单元”那么L2但如果不幸看到“每个元素占4字节”而数组以字Word为编址单位那么L1因为一个元素占1个字。需要仔细甄别编址单位。第四个陷阱是二维数组的“列优先”考法。很多考生只练了行优先看到列优先就直接用行优先公式结果错得离谱。应对方法很简单只要记住行优先乘列数列优先乘行数。第五个陷阱是求整个数组占用的存储空间。这时不是算某个元素的地址而是算从首地址到末地址的差再加上一个元素大小。公式为总字节数 元素总数 × L也可以写成末地址 - 首地址 L。如果问“最后一个元素的地址”不要忘了它是首地址 (元素总数-1) * L不是首地址 元素总数 * L。提示做这类题时我习惯先在草稿纸上写下“行数、列数、起始下标、L、存储方式”五个要素再开始套公式。这样做能大幅降低看错题的概率。3. 矩阵压缩存储二维关系如何映射到一维空间3.1 压缩存储的适用场景特殊矩阵与稀疏矩阵不是所有矩阵都需要压缩。软考考的是两类一类是特殊矩阵即元素分布有规律可循的矩阵比如对称矩阵、三角矩阵、对角矩阵另一类是稀疏矩阵即零元素特别多的矩阵通常认为非零元素个数占比小于5%时称为稀疏矩阵。特殊矩阵的压缩思路是“只存有用的元素”把二维矩阵的下标映射到一维数组的下标。这种映射必须是可逆的也就是说从(i,j)能推出k从k也能反推出(i,j)。软考考查的重点就是这两个方向的推导。稀疏矩阵的压缩思路则完全不同。它只存储非零元素的行号、列号和值也就是三元组(row, col, value)同时还要记录矩阵的总行数和总列数否则无法还原。稀疏矩阵在软考中通常以三元组表的形式出现偶尔会考十字链表的结构但三元组更常考。3.2 对称矩阵的压缩公式与下标互推对称矩阵的特点是A[i][j] A[j][i]所以只需要存储上三角或下三角含对角线的元素就能还原整个矩阵。假设矩阵是n × n按行优先存储下三角含对角线元素则元素总数为n(n1)/2。下三角元素A[i][j]其中i ≥ j在一维数组中的下标k计算方法是先算前面0到i-1行有多少个元素第p行p从0开始有p1个元素所以前i行元素总数为i(i1)/2再加上当前行中A[i][j]是该行的第j个元素j从0开始于是k i(i1)/2 j如果矩阵下标从1开始那么A[i][j]i ≥ j的下标公式是k i(i-1)/2 j - 1这里需要特别注意一维数组的下标通常从0开始但真题里也可能从1开始要看清题目条件。上三角元素A[i][j]i j怎么处理利用对称性把它映射成A[j][i]再套下三角的公式即可。即k j(j1)/2 i 下标从0开始反过来从一维数组下标k反推二维下标(i,j)是软考中偏难的考法。思路是找最大的i使得i(i1)/2 ≤ k那么行号就是i列号就是k - i(i1)/2。这个逆向过程需要一定的数学敏感度我在后面会给出具体例题演示。3.3 三角矩阵和对角矩阵的压缩方法三角矩阵分上三角和下三角。下三角矩阵的压缩方式和对称矩阵几乎一样但不是每个元素都能通过对称性还原。因为三角矩阵的另一半全是常数c通常是0所以存储时除了n(n1)/2个下三角元素外还要额外存一个常数c。也就是说一维数组的总长度是n(n1)/2 1。下三角矩阵A[i][j]i ≥ j的映射公式与对称矩阵相同而i j时所有元素都对应那个常数c。上三角矩阵的压缩稍微绕一点。按行优先存储上三角含对角线第p行p从0开始有n-p个元素前i行元素总数为n (n-1) ... (n-i1) i(2n - i 1)/2。元素A[i][j]i ≤ j在其所在行中排第j-i个所以k i(2n - i 1)/2 (j - i)对角矩阵就简单多了常见的三对角矩阵只有主对角线及其上下相邻的两条对角线上的元素非零。软考考查的重点是“给定(i,j)判断是否在三条对角线上”即|i - j| ≤ 1时是有效元素否则是0。三对角矩阵的压缩通常按行优先存储这三条对角线上的元素每条对角线上的元素个数不同公式会更复杂一些但真题里通常只要求判断元素是否为0以及总存储量的大致估算。3.4 稀疏矩阵的三元组表示与十字链表稀疏矩阵的三元组表示法本质上是把“矩阵”这个二维结构翻译成一张“只记录非零元素”的线性表。每个三元组包含三个字段行号、列号、值。存储时通常还有两个额外信息矩阵的行数、列数以及非零元素的个数。软考对三元组的考法主要有三种第一种是给你一个稀疏矩阵让你写出它的三元组表第二种是给你三元组表让你还原矩阵第三种是考三元组表的转置操作即交换行号和列号并重新排序。三元组表的转置有一个经典算法先统计原矩阵每一列中非零元素的个数再算出每一列第一个非零元素在转置后三元组表中的起始位置最后扫描原三元组表按照“列号从小到大”的顺序放入转置后的数组。这个算法的时间复杂度是O(n t)其中n是列数t是非零元素个数比直接扫描矩阵找非零元素再逐个转置要高效得多。十字链表法在软考中出现频率不高但偶尔会在上午题中以概念题出现。它把每一行和每一列分别用一条链表串起来每个非零元素节点同时挂在行链表和列链表上。十字链表的优势是插入和删除操作更灵活不用像三元组表那样移动大量元素但结构更复杂。软考只要求掌握结构图的识别和基本概念不需要实现。注意稀疏矩阵的三元组表元素的排列顺序通常按行优先排序也就是先按行号从小到大行号相同时按列号从小到大。转置后为了保持这个顺序需要重新排列这正是前面说的经典算法要解决的问题。4. 广义表表中有表的递归结构如何准确拆解4.1 广义表的定义与基础操作广义表是线性表的推广线性表中的元素必须是单个数据元素而广义表中的元素可以是单个元素也可以是一个广义表。比如A (a, (b, c), d)就是一个广义表它的第二个元素是子表(b, c)。广义表用大写字母表示表名用小写字母表示原子单个数据元素。原子的深度为0空表的深度为1非空表的深度等于“括号嵌套的最大层数”。比如(a, (b, (c)))的深度是3。广义表有两个基本操作取表头Head和取表尾Tail。表头是广义表的第一个元素它可以是原子也可以是子表表尾是除去第一个元素后剩余元素组成的表。注意表尾一定是一个表即使只有一个元素它也是一个表的形式。比如(a, b, c)的表头是a表尾是(b, c)而不是b或c。4.2 表头、表尾的递归拆解技巧软考对广义表的考查最经典的一类题是“已知广义表求连续取表头或表尾后的结果”。这类题的目的不是考验记忆力而是考验对递归定义的理解。要拆解这类题我的经验是每次只做一步操作然后在草稿纸上把新的表写出来再继续下一步。比如广义表L ((a, b), c, d)求Tail(Head(Tail(L)))。可以从最内层开始拆解Tail(L)L去掉第一个元素(a, b)剩下的是(c, d)所以Tail(L) (c, d)。Head(Tail(L))取(c, d)的表头是c。Tail(Head(Tail(L)))取c的表尾。注意c是原子原子的表尾是空表()因为广义表的定义中原子可以看作退化的广义表它的表尾为空。所以结果是()。这里有个易错点原子也能取表尾吗严格来说广义表的表头、表尾操作要求操作对象是广义表。但在软考的题目中原子常被视为一个仅含该原子的广义表所以其表尾是空表。这一点要注意。当然如果题目没有明确说原子不能取表尾默认按广义表规则处理。4.3 广义表的长度与深度计算长度是广义表中元素的个数这里“元素”指的是直接元素不递归展开。比如(a, (b, c), d)的长度是3因为直接元素是a、(b, c)、d三个。深度则是括号嵌套的最大层数空表深度为1原子深度为0。比如(a, (b, (c)))的深度是3。深度计算的递归定义是Depth(A) 1 max(Depth(x))其中x是A的所有直接元素。特殊地原子的深度为0空表即()的深度为1。我建议遇到深度计算题时先画出括号嵌套的层级然后从最内层往外逐层加1这样不容易出错。软考偶尔会把广义表和二叉树、图的遍历结合起来考。比如让你判断一个广义表和某棵树的结构是否一致或者用广义表表示一棵二叉树。二叉树可以表示为(根节点左子树右子树)其中左子树和右子树本身又是广义表。这种题的关键是分清“节点”和“子树”不要混淆。4.4 广义表存储结构的理解要点广义表可以采用两种存储结构头尾链表存储结构和扩展线性链表存储结构。软考对存储结构的考查停留在概念层面通常不会让你手写完整代码但会考这两种结构的节点类型和图解识别。头尾链表存储结构中每个表节点包含两个指针表头指针和表尾指针。表头指针指向该表的第一个元素表尾指针指向除去第一个元素后剩余元素组成的表。如果元素是原子则用原子节点存储原子节点中有一个标志位区分原子和子表同时存储数据值。扩展线性链表存储结构中每个节点也包含两个指针第一个指针指向该表的第一个元素第二个指针指向该表的下一个兄弟元素。这种结构更像是把树转换成二叉树的过程每个表看成一棵树第一个元素是“长子”第二个指针指向“下一个兄弟”。在考试中如果能识别出这两种结构的差异选择题基本就能拿分。如果遇到画图题优先按“头尾链表结构”画因为它在教材中出现的频率更高。提示广义表的内容看起来抽象但考试题型非常固定。我建议集中刷最近5年的选择题把每一道广义表题归纳为“求表头表尾”“求长度深度”“判断存储结构”三类你会发现出题套路几乎没有变化。5. 软考真题高频题型从概念到计算的全拆解5.1 选择题常考的4类题型与速解模板结合历年真题多维数据结构部分的选择题基本逃不出下面这4类。我把每一类的出题特点和速解思路整理成了模板临考前直接背模板比临时推导要稳妥得多。第一类数组地址计算题。核心思路是“要素定位法”。先在草稿纸上写下五个要素——数组维度、各维范围下标从几到几、存储方式行优先还是列优先、每个元素占用单元数L、数组首地址。然后根据元素下标计算偏移量再乘以L加上首地址。遇到反推下标的题用“偏移量除列数或行数”的方法。第二类矩阵压缩映射题。核心思路是“先判断矩阵类型再套对应的映射公式”。对称矩阵和三角矩阵的难点在于公式记忆我建议不要死记硬背而是理解“前i-1行元素总数 当前行内的偏移”这个推导逻辑。考试时如果一时想不起公式可以用小矩阵比如3×3现场推一遍熟练后不到一分钟就能推出来。第三类广义表计算题。核心思路是“一步一化简”。表头表尾的操作每次只做一步写出中间结果再继续。长度只看直接元素个数深度看括号层数。遇到混合运算从最内层括号开始拆。第四类稀疏矩阵与三元组表题。核心思路是“按行优先顺序扫描矩阵逐行收集非零元素”。转置题用“统计-定位-填充”三步法不要硬转置。5.2 下午题中数据结构的低频出现与应对策略软件设计师下午题案例分析题通常包含数据流图、数据库设计、UML建模、算法设计和C语言编程等题目多维数据结构很少作为独立大题出现但它的身影会悄悄藏在算法设计题中。最典型的是算法设计题中会要求你实现“矩阵相加”“矩阵转置”“稀疏矩阵乘法”之类的操作。这类题目如果考到稀疏矩阵往往需要你定义三元组结构体并实现转置或乘法算法。虽然近几年下午题更偏重排序、查找和图算法但多维数据结构的底子不好遇到矩阵题就会特别吃力。我的建议是把数组、矩阵压缩、广义表的代码实现能力作为“备而不用”的储备。重点掌握两个代码模板三元组表的快速转置算法和对称矩阵的压缩存储赋值与读取。这两个代码量都不大逻辑清晰万一考到就是送分题。5.3 易错题型专项下标互推与连续的Tail操作下标互推是选择题中错误率最高的一类。比如按行优先存储的对称矩阵A[1..6][1..6]只存储下三角问A[4][2]存储在一维数组的第几个位置假设一维数组下标从1开始。解法因为存储下三角且i4 ≥ j2根据公式k i(i-1)/2 j 4*3/2 2 8。这里的8就是在一维数组中的位置从1开始。如果你用从0开始的公式会算出7和正确答案差1。连续的Tail操作是另一个高频失分点。比如广义表L ((a, b, c), d, (e, f))求Tail(Tail(L))。Tail(L) (d, (e, f))Tail(Tail(L)) ((e, f))注意((e, f))是一个表它的唯一元素是子表(e, f)。所以Tail(Tail(L))和(e, f)是不同的。前者是一个“外层还有一个括号”的表后者是子表本身。有些考题会故意在这里设陷阱问你两者的区别或者继续做Head操作。如果题目问Head(Tail(Tail(L)))结果是(e, f)而不是e。5.4 近5年真题考点频率分析我统计了近5年2019-2023软考软件设计师真题中多维数据结构相关题目的分布大致频率如下考点出现频率典型出题形式二维数组地址计算每年1-2题行优先/列优先求地址或反推下标对称矩阵/三角矩阵压缩每1-2年1题求一维下标求存储总长稀疏矩阵三元组每2年1题三元组表转置还原矩阵广义表表头表尾每年1题连续取表头/表尾求结果广义表长度/深度每1-2年1题直接计算或结合存储结构十字链表概念近年较少结构识别、与三元组对比从频率来看数组地址计算和广义表是绝对的重点每年基本都能遇到。矩阵压缩的考频略低但一旦出现分值通常是2分而且因为公式繁多容易被拉开差距。6. 临考冲刺高频考点速记与错误避坑指南6.1 考前必背公式速查表临考前几天不建议再去翻教材推导公式而应该把高频公式浓缩在一张纸上每天过一遍。下面这张速查表是我自己整理的重点按考题出现频率排序。内容公式或结论使用条件一维数组地址LOC(ai) LOC(a0) i × L下标从0开始二维数组行优先地址LOC(A[i][j]) LOC(A[0][0]) (i × n j) × L下标从0开始n为列数二维数组列优先地址LOC(A[i][j]) LOC(A[0][0]) (j × m i) × L下标从0开始m为行数对称矩阵下三角下标k i(i1)/2 j下标从0开始i ≥ j下三角矩阵存储总长n(n1)/2 1含常数c上三角矩阵下标k i(2n - i 1)/2 (j - i)下标从0开始i ≤ j广义表长度直接元素个数不递归展开广义表深度1 max(子表深度)原子深度为0空表深度为1广义表表头第一个元素可能是原子或子表广义表表尾除第一个元素外其余元素组成的表一定是表不是原子这张表不用死记而是要能“推”。前面已经讲了怎么推导考场上一旦卡壳用一个3×3的小矩阵快速验算即可。6.2 多选题与概念题的命题陷阱软考上午题虽然以单选为主但概念题中会出现“以下说法正确的是”“以下错误的是”这类变相多选题干扰项设置非常刁钻。针对多维数据结构常见的陷阱说法有陷阱一“对称矩阵只需要存储下三角元素。”这种说法不严谨应该说“只需存储上三角或下三角其一”因为从另一个三角可以通过对称性推出来。陷阱二“广义表的深度等于长度。”这完全是两个概念长度是元素个数深度是嵌套层数没有任何必然关系。比如((a), (b), (c))长度为3深度为2。陷阱三“三元组表能直接进行矩阵加减法运算。”三元组表存储的是稀疏矩阵的非零元素如果两个矩阵的非零元素位置不一致直接运算会非常麻烦通常需要先还原成二维数组或者进行特殊处理。所以这个说法是错的。陷阱四“数组是一种非线性结构。”数组在逻辑上是线性结构因为元素之间存在唯一的前驱和后继关系。多维数组从存储角度看是线性的从逻辑上看是多维的但软考通常将数组归为线性结构。因此看到“数组是非线性结构”必须果断排除。陷阱五“广义表中不能包含自身。”这其实是递归定义造成的误解。广义表可以递归定义某些广义表在概念上可以引用自身比如A (a, A)。虽然这种表在计算机存储中实现比较复杂但在理论定义上是允许的。软考如果考这个概念通常是以判断题形式出现。6.3 三个最容易在考场上犯的低级错误经验之谈简化成三个字漏、转、混。漏是指漏掉题目开头那句不起眼的条件比如“假设数组下标从1开始”或者在矩阵压缩题里漏看“矩阵从1开始编号”导致整个公式用错。解决方法是做题前把题干的数字条件圈出来特别是起点、步长、边界值。转是指转置矩阵时忘了重新排序。三元组表转置后行号列号虽然交换了但顺序不会自动变成“按行优先”需要重新按新行号排序。有些题直接问“转置后的三元组表是什么”如果你没有排序答案就错了。混是指混淆行优先和列优先。软考为了增加区分度有时会在同一个题目里同时出现“数组A按行优先存储”“矩阵B按列优先存储”如果你在计算时没有区分开后面就全乱了。6.4 刷题路径与复习节奏建议如果你距离考试还有三到四周我建议按下面的节奏推进多维数据结构部分第一周用两天时间把数组地址计算全部类型刷一遍包括一维、二维、下标从1开始、列优先、反推下标。重点理解“偏移量”的概念而不是死背公式。再用两天时间攻克矩阵压缩把对称矩阵、三角矩阵的推导过程亲自做一遍然后刷真题。第二周用两天时间刷广义表的表头表尾和长度深度题把近五年的真题全部做一遍总结出题套路。后面几天开始做套题遇到多维数据结构的题就重点标记整理错题本。第三周及以后每天花10分钟过一遍速查表然后从近三年真题中随机抽取多维数据结构相关题目练手保持手感。如果时间紧张优先保证数组地址计算和广义表题目因为这两类题最稳定、最容易拿分。我个人在备考时有个习惯把每一道错题都抄在一个小本子上并标注错误原因公式用错、漏看条件、计算粗心等。考前三天翻一遍错题本效果比做十套新题都好。因为错题本记录的正是你的思维盲区而新题很难精准命中这些盲区。7. 实操心得我用“小矩阵反推法”解决公式遗忘问题最后分享一个实战技巧。很多考生担心考场上公式记不住我自己的应对办法是“小矩阵反推法”——不管遇到什么矩阵压缩题先写一个3×3的小矩阵用最笨的方式把存储序列列出来然后反推公式。举个例子记不清上三角矩阵的映射公式时我就写一个3×3的上三角矩阵1 2 3 0 4 5 0 0 6按行优先存储存储序列是1 2 3 4 5 6对应位置关系是A[0][0] → 0A[0][1] → 1A[0][2] → 2A[1][1] → 3A[1][2] → 4A[2][2] → 5。然后我用A[1][2]来验证公式k i(2n - i 1)/2 (j - i) 1 * (6 - 1 1)/2 (2 - 1) 1 * 6 / 2 1 3 1 4结果和手动序列吻合说明公式正确。这个方法在考场上特别实用。因为3×3矩阵的存储序列不超过6个元素手动列出来只需要十几秒钟但能帮你确认公式是否正确避免因为记忆偏差导致整题全错。多维数据结构在软考中占的分值并不算最多但它性价比很高。只要理解了底层逻辑、掌握了公式推导方法、刷透近五年真题这部分分数基本是必拿的。不要在这些基础题上丢分你的上午题总分就会更有保障。
返回列表