里德(Reed)-所罗门(Solomon)纠删码
纠删码是一种差错控制方式,常用在通信和存储中。
应用在存储中时,它和多副本相比,能用更少的空间实现与多副本相当水平的可靠性。
经典的纠删码两个参数, 和 , 表示把数据分成多少部分, 表示这 部分数据有 个校验信息。令 ,则 份数据占据 份空间,需要的额外空间比为 ,而多副本——例如 副本——需要的额外空间比为 。
纠删码的用途和优劣势
优势:省空间
在实际生产中,多副本的 一般取 3,需要额外 2 倍空间,纠删码 一般取 之类的值,需要额外 0.5 倍空间。
劣势:性能差,计算负担重
所以纠删码适用的场景是,读很少,写极少。典型的例子是陈年卷宗。
环上的代数与纠删码原理
原理
设 是需要保存的数据,考虑如下矩阵
则
显然,,而 ,可以证明 去掉任何两行后,都会得到一个满秩矩阵 ,在上式右边去掉行号相同的对应两行,得到
那么
于是,我们在丢失了两个数据后,通过代数运算,还原了原始数据。这就是 的里德所罗门编码的基本原理。它可以容忍任意 个数据丢失。
但是,上述计算直接应用在计算机上,则完全行不通,因为计算机只能处理有限数,不能处理实数。所以,必须通过某种转换,使得计算机也能处理这样的计算。
有限域
不难发现,上面的计算只用到了加减乘除(除数不能是零),矩阵的求逆也是有限次加减乘除的复合,没有用到实数的连续性等其他性质。所以我们可以(也必须)用一个对计算机友好的、支持加减乘除的代数结构替换上面的实数集 。
域,Field,指的是一个非空集合 ,上面有加法和乘法两个运算,且:
- 加法使 成为交换群 ,其单位元记为
- 乘法满足交换律,且使 成为交换群 ,其单位元记为
- 满足乘法对加法的分配律,
如果 是有限集,那么就称这个域是有限域,也叫伽罗瓦域。
可以证明,有限域的元素个数必须是 ,其中 是素数, 是正整数。
一个字节有 256 个不同的值,即 0 到 255,而幸运的是, 256 可以作为有限域的元素个数,因为它是 。所以我们的目标是,构造一个元素个数为 256 的域 ,它和
之间建立起一一对应
于是 中的四则运算可以定义为
有限域的构造
构造元素个数为 256 的域需要一些小波折。但好在,构造元素个数为素数的域很简单。
设 为素数。考虑整数的模 同余类 ,加法就是相加后取模 同余,乘法就是相乘后取模 同余,相反数定义为如果 整除 则称 和 互为相反数, 的倒数定义为使 的那个 ,有了相反数和倒数就可以定义减法和除法。根据初等数论知识,这些定义都是良定义的,并且满足域的条件,它是有限域 。
特别地,,那么 ,加减法都是亦或,乘法是只有 其他乘积都等于零。这是有 2 个元素的有限域 。
正如我们用整数环和素数 构造有限域 那样,可以考虑系数在 中的多项式组成的多项式环 以及该环上的本原多项式构造有限域。
考察多项式
它在 中是本原多项式。在 中取模 同余,得到由次数小于 8 的多项式环
它是 上的 8 维线性空间,里面正好有 个向量。
中的加减法按照多项式自然的加减法进行,加法单位元为零多项式 0,乘法按照多项式相乘模 ,除零多项式外,乘法都有逆元,乘法的单位元是零次多项式 1,于是,它满足域的所有条件。
终于,我们得到了一个有 256 个元素的域!
代码实现
在开始代码之前,我们还是要继续啰嗦几句代数。
一个有限域 ,它的乘法群 必定是循环群,也就是群 的所有元素都可以由某个不是单位元的元素不断乘以自己得到。具体到 ,它里面的 1 次多项式 可以生成它里面除零多项式之外的所有多项式
而 的阶 ,这就为我们编码实现提供了便利。
给定多项式 和 ,计算它们的乘积可以直接把指数加起来 。而在代码中,我们可以直接构造两个表:
- 指数表:给定 ,得到 展开后的样子,byte 表示
- 对数表:给定 byte,它对应的多项式是 的多少次幂
有了这两个表,我们计算乘除法才好算。
constexpr int FIELD_SIZE = 256;
constexpr uint16 PRIMITIVE_POLYNOMIAL = 0b100011101; // x^8 + x^4 + x^3 + x^2 + 1
uint8 gf_power[FIELD_SIZE - 1];
uint8 gf_log[FIELD_SIZE];
// 计算指数表和对数表
// 在实际生产中,提前在别的地方算好放入 constexpr 数组,这里为了演示无所谓了
void init()
{
constexpr uint16 MASK = 0xFF00;
gf_power[0] = uint8(1);
gf_power[1] = uint8(1) << 1;
for (int i = 2; i < FIELD_SIZE - 1; ++i)
{
uint16 temp = gf_power[i - 1] << 1;
if ((temp & MASK) != 0)
{
// 越界了,取模
temp ^= PRIMITIVE_POLYNOMIAL;
}
gf_power[i] = static_cast<uint8>(temp);
}
gf_log[0] = 0; // 注意,0 没有对数
for (int i = 0; i < FIELD_SIZE - 1; ++i)
{
gf_log[gf_power[i]] = i;
}
}
除此之外,还要补充矩阵相关的实现,例如逆运算、行列式、余子阵、余子式、代数余子式之类的。
完整代码参考我的仓库:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)