☰
信息论与编码课后习题参考答案:从对答案到真会算的避坑指南
2026/10/15 13:38:38 网站建设 项目流程

简介:这份文档是《信息论与编码》曹雪虹教材的课后习题参考答案,面向正在学习信息论基础课程的高校学生与备考人员,帮助解决课后练习无从下手的困扰。压缩包内仅含1个doc文件,大小约695KB,内容按章节整理,覆盖马尔科夫链、状态图、信息熵、自信息量、条件熵等核心概念,并给出转移概率、状态转移矩阵、信息压缩与加密等延伸知识点的解题过程。从预览可见,答案包含马尔可夫信源稳态概率推导、掷骰子事件自信息量计算、离散无记忆信源序列信息量求解、四进制与八进制脉冲信息量比较等典型题型,步骤较为完整,便于对照教材逐题核对。目前已有2964人学习下载,适合作为课后自测、期末复习与概念梳理的参考材料。

1. 信息论与编码课后习题参考答案:从“对答案”到“真会算”的分水岭

很多人第一次翻开信息论与编码的教材,看到熵、互信息、信道容量、汉明码这些词,第一反应是“公式我认识,题我不会做”。尤其是曹雪虹老师这本教材,课后习题覆盖面广,从信源熵计算到香农编码、费诺编码、哈夫曼编码,再到循环码和卷积码,每一章都有大量需要动手推导的题目。于是“信息论与编码-曹雪虹-课后习题参考答案.doc”成了很多人搜索的关键词——大家想要的不是一份抄完就忘的答案,而是一条能让自己独立把题算对的路径。

这份参考答案真正的价值,不在于给出最终数字,而在于展示中间步骤:概率怎么归一化、联合熵怎么拆、信道矩阵怎么列、生成多项式怎么选。如果你只是把答案抄一遍,考试换个数就翻车;但如果你把每道题的推导逻辑吃透,信息论与编码这门课反而会成为你后续做通信、压缩、纠错的地基。这篇文章面向正在学这门课、或者需要快速复习信息论核心计算的人,从“怎么用参考答案”讲到“怎么自己算对”,再到“哪些坑最容易踩”。

2. 信源熵与互信息:参考答案里最容易被跳过的计算细节

2.1 先分清自信息、熵、联合熵、条件熵的适用场景

很多课后题第一眼看过去是在求“熵”,但实际要区分四种量:自信息、信源熵、联合熵、条件熵。参考答案里通常直接写结果,但如果你不清楚每个量的定义域,换一道题就会混。自信息是对单个事件而言,单位是比特;信源熵是对整个概率分布求期望;联合熵针对两个随机变量的联合分布;条件熵则是在已知一个变量后,另一个变量还剩多少不确定性。

我一般会先画一张表,把题目给的联合概率分布列成矩阵,行表示X,列表示Y,然后逐行逐列求和得到边缘概率。这一步不做,后面所有计算都是空中楼阁。参考答案里经常省略边缘概率的推导,直接代入公式,但恰恰是这一步决定了你后面会不会把行列搞反。

量公式单位常见误用
自信息I(x) = -log₂P(x)比特把概率为0的事件代入
信源熵H(X) = -ΣP(x)log₂P(x)比特/符号忘记归一化概率
联合熵H(X,Y) = -ΣΣP(x,y)log₂P(x,y)比特/符号对行列求和顺序错
条件熵H(YX) = ΣP(x)H(YX=x)

注意:概率为0的项在熵计算中贡献为0,不要因为log₂0无定义就跳过整道题,按极限处理即可。

2.2 用Python验证课后题里的熵计算

参考答案里的数字不一定错,但你最好自己写几行代码验证。下面这段代码以一道典型的联合概率分布题为例,计算边缘熵、联合熵、条件熵和互信息。你只需要把题目给的联合概率矩阵替换进去,就能快速核对答案。

import numpy as np # 联合概率分布 P(X,Y),行是X,列是Y # 这里用一个典型课后题分布作为示例 Pxy = np.array([ [1/8, 1/16, 1/32, 1/32], [1/16, 1/8, 1/32, 1/32], [1/16, 1/16, 1/16, 1/16], [1/4, 0, 0, 0] ]) # 归一化检查,防止题目数据录入错误 Pxy = Pxy / Pxy.sum() print("归一化后总和:", Pxy.sum()) # 边缘概率 Px = Pxy.sum(axis=1) # 对Y求和得到X的边缘 Py = Pxy.sum(axis=0) # 对X求和得到Y的边缘 # 熵函数,处理0概率 def entropy(p): p = p[p > 0] return -np.sum(p * np.log2(p)) H_X = entropy(Px) H_Y = entropy(Py) H_XY = entropy(Pxy.flatten()) # 条件熵 H(Y|X) = H(X,Y) - H(X) H_Y_given_X = H_XY - H_X H_X_given_Y = H_XY - H_Y # 互信息 I(X;Y) = H(X) + H(Y) - H(X,Y) I_XY = H_X + H_Y - H_XY print(f"H(X) = {H_X:.4f} bit") print(f"H(Y) = {H_Y:.4f} bit") print(f"H(X,Y) = {H_XY:.4f} bit") print(f"H(Y|X) = {H_Y_given_X:.4f} bit") print(f"H(X|Y) = {H_X_given_Y:.4f} bit") print(f"I(X;Y) = {I_XY:.4f} bit")

这段代码的关键在于entropy函数里先过滤掉零概率项,避免log2(0)报错。Pxy.sum(axis=1)得到的是行和,对应X的边缘分布;axis=0得到列和,对应Y的边缘分布。条件熵用H(X,Y)-H(X)计算,比直接套条件概率公式更不容易出错。互信息用H(X)+H(Y)-H(X,Y)验证,结果应该和H(Y)-H(Y|X)一致。如果你算出来的互信息是负数,那一定是边缘概率或联合概率搞反了。

2.3 参考答案里条件熵的两种算法怎么选

课后题里求条件熵通常有两种路径:一种是先求条件概率P(y|x),再对每个x求条件熵后加权;另一种是用H(X,Y)-H(X)。参考答案往往只写一种,但你自己要会两种,因为题目给的数据形式不同。如果题目直接给了联合概率矩阵,用减法更快;如果题目给了条件概率和边缘概率,那就直接加权求和。

我见过不少同学在考试时用减法,结果把H(X,Y)-H(Y)当成H(Y|X),整道题白算。记住:H(Y|X)=H(X,Y)-H(X),减掉的是条件里那个已知变量的熵。这个细节在参考答案里不会特意提醒,但它是血泪教训。

3. 信道容量与编码定理:从参考答案反推参数设置

3.1 信道矩阵的列归一化与对称信道判定

信道容量这一章的课后题,核心是给一个信道转移矩阵,求容量。参考答案通常会写“该信道为对称信道,容量为C=log₂M-H(行向量)”。但问题在于,很多题目给的不是标准对称信道,你需要先判断它是否对称,再决定用哪个公式。

判断对称信道的条件是:转移矩阵的每一行都是同一组元素的排列,每一列也是同一组元素的排列。如果只满足行排列,叫行对称;只满足列排列,叫列对称。强对称信道要求行列都对称。参考答案里经常直接说“由对称性可知”,但如果你没验证就套公式,遇到非对称信道就会翻车。

我一般会先做列归一化检查:每一列的和应该等于1,因为给定输入后,输出概率之和为1。如果列和不为1,说明矩阵抄错了或者题目给的是非归一化形式。然后检查行向量是否互为排列,列向量是否互为排列。

import numpy as np # 信道转移矩阵,行是输入,列是输出 P = np.array([ [0.7, 0.2, 0.1], [0.2, 0.7, 0.1], [0.1, 0.2, 0.7] ]) # 列归一化检查 col_sums = P.sum(axis=0) print("列和:", col_sums) assert np.allclose(col_sums, 1.0), "列和不为1,矩阵有误" # 行和检查 row_sums = P.sum(axis=1) print("行和:", row_sums) # 判断是否强对称:每行是否为同一组元素的排列 def is_permutation_rows(mat): sorted_rows = np.sort(mat, axis=1) return np.allclose(sorted_rows, sorted_rows[0]) def is_permutation_cols(mat): sorted_cols = np.sort(mat, axis=0) return np.allclose(sorted_cols, sorted_cols[:, 0]) print("行对称:", is_permutation_rows(P)) print("列对称:", is_permutation_cols(P)) # 对称信道容量 C = log2(M) - H(行向量) M = P.shape[1] row_entropy = -np.sum(P[0] * np.log2(P[0])) C = np.log2(M) - row_entropy print(f"信道容量 C = {C:.4f} bit/符号")

这段代码先验证列和,再判断行列对称性。is_permutation_rows把每一行排序后比较,如果所有行排序后相同,说明行之间是排列关系。对称信道容量公式C=log₂M-H(行向量)只适用于强对称信道,其中M是输出符号数。如果信道不是对称的,需要用Blahut-Arimoto算法迭代求容量,课后题一般不会要求手算到那一步,但你要知道公式的适用边界。

3.2 香农编码、费诺编码、哈夫曼编码的手算步骤对比

编码这一块的课后题,参考答案往往只给最终码字,但考试要求你写出完整过程。三种编码方法的手算步骤差异很大,我整理成表格方便对照。

编码方法核心步骤是否唯一平均码长特点
香农编码按概率降序,累加概率转二进制取位唯一接近但通常大于H(X)
费诺编码按概率分组,使两组概率和尽量接近不唯一接近H(X)
哈夫曼编码每次合并最小两个概率,反向分配0/1不唯一最优,等于或接近H(X)

香农编码的关键是累加概率的二进制表示取前ceil(-log₂P(x))位。费诺编码的关键是每次分组要尽量均分,分组方式不同会导致码长不同。哈夫曼编码从叶子反向建树,合并顺序决定码字但不影响平均码长。

我一般会先用哈夫曼编码求最优平均码长,再用香农编码验证是否满足H(X)≤L<H(X)+1。如果参考答案给的香农编码平均码长比哈夫曼还短,那一定是算错了,因为哈夫曼是最优的。

3.3 用代码复现哈夫曼编码并核对参考答案

手算哈夫曼编码容易在合并顺序上出错,尤其是概率相同时。下面这段代码实现哈夫曼编码,并输出每个符号的码字和平均码长,你可以直接和参考答案对比。

import heapq def huffman_coding(symbols, probs): # 构建优先队列,元素为(概率, 唯一标识, 节点) heap = [[p, i, {s: ""}] for i, (s, p) in enumerate(zip(symbols, probs))] heapq.heapify(heap) while len(heap) > 1: # 弹出两个最小概率节点 lo = heapq.heappop(heap) hi = heapq.heappop(heap) # 合并:左分支加0,右分支加1 for s in lo[2]: lo[2][s] = "0" + lo[2][s] for s in hi[2]: hi[2][s] = "1" + hi[2][s] # 新节点概率为两者之和 merged = {**lo[2], **hi[2]} heapq.heappush(heap, [lo[0] + hi[0], lo[1], merged]) return heap[0][2] symbols = ['a', 'b', 'c', 'd', 'e'] probs = [0.4, 0.2, 0.2, 0.1, 0.1] codes = huffman_coding(symbols, probs) avg_len = sum(probs[i] * len(codes[s]) for i, s in enumerate(symbols)) print("符号\t概率\t码字\t码长") for i, s in enumerate(symbols): print(f"{s}\t{probs[i]}\t{codes[s]}\t{len(codes[s])}") print(f"平均码长 L = {avg_len:.4f} bit")

代码里用heapq维护最小堆,每次弹出两个最小概率节点合并。lo[2]和hi[2]是字典,存储该节点下所有符号的当前码字。合并时左分支加0、右分支加1,这个约定和参考答案可能相反,但平均码长不变。avg_len用概率加权码长得到。如果参考答案的平均码长和这里不一致,先检查概率是否归一化,再检查合并顺序是否影响了码长分配。

4. 线性分组码与循环码:生成矩阵、校验矩阵的避坑指南

4.1 生成矩阵的系统化与校验矩阵的对应关系

线性分组码的课后题通常给生成矩阵G,要求求校验矩阵H、最小距离、纠错能力。参考答案里经常直接写H,但中间的系统化步骤省略了。如果你不把G化成系统形式[I|P],直接写H,很容易把P的位置搞反。

系统化生成矩阵的形式是G=[I_k|P],其中I_k是k阶单位阵,P是k×(n-k)矩阵。对应的校验矩阵是H=[P^T|I_{n-k}]。注意P^T是P的转置,I_{n-k}是n-k阶单位阵。参考答案里如果H和G不是这个关系,那要么G没系统化,要么H写错了。

我一般会先对G做行变换,把它变成系统形式。行变换包括交换两行、把某行加到另一行、某行乘以非零常数。这些操作不改变码空间,但会改变G的表示。系统化之后,P矩阵一目了然,H也就直接写出来了。

4.2 循环码的生成多项式与伴随式计算

循环码的课后题核心是生成多项式g(x)。参考答案通常给一个g(x),然后要求求生成矩阵、校验矩阵、编码电路。但g(x)必须满足两个条件:它是x^n-1的因式,且次数为n-k。如果题目给的g(x)不满足这两个条件,那这道题本身有问题。

伴随式译码是循环码的另一个重点。收到码字r(x)后,计算伴随式s(x)=r(x) mod g(x)。如果s(x)=0,说明没有错误;否则根据s(x)查表找错误图样。参考答案里经常只给伴随式结果,不展示多项式除法过程。我建议手算时用长除法,把每一步写清楚,这样考试时不会因为跳步丢分。

import numpy as np def poly_mod(dividend, divisor): """多项式模2除法,返回余数""" dividend = dividend.copy() divisor = divisor.copy() while len(dividend) >= len(divisor): if dividend[0] == 1: for i in range(len(divisor)): dividend[i] ^= divisor[i] dividend = dividend[1:] return dividend # 示例:g(x) = x^3 + x + 1,对应系数[1,0,1,1] g = np.array([1, 0, 1, 1]) # 收到码字 r(x) = x^6 + x^5 + x^2 + 1,对应[1,1,0,0,1,0,1] r = np.array([1, 1, 0, 0, 1, 0, 1]) syndrome = poly_mod(r, g) print("伴随式系数:", syndrome) if np.all(syndrome == 0): print("无错误") else: print("有错误,需查表译码")

这段代码实现模2多项式除法。poly_mod函数每次看被除数的最高位,如果是1就异或除数,然后右移一位。最终剩下的就是余数,即伴随式。注意多项式的系数顺序是从高次到低次,g=[1,0,1,1]对应x^3+0x^2+1x+1。如果伴随式全零,说明接收码字是合法码字;否则需要根据伴随式查错误图样表。

4.3 最小距离与纠错能力的快速判定

线性分组码的最小距离d_min等于非零码字的最小重量。参考答案里经常直接给d_min,但你要会自己验证。对于系统码,d_min等于G中非零行的最小重量,但这不是充分条件,因为行组合可能产生更小重量。最可靠的方法是枚举所有2^k个码字,找最小非零重量。课后题k一般不大,枚举可行。

纠错能力t=floor((d_min-1)/2)。如果d_min=3,可以纠1位错;d_min=4,可以纠1位错并检2位错;d_min=5,可以纠2位错。参考答案里如果写“可纠2位错”但d_min=4,那是不对的,因为4只能保证纠1位。

5. 卷积码与维特比译码:状态图、网格图的手算技巧

5.1 卷积码编码器的状态转移与输出计算

卷积码的课后题通常给一个编码器结构,要求画状态图、网格图,或者用维特比译码。参考答案里状态图往往画得很简略,但如果你不自己推一遍状态转移,考试时根本画不出来。

我一般会先确定约束长度和状态数。如果编码器有m级移位寄存器,状态数就是2^m。然后列出当前状态和输入下的下一状态及输出。以(2,1,3)卷积码为例,m=2,状态数4。输入1位,输出2位。状态转移表可以手算,也可以用代码生成。

def conv_encoder(input_bits, g1, g2, m): """卷积码编码器,g1和g2是生成多项式系数""" state = [0] * m output = [] for bit in input_bits: # 当前输入加上移位寄存器内容 reg = [bit] + state # 模2和 out1 = sum(reg[i] * g1[i] for i in range(len(g1))) % 2 out2 = sum(reg[i] * g2[i] for i in range(len(g2))) % 2 output.extend([out1, out2]) # 更新状态 state = [bit] + state[:-1] return output # 示例:g1=111, g2=101, m=2 input_bits = [1, 0, 1, 1] g1 = [1, 1, 1] g2 = [1, 0, 1] encoded = conv_encoder(input_bits, g1, g2, 2) print("编码输出:", encoded)

代码里reg是当前输入位加上移位寄存器状态,g1和g2分别对应两个输出支路的抽头系数。sum(...)%2实现模2和。每编码一位,状态右移并填入新输入。输出顺序是每输入一位输出两位,所以最终输出长度是输入长度的两倍。你可以用这段代码验证参考答案里的编码结果。

5.2 维特比译码的路径度量与回溯

维特比译码是卷积码课后题的重头戏。参考答案通常给一个接收序列,要求找最大似然路径。手算时容易在路径度量累加和回溯上出错。我一般会画网格图,每个时刻每个状态标出累积度量,然后从最后一个时刻的最小度量状态回溯。

关键点:路径度量是汉明距离的累加,不是欧氏距离。每到一个状态,比较两条到达路径的累积度量,保留小的那条。如果两条相等,可以任选一条,但参考答案可能只给一种。回溯时从最终状态沿幸存路径反向走到起点,得到译码输出。

注意:维特比译码的复杂度随状态数指数增长,课后题状态数一般不超过8,手算可行。如果状态数超过16,建议写代码。

6. 用参考答案的常见翻车现场与排查清单

6.1 概率未归一化导致熵计算全错

现象:算出来的熵大于log₂N,或者互信息为负。 原因:题目给的联合概率矩阵没有归一化,或者你抄错了某个概率值。 解决:第一步永远是对概率矩阵求和,如果不是1,先归一化再算。参考答案里的概率通常已经归一化,但你自己录入代码时要检查。

6.2 信道矩阵行列搞反导致容量公式用错

现象:套对称信道容量公式算出的C和参考答案差很多。 原因:把行当成输出、列当成输入,或者判断对称性时只看行不看列。 解决:记住信道矩阵的行对应输入、列对应输出。列和必须为1。判断对称性时行列都要检查。非对称信道不能用log₂M-H(行)。

6.3 哈夫曼编码合并顺序不同导致码字不一致

现象:你的码字和参考答案不一样,但平均码长相同。 原因:哈夫曼编码不唯一,概率相同时合并顺序不同会产生不同码字。 解决:只要平均码长一致,码字不同不算错。但如果平均码长不同,检查是否某次合并没选最小的两个概率。

6.4 循环码生成多项式次数不对导致矩阵维度错

现象:生成的矩阵行数或列数和参考答案不一致。 原因:g(x)的次数不等于n-k,或者g(x)不是x^n-1的因式。 解决:先验证g(x)的次数,再验证它能否整除x^n-1。如果题目给的g(x)不满足,那题目本身有问题,按题目要求做即可,但心里要知道边界。

6.5 维特比译码回溯路径选错导致译码输出不同

现象:你的译码输出和参考答案不同,但路径度量相同。 原因:两条路径累积度量相等时,参考答案选了另一条。 解决:维特比译码在度量相等时输出不唯一,只要你的路径度量是最小的,就算正确。考试时如果参考答案只给一种,可以备注“另一条等度量路径也可”。

7. 把参考答案变成自己的验算工具:一个可复用的检查习惯

我后来养成了一个习惯:每做完一道信息论与编码的课后题,不急着对参考答案,而是先用自己的代码或手算表格验证三个东西——概率是否归一化、熵是否在合理范围、码长是否满足编码定理的下界。这个习惯让我在考试时少丢了很多冤枉分。

具体来说,熵计算后检查0≤H(X)≤log₂N,互信息检查0≤I(X;Y)≤min(H(X),H(Y)),信道容量检查0≤C≤log₂M,平均码长检查H(X)≤L<H(X)+1。这些不等式就像后悔药,一旦越界,不用看参考答案就知道自己错了。

对于循环码和卷积码,我会用Python把生成矩阵、校验矩阵、状态转移表都打印出来,和手算结果逐行对比。代码不会骗人,如果代码和手算不一致,那一定是手算某一步漏了或抄错了。这个验算过程比单纯抄答案慢,但做上十道题之后,速度会反过来超过抄答案,因为你不必反复翻参考答案确认。

最后一个技巧:把参考答案里的最终数字遮住,只看题目,自己完整算一遍,然后拿代码验证。如果代码结果和参考答案一致,说明你算对了;如果不一致,先查代码,再查手算,最后才怀疑参考答案。多数情况下,参考答案是对的,但偶尔也会有印刷错误,这时候你的验算能力就是最后的防线。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询