1. 从“循环”说起:为什么我们需要循环码?
如果你学过信息论或者编码理论,大概率对线性分组码、汉明码这些概念不陌生。它们就像是给原始信息穿上了一层“防弹衣”,在传输过程中即使被“子弹”(噪声)击中,也能通过校验位把错误找出来甚至纠正。但今天我们要聊的循环码,它不仅是“防弹衣”,更是一件“有特殊编织纹理的防弹衣”。这个“纹理”,就是“循环性”。
我第一次接触循环码时,觉得这个概念有点“绕”。什么叫“循环”?简单说,就是码字(一个合法的编码序列)向左或向右循环移位任意位后,得到的新序列仍然是一个合法的码字。比如,一个码字是1101000,把它循环左移一位变成1010001,这个新序列也必须是这个码集合里的一个码。这个性质听起来有点数学美感,但它的威力远不止于此。正是这个看似简单的循环特性,让循环码的编码和译码电路变得异常简单和高效——你几乎可以用一个带反馈的移位寄存器就搞定编码,这在硬件实现上简直是福音。
那么,为什么在信息论的复习中,循环码是绕不开的重点?因为它是连接理论抽象和工程实践的一座关键桥梁。线性分组码告诉了我们纠错码的数学框架,而循环码则在这个框架上,引入了多项式环这个强大的代数工具,让码的构造、分析和实现都变得系统化。无论是你手机里的4G/5G信号,还是Wi-Fi传输、卫星通信,甚至是光盘和二维码(QR Code)里,循环码或其衍生码(如BCH码、RS码)都扮演着核心角色。搞懂循环码,你才算真正摸到了现代数字通信和存储系统可靠性的“门道”。
2. 核心基石:如何用多项式“描述”一个码?
要玩转循环码,你必须先掌握它的“语言”——多项式。这不是高等数学里那种求导积分的多项式,而是系数在二元域GF(2)(就是0和1,加法是异或,乘法是与)上的多项式。一个长度为n的二进制序列,可以直接对应成一个次数不超过n-1的多项式。
举个例子:码字1101(通常我们认为最左边是最高位),对应的多项式就是1*x^3 + 1*x^2 + 0*x^1 + 1*x^0 = x^3 + x^2 + 1。看到了吗?序列的每一位就是多项式对应次幂的系数。
循环码的整个理论大厦,就建立在一条核心性质上:一个 (n, k) 循环码(即长度为 n,信息位为 k 的循环码)中,所有码字多项式都是某个称为“生成多项式” g(x) 的倍式。同时,g(x) 本身必须是x^n + 1的一个因式,且其次数为r = n - k。
这有点抽象,我们拆开看:
- 生成多项式 g(x):这是循环码的“灵魂”。它是一个
r次多项式(r = n - k)。一旦确定了 g(x),整个码的所有码字就都确定了。所有码字多项式c(x)都可以写成c(x) = m(x) * g(x),其中m(x)是次数小于k的任意信息多项式。 x^n + 1的因式:这个条件保证了码的“循环性”。它意味着如果你对一个码字多项式进行循环移位,相当于乘以x再对x^n + 1取模,结果仍然会是g(x)的倍式,从而还是一个合法码字。
实操中的关键点:如何找到一个可用的 g(x)?这通常需要分解x^n + 1。在二元域下,x^n + 1可以分解成若干个“既约多项式”(类似于整数中的质数)的乘积。选择其中一些因子的乘积作为 g(x),就能生成一个循环码。例如,x^7 + 1可以分解为(x+1)(x^3+x+1)(x^3+x^2+1)。如果我们选择g(x) = (x^3+x+1),那么就能得到一个 (7, 4) 循环码(因为 g(x) 次数为 3,所以n=7, k=4)。这个码其实就是著名的 (7,4) 汉明码,它恰好是一个循环码。
注意:这里有个容易混淆的地方。不是所有线性码都是循环码,但汉明码中有一些特定的参数(如(7,4), (15,11))可以构成循环码。当你看到“循环汉明码”时,指的就是这种具有循环结构的特殊汉明码。
3. 编码实战:两种方法,从原理到电路
知道了 g(x),我们怎么把一个 k 位的信息组m编成一个 n 位的码字c呢?有两种主流方法,它们本质相通,但思路不同。
3.1 方法一:非系统码编码
这是最直接的方法。既然码字c(x) = m(x) * g(x),那么直接把信息多项式m(x)和生成多项式g(x)在GF(2)上乘起来就行了。
步骤:
- 将 k 位信息组表示为信息多项式
m(x)(次数 < k)。 - 计算
c(x) = m(x) * g(x)。 - 将
c(x)的系数写出,得到一个 n 位码字(因为m(x)最高k-1次,g(x)是r次,乘起来最高n-1次)。
例子:对于 (7,4) 循环码,g(x)=x^3+x+1。假设信息m=1101,则m(x)=x^3+x^2+1。 计算c(x) = (x^3+x^2+1)(x^3+x+1) = x^6 + x^5 + x^4 + x^3 + x^2 + x + 1(在 GF(2) 上计算,注意1+1=0)。 所以码字c=1111111。看,所有位都是1。
特点:简单粗暴,但生成的码字不是“系统码”。也就是说,在码字c中,你无法直接看到原始的信息位m,信息位和校验位是混合在一起的。这在某些需要直接提取信息的场景下不太方便。
3.2 方法二:系统码编码(更常用)
我们更希望码字的前 k 位(或后 k 位)就是原始信息位,后面跟着 r 位校验位。这种形式称为系统码。循环码可以很方便地编成系统码。
步骤:
- 将信息多项式
m(x)乘以x^r(即左移 r 位),得到x^r * m(x)。这相当于在信息位后面预留出 r 个校验位的位置。 - 用
g(x)去除x^r * m(x),得到一个余式r(x)(次数小于 r)。x^r * m(x) = q(x) * g(x) + r(x) - 构造码字多项式
c(x) = x^r * m(x) + r(x)。因为r(x)是余数,所以c(x)必定能被g(x)整除(因为c(x) = q(x)*g(x) + r(x) + r(x) = q(x)*g(x),在 GF(2) 中r(x)+r(x)=0)。 - 此时,
c(x)的前 k 位系数对应m(x)(高位),后 r 位系数对应r(x),正是我们想要的系统码形式。
例子:同样对于 (7,4) 码,g(x)=x^3+x+1,r=3。信息m=1101,m(x)=x^3+x^2+1。
x^3 * m(x) = x^6 + x^5 + x^3。- 用
g(x)除x^6+x^5+x^3:x^6 / x^3 = x^3,计算x^3*g(x) = x^6 + x^4 + x^3,相减(异或)得余项x^5 + x^4。x^5 / x^3 = x^2,计算x^2*g(x) = x^5 + x^3 + x^2,相减得余项x^4 + x^3 + x^2。x^4 / x^3 = x,计算x*g(x) = x^4 + x^2 + x,相减得余项x^3 + x^2 + x。x^3 / x^3 = 1,计算1*g(x) = x^3 + x + 1,相减得余项x^2 + x + 1。 所以,余式r(x) = x^2 + x + 1,对应111。
- 码字多项式
c(x) = x^6+x^5+x^3 + (x^2+x+1) = x^6+x^5+x^3+x^2+x+1。 对应码字c = 1101 111。看,前4位1101就是原始信息!
电路实现(宝藏所在):系统码编码可以用一个简单的线性反馈移位寄存器实现。这个电路的核心就是一个根据g(x)系数连接的移位寄存器。对于g(x)=x^3+x+1(系数为1, 0, 1, 1,对应x^3, x^2, x^1, x^0),其编码电路如下图所示(文字描述):
- 一个3级移位寄存器(D触发器)
b0, b1, b2,初始为零。 - 反馈连接:根据
g(x)的系数(除最高次项),x^2系数为0,所以无连接;x^1系数为1,所以b2输出反馈到加法器;x^0系数为1,所以b0输出也反馈到加法器。 - 操作:前k个时钟周期,开关打到“信息输入”位置,信息位
m_k-1, ..., m_0一边输出为码字高位,一边送入LFSR计算余数。后r个时钟周期,开关打到“校验输出”位置,将移位寄存器中的余数(校验位)依次输出。 通过这个简单的电路,无需进行复杂的多项式除法运算,就能完成系统码编码。这是循环码在硬件上极具优势的体现。
4. 译码与纠错:伴随式解码的循环妙用
码发出去,经过有噪声的通道,接收端收到一个可能出错的向量r(对应多项式r(x))。译码器的任务就是判断是否有错,以及错了哪几位。
对于任何线性分组码,伴随式都是译码的核心。对于循环码,伴随式的计算和利用因其循环特性而变得更加高效。
伴随式 s(x) 的定义:用生成多项式g(x)除接收多项式r(x)所得的余式。r(x) = a(x) * g(x) + s(x),其中s(x)次数小于r。
- 如果
s(x) = 0,则认为r(x)是一个码字,传输无误(当然,也可能错成了另一个码字,这是不可检测的错误,但概率极低)。 - 如果
s(x) ≠ 0,则传输一定发生了错误。
假设错误图样为e(x)(错误的位置为1,正确为0),那么r(x) = c(x) + e(x)。因为c(x)能被g(x)整除,所以s(x)实际上等于e(x)除以g(x)的余式:s(x) = e(x) mod g(x)。
循环码译码的关键思路:相同的错误图样,即使循环移位后,计算出的伴随式也具有循环关系。这意味着,我们不需要为所有可能的错误位置单独计算和存储伴随式。我们只需要针对那些“最可能发生的错误图样”(比如重量较小的错误)建立一个伴随式-错误图样查询表。当收到一个伴随式s(x)后:
- 查表,如果找到对应的错误图样
e(x),则纠正:c_hat(x) = r(x) + e(x)。 - 如果没找到,则将伴随式循环移位(相当于将接收向量循环移位),同时更新伴随式(这也有对应的简单电路操作),再查表。重复这个过程最多
n次。
这个过程称为梅吉特译码,它极大地简化了译码器的结构。译码器主要包含三部分:一个用于计算伴随式的除法电路(和编码电路类似)、一个伴随式循环移位寄存器、一个只存储少数典型错误图样的查询表(ROM)。
举例说明:对于 (7,4) 循环汉明码,它能纠正1位错误。所有可能的单比特错误图样只有7种:0000001,0000010, ...,1000000。
- 译码器预先计算好这7种错误图样对应的伴随式
s(x),并存入表格。 - 接收端计算
s(x)。 - 如果
s(x)为0,判为无错。 - 如果
s(x非0,则在表格中查找。如果找到,直接纠正对应位。 - 如果没找到(说明可能是多位错误,超出了码的纠错能力),则进入循环移位流程:将接收向量循环左移一位,重新计算伴随式(这可以通过电路快速完成,无需重新做除法),再查表。最多移位7次。如果始终找不到匹配,则宣布为不可纠正错误。
这种方法将译码的复杂度从“应对2^n种可能接收向量”降低到“应对n种循环等价类”,对于硬件实现来说,节省了大量的存储和计算资源。
5. 从循环码到实用强码:BCH码与RS码
理解了循环码,你就拿到了学习两类极其重要的实用纠错码——BCH码和RS码的钥匙。它们都是循环码家族的扩展。
5.1 BCH码:纠多个随机错误的利器
BCH码是以其发明者 Bose, Chaudhuri, Hocquenghem 命名的。它是一种可以精确设计纠错能力的循环码。对于任意给定的正整数m和t,存在一个二元 BCH 码,其参数为:
- 码长
n = 2^m - 1 - 校验位数
n - k ≤ m * t - 最小距离
d_min ≥ 2t + 1 - 纠错能力:能纠正
t个或更少的随机错误。
它的强大之处在于:你可以直接指定“我要一个能纠t=3个错的码”,然后通过一套代数方法(涉及有限域GF(2^m)和极小多项式)构造出它的生成多项式g(x)。而传统的循环码设计往往是通过试凑x^n+1的因式,纠错能力不直观。
BCH码的生成多项式g(x)是由若干个极小多项式的乘积构成的。具体来说,若α是GF(2^m)的一个本原元,则一个纠t个错误的 BCH 码的生成多项式g(x),是以α, α^2, α^3, ..., α^(2t)为根的所有多项式中,系数在GF(2)上的最低次多项式。这个定义保证了码的最小距离至少为2t+1。
应用场景:BCH码广泛用于卫星通信、深空通信、无线通信(如DVB-S2标准)、NAND闪存(如SSD、U盘)的控制器中,用于纠正存储单元产生的随机比特错误。
5.2 RS码:纠突发错误的王者
RS码是里德-所罗门码的简称,它是 BCH 码的一个重要子类,但其符号定义在更大的有限域GF(2^m)上,而不仅仅是GF(2)。一个(n, k)RS 码有如下特点:
- 每个符号是
GF(2^m)中的一个元素,可以看作是一个m位的字节。 - 码长
n = 2^m - 1个符号。 - 信息段
k个符号。 - 最小距离
d_min = n - k + 1(这是一个最大值,称为 Singleton 界,RS码达到了这个界,所以是 MDS 码)。 - 纠错能力:能纠正最多
t = floor((n-k)/2)个符号错误。
RS码的核心理念是“符号纠错”。一个符号(m比特)无论里面错了1位还是全错了,都算一个符号错误。这使得RS码特别擅长纠正突发错误。因为一长串连续的比特错误,在字节(符号)视角下,可能只影响到少数几个连续的符号。
例子:一个GF(2^8)上的(255, 223)RS码,每个符号是一个字节(8比特),码长255字节,信息位223字节,校验位32字节。它可以纠正最多(255-223)/2 = 16个字节的错误。这意味着,即使信道中发生了长达16*8=128比特的连续突发错误,只要这些错误集中在不超过16个字节内,RS码就能完全纠正!这是二进制码难以做到的。
应用场景:RS码是数据存储和传输领域的“标配”。CD、DVD、蓝光光盘使用RS码来抵抗盘片划伤产生的长突发错误。早期的硬盘、现在的RAID 6系统、二维码(QR Code)、以及许多数据广播标准(如DVB-T)中,都使用了RS码。在通信中,它常作为外码,与作为内码的卷积码或LDPC码级联,构成强大的级联编码系统。
个人经验:在学习RS码时,一定要跳出“比特”的思维,建立起“符号”或“字节”的概念。它的编解码算法(如伯利坎普-梅西算法)虽然复杂,但核心思想仍然是基于伴随式和错误位置多项式,只是运算是在
GF(2^m)上进行的。很多开源库(如Python的reedsolo)提供了RS码的实现,动手调库跑几个例子,对理解符号运算帮助巨大。
6. 复习要点与常见误区梳理
最后,我们来梳理一下信息论中循环码部分的复习要点,并澄清几个常见的理解误区。
核心知识脉络:
- 定义与性质:循环移位封闭性 -> 用多项式表示码字 -> 生成多项式
g(x)的概念 ->g(x)整除x^n+1。 - 编码:
- 非系统编码:
c(x)=m(x)g(x)。 - 系统编码(重点):
c(x)=x^r m(x) + [x^r m(x) mod g(x)]。 - 电路实现:基于LFSR的除法电路,务必能画出给定
g(x)的编码电路图。
- 非系统编码:
- 译码:
- 伴随式
s(x) = r(x) mod g(x)。 - 伴随式与错误图样的关系:
s(x) = e(x) mod g(x)。 - 循环码的译码优势:伴随式的循环特性 -> 梅吉特译码 -> 简化译码器。
- 伴随式
- 进阶码型:
- BCH码:定义在
GF(2)上,可精确设计纠错能力t,生成多项式由α, α^2, ..., α^(2t)的极小多项式构成。 - RS码:定义在
GF(2^m)上,进行符号纠错,是MDS码,擅长纠突发错误。
- BCH码:定义在
常见误区与难点:
- 误区一:循环码一定是系统码。不对。循环码是一个码的集合,这个集合具有循环特性。我们可以用系统形式或非系统形式来生成这个集合中的码字。通常我们采用系统编码方法,但码本身的性质不依赖于编码方法。
- 误区二:生成多项式
g(x)的常数项必须为1。不一定。但通常我们选择的g(x)是不可约的或者是由不可约多项式乘积构成,这些多项式在二元域下的常数项通常为1(除了x+1这类因子)。如果常数项为0,意味着g(x)能被x整除,这通常不是我们想要的好码。 - 难点一:多项式运算。在
GF(2)上的多项式乘除法是基础,必须熟练。特别是除法,是编码和计算伴随式的核心。建议多手算几个例子,直到形成肌肉记忆。 - 难点二:伴随式译码流程。梅吉特译码的“循环移位查表”过程容易绕晕。关键要理解:电路上对接收向量的循环移位,等价于在数学上对伴随式进行一个固定的线性变换。这个变换可以通过一个简单的反馈电路实现,无需重新计算除法。
- 难点三:BCH/RS码的域转换。这是最大的跳跃。从比特到符号,从
GF(2)到GF(2^m)。理解GF(2^m)的构造(本原多项式)、元素表示(多项式、指数、二进制向量)以及其上的运算(加、乘、求逆)是看懂BCH/RS码教科书的前提。不要试图绕过,找一些带有具体小域(如GF(2^3))例子一步步推导的教程,会豁然开朗。
给实践者的建议:理论学习之外,强烈建议用代码(如Python)实现一个简单的 (7,4) 或 (15,11) 循环汉明码的编码和伴随式译码。自己写一下多项式除法、伴随式计算和查表纠错的流程,对理解整个闭环有质的帮助。再尝试调用一个RS码的库,对一个字节数组进行编码,然后故意篡改几个字节,再看能否成功译码恢复。这种实操带来的理解深度,是纯看书无法比拟的。
循环码的魅力在于,它用一个优雅的数学性质(循环性),催生出了一系列高效、实用的编解码算法与硬件结构。它像是一座桥,一头连着抽象的代数结构,另一头连着实实在在的通信芯片和存储控制器。把这部分啃下来,信息论这门课才算没白学。