+++
author = "FlintyLemming"
title = "海明校验码"
slug = "126cc71ce5234e699042036798edcf66"
date = "2020-06-01"
description = ""
categories = ["Coding"]
+++

在了解海明校验码之前，需要知道一个重要的概念——码距。

## 概念介绍-码距
码距是指是两个码字中不相同的二进制位的个数，举个例子，有两个编码，分别是01101101和01000101，可以发现，这两个编码有两位值不相同，所以他们的码距为2。
那么，你还有可能听到这么一个说法：“8421码码距d=1”，这里的码距指的是“该数据编码的最小码距”，是整个编码系统中中任意两个码字的码距的最小值。还有一个问题，使用四位二进制的编码系统，码距一定是1吗？显然不是，虽然用的是四位二进制编码系统，但是没有限定编写什么码字。如果我用这个编码系统只编写0000、0011、0101这三个码字，那么这个编码系统的码距就是2，因为这三个码子互相之间的码距最小值为2。

## 码字的结构
接下来，我还要介绍一下添加校验码后，整个码字的结构。码字包括信息位和校验位，这里设信息位的位数为k，校验位的位数为r，那么整个码字的长度就是k+r。

## 所需校验位的计算
对于校验位来说，需要有足够的位数来表示信息位中发生的错误，而海明校验码只表示信息位中一位的错误。
举个例子（并不是海明码的结构，不严谨，仅作理解），现在有信息位0110，那么，校验位就需要有四个状态分别表示信息位的第一、二、三或者第四位有误。那么校验位如果只有一位，0和1，并不能表示四种状态。我们试试两位：比如校验位我用00表示信息位第一位发生错误，01表示第二位发生错误，用10表示第三位发生错误，11表示第四位发生错误。看似没有问题，但是忘了一点：校验位如何表示信息位没有错误呢？所以其实我们需要4+1=5种情况来表示信息位的校验信息，即需要3位的校验位。
而对于海明码来说，数据位和校验位穿插，存在校验位可以“我 查 我 自 己”的情况，需要检查的位数就是k+r。所以对于k位的信息位，r位的校验位，需要满足2^r-1>k+r才能检查出1个位的错误。

## 确定校验码和数据的位置
上节提到，海明码的校验位和信息位是穿插的，所以我们需要确定校验位的各个位置。为了方便说明，我们给校验码定义为P1、P2、P3…；数据为定义为D1、D2、D3…；最终整个码字的编码依次为M1、M2、M3……那对于校验码Pi来说，它的位置是M[2^(i-1)]，比如P1的位置是M1、P2的位置是M2，P3的位置是M4，P4的位置是M8……那么剩下的就是数据的位置
M1    M2    M3    M4    M5    M6    M7    M8    M9 
P1    P2     D1    P3     D2    D3     D4    P4     D5

## 求出校验位的值（规律法）
这里以一个例子讲解，比如我们现在掌握如下信息

M1    M2    M3    M4    M5    M6    M7    M8    M9    M10

P1    P2     D1    P3     D2    D3      D4    P4    D5     D6

(空)    (空)    1    (空)    0        1         1    (空)    0        1

表里可以看出已经给定了一组信息位的值，现在，我们先计算P1的值：
我们从**P1开始，连续1个，中间间隔1个，再连续1个**，组合成一个串：
【为了方便理解，我详细说一下上方加粗的过程：从P1（也就是对应的M1）开始，连续一个，就还是P1；然后间隔一个，就跳过M2；再连续一个，取M3的值；然后间隔一个，也就是跳过M4；再连续一个，取M5的值…以此类推，我们取了P1、3、5、7…的值，把它放在一个串里】
然后对这个串进行偶校验，也就是说，保证这个串中1的个数为偶数个，可以看到，已经有两个1了，所以我们在第一位补0：
这也就是我们得到的P1的值

为了加强理解，我们再计算一下P2的值：
我们**从P2开始，连续2个，中间间隔2个，再连续2个**，组合成一个串：
 
【为了方便理解，我再详细说一下加粗部分的过程：从P2（也就是对应的M2）开始，连续两个，就是M2、M3，取M3的值；然后间隔2个，跳过M4、M5；再连续两个，取M6、M7的值；再间隔两个，跳过M8、M9；再连续两个，取M10、M11…以此类推，我们取了M3、6、7、10的值】
然后同样进行偶校验，发现已经有4个1，所以补0：
这也就是我们得到的P2的值
 
同理，计算P3的过程中，我们**从P3开始，连续3个，中间间隔3个，再连续3个**，也就是取M4、5、6；跳过M7、8、9；取M10、11、12
然后进行偶校验，补0：

P4同理，最后补的是1：
这样，我们就依次获得了校验位的数据，P1-4依次为0、0、0、1

## 求出校验位的值（数学法）
还是用这一题：
第一步，我们要列出整个码字的编码对应的二进制。比如M1，1对应的就是0001；M2，就是0010；M3就是0011
观察这个二进制编码，以M3为例，0011，第一位和第二位都是1，那么我们称M3和P1、P2有关
第二步，我们要找到还有哪些和P1有关，可以发现，M1、3、5、7、9都和P1有关
第三步，以此类推，找出哪些和P2有关（可以找到M2、3、6、7、10），哪些和P3有关……
第四步，去掉第一位（因为比如对于P1来说，M1、3、5、7、9中，M1并没有值），列出如下方程式：
P1 = M3 ⊕ M5 ⊕ M7 ⊕ M9 = 1 ⊕ 0 ⊕ 1 ⊕ 0 = 0
P2 = M3 ⊕ M6 ⊕ M7 ⊕ M10 = 1 ⊕ 1 ⊕ 1 ⊕ 1 = 0
P3 = M5 ⊕ M6 ⊕ M7 = 0 ⊕ 1 ⊕ 1 ⊕ = 0
P4 = M9 ⊕ M10 = 0 ⊕ 1 = 1
便可以算出校验位的值

## 接收端校验
参考上一节求出校验位的值（数学法）中求P的过程，我们需要把P，和其相关的数据位相加，比如上面我们计算了P1 = M3 ⊕ M5 ⊕ M7 ⊕ M9 = 1 ⊕ 0 ⊕ 1 ⊕ 0 = 0，这里我们就校验P1 ⊕ M3 ⊕ M5 ⊕ M7 ⊕ M9 是否等于 0，接着，依次计算P2 ⊕ M3 ⊕ M6 ⊕ M7 ⊕ M10、P3 ⊕ M5 ⊕ M6 ⊕ M7、P4 ⊕ M9 ⊕ M10是否都等于0，如果都是0，说明这个数据没有问题。
如果出问题了呢？下面举个例子：
还是上面那一题，如果M5在传输中发生错误，0变成了1
那么计算中就会发现：
P1 ⊕ M3 ⊕ M5 ⊕ M7 ⊕ M9 = 1
P2 ⊕ M3 ⊕ M6 ⊕ M7 ⊕ M10 = 0
P3 ⊕ M5 ⊕ M6 ⊕ M7 = 1
P4 ⊕ M9 ⊕ M10 = 0
得出出错位为：0101(2)位，即第五位，那么，只要把M5的值反转，即可得到正确的字串。

## 海明码的码距
回到一开始说的码距问题，有题目会问，海明码的码距是多少？答案是3。为什么呢，其实我们不妨再观察一下用数学法计算校验位的过程：
P1 = M3 ⊕ M5 ⊕ M7 ⊕ M9 = 1 ⊕ 0 ⊕ 1 ⊕ 0 = 0
P2 = M3 ⊕ M6 ⊕ M7 ⊕ M10 = 1 ⊕ 1 ⊕ 1 ⊕ 1 = 0
P3 = M5 ⊕ M6 ⊕ M7 = 0 ⊕ 1 ⊕ 1 ⊕ = 0
P4 = M9 ⊕ M10 = 0 ⊕ 1 = 1
不难发现，即使是靠前的数据位，比如M3、M5，都至少与两个校验位有关，所以变了一个数据位，会更改两个校验位，一共改变三个值。即可得出海明码的码距为3。