海明碼編碼和校驗原理與實現【轉載】

海明編碼與檢驗原由
以內存爲例, 如果內存所處的電磁環境比較複雜, 或者空間環境下受到帶電粒子的打擊, 那麼可能導致電容的充放電或者觸發器的翻轉(SRAM)。 這樣會導致存儲信息的改變。 如果不校驗, 存儲中存儲的程序可能不會發揮它應有的作用, 甚至會發生嚴重的後果。

編碼的最小距離
任意兩組合法代碼之間二進制位數的最小差異、編碼的糾錯、改錯能力與編碼的最小距離有關。 編碼的最小距離就是從一種合法代碼轉化成另外一種合法代碼所變化的最小位數。存在如下公式:
L1=C+D(DC)L-1=C+D(D\geq C)漢明碼是一種具有糾錯能力的編碼。其中:

L:L:編碼的最小距離
D:D:檢測錯誤的位數(信息位)
C:C:糾正錯誤的位數(校驗位或冗餘位)

冗餘位
冗餘位”是一種二進制位,它被用來添加到需要傳輸的數據信息中,以確保信息在傳輸過程中不會發生丟失或者改變。

對於“冗餘位”究竟需要多少位這個問題,我們有一個公式可以用來計算:
2rm+r+12^r\geq m+r+1其中rr是冗餘位,mm爲數據位。rr指的是冗餘位究竟需要多少位,而mm指的是傳輸的數據的二進制位數。

假設傳輸的數據的二進制位數是7位,那麼冗餘位的個數就可以通過上面的公式來計算:=247+4+1= 2^4 ≥ 7 + 4 + 1因此,我們的至少需要4個二進制位作爲“冗餘位”。

對比以上兩種概念,可看出冗餘位等價於糾正錯誤的位數

(注意:漢明碼默認一串數據只錯一位

奇偶校驗位
奇校驗: 在奇校驗檢測方式中,對於需要發送的數據信息比特,檢查其中1的個數。如果這串比特中1的個數是奇數,爲了保證加上“冗餘位“後,””整串數據中1的個數最後爲奇數,可想而知,冗餘位上應該設置爲“0”。如果在沒有添加“冗餘位”之前,數據比特流中的1的個數爲偶數,那麼爲了最後把1的個數湊成一個奇數,冗餘位上應該設置爲1。
偶校驗: 同理,在偶校驗檢測方式中,對於需要發送的數據信息比特,仍然檢查其中1的個數。如果這串比特中1的個數是奇數,爲了保證加上“冗餘位“後,””整串數據中1的個數最後爲偶數,可想而知,冗餘位上應該設置爲“1”。如果在沒有添加“冗餘位”之前,數據比特流中的1的個數爲偶數,那麼爲了最後把1的個數湊成一個偶數,冗餘位上應該設置爲0。例如(偶校驗):

如果整個代碼“1”的個數爲奇數, 我們就知道有一位數據發生了翻轉(因爲數據位翻轉的位數越多, 可能性越小。)如果我們將數據劃分成爲兩組,每組都加一個校驗位,那麼可以大大縮小確定發生翻轉的位數的範圍。這種劃分方式是沒有重疊的, 每個分組的數據連在一起就是原來的數據。

但海明碼並未使用上述分組原理。

海明碼分組原理
漢明碼採用的是非劃分方式。對於下面這個數據, 我們採用漢明碼的方式。

將數據分爲三組, 每組一個校驗位, 每組四位數據。 按照如下方式分組

(注意:上、左、右三個圓分別表示P1、P2、P3組) 把表示位置的這個數,轉化成二進制數。也就是,

第1個位置,變成第0001個位置;
第2個位置,變成第0010個位置;
第3個位置,變成第0011個位置;
第4個位置,變成第0100個位置;
第5個位置,變成第0101個位置;
第6個位置,變成第0110個位置;
第7個位置,變成第0111個位置;

那麼,規定來了,
凡是位置符合這種形式的,XXX1,歸到P1;
凡是位置符合這種形式的,XX1X,歸到P2;
凡是位置符合這種形式的,X1XX,歸到P3;
凡是位置符合這種形式的,1XXX,歸到P4;

則上圖中:
位置在1、3、5、7的數據進入組P1;
位置在2、3、6、7的數據進入組P2;
位置在4、5、6、7的數據進入組P3;
沒有數據進入組P4;

若某個組位置錯了將其置爲1,沒錯就是0,則有:

P3 P2 P1 錯位的位置號
0 0 0 無差錯
0 0 1 1
1 0 1 5
1 1 0 6
1 1 1 7

可以看出P3、P2、P1組成的二進制值就是發生錯誤的位置。記住規定,在採用漢明碼的一串數據中,2i2^i的位置上,我們放校驗碼。


海明碼編碼和校驗實例(以奇校驗爲例)
編碼:
假定數據爲:1011001。由於247+4+12^4 ≥ 7 + 4 + 1,因此需要4位校驗位或冗餘位。填充冗餘位後有:
在這裏插入圖片描述

  • 對於第一組來說(1,3,5,7,9,11位爲一組):1的個數爲4個,偶數個,因此①號應該爲1。這樣1的個數最後才能保證爲奇數。
    在這裏插入圖片描述
  • 同理,對於第二組來說(2,3,6,7,10,11位1組):1的個數爲3個,已經是奇數了,因此②號應該是0。在這裏插入圖片描述
  • 對於第三組來說(4,5,6,7位一組),1的個數爲1個,因此,③號應該是0。
    在這裏插入圖片描述
  • 對於第4組來說(8,9,10,11爲一組),1的個數爲2個,因此,④應該爲1。
    在這裏插入圖片描述
  • 最後,總的漢明碼就構造完畢了,如下所示:
    在這裏插入圖片描述
    校驗:
    上面,我們已經完成了“漢明碼”的編碼,那麼,漢明碼又是如何發現錯誤以及改正錯誤的呢?
    假設,第“5”號位上的“0”在傳輸過程中變成了“1”,接收方收到的數據則爲:10111010101。
    在這裏插入圖片描述
    漢明碼通過檢查每一小組的“奇校驗”,來確定是否發生了錯誤。
  • 首先第一組(1,3,5,7,9,11位):1的個數爲6位,不再是奇數個了,因此,我們可以斷定,這一組中肯定有某個數據發生了錯誤,但不能確定是哪一位上發生了錯誤。爲了達到“奇校驗”,我們必須補1個1來達到奇數個1。
  • 接下來,我們檢查第二組(2,3,6,7,10,11) ,1的個數爲3位,仍然滿足“奇校驗”,因此我們也可以斷定這一組中沒有任何一位數據發生了改變。所以,我們只需要補0。
  • 我們繼續檢查第三組(4,5,6,7),1的個數爲2個,不在滿足“奇校驗”,因此,我們可以斷定,這一組中也有數據發生改變。爲了達到“奇校驗”,我們必須補1個1來達到奇數個1。
  • 我們檢查第4組(8,9,10,11位),1的個數爲3位,滿足“奇校驗”,因此沒有發生改變。所以我們只需要補0。
    如下圖所示:
    在這裏插入圖片描述
    最後得出來的二進制數是:0101,我們會神奇地發現,0101就是10進制5的二進制表現,因此,我們可以準確的知道,5號位上發生了數據的改變,我們只要對5號位進行置反操作即可。最後,接收方就可以修改成爲正確的數據啦。

海明碼編碼和校驗實例(以偶校驗爲例)
編碼(通常漢明編碼是以偶校驗進行編碼的):
假定數據爲:10011010,由於248+4+12^4 ≥ 8 + 4 + 1,因此需要4位校驗位或冗餘位。填充冗餘位後有:
_ _1_001_1010
分組情況如下:
組數是這樣的:
P1: 1,3,5,7,9… 檢驗奇數位
P2: 2,3,6,7,10,11… 檢驗兩個,跳過兩個,檢驗兩個,跳過兩個
P3: 4,5,6,7,12,13,14,15… 檢驗4個,跳過4個
P4: 8,9,10,11,12,13,14,15… 檢驗8個,跳過8個
由於進行偶校驗,要求每個組中1的個數是偶數,因此有:

  • 第1組檢驗位爲 1,3,5,7,9,11:
    ? _ 1 _ 0 0 1 _ 1 0 1 0. 看看1,3,5,7,9,11位,發現正好是偶數個1,所以校驗位1設爲0就行了:
    0 _ 1 _ 0 0 1 _ 1 0 1 0
  • 第2組檢驗位爲 2,3,6,7,10,11:
    0 ? 1 _ 0 0 1 _ 1 0 1 0. 發現是奇數個1,於是設置校驗位2爲1,這樣算上校驗位的1,恰好是偶數個0 1 1 _ 0 0 1 _ 1 0 1 0
  • 第3組檢驗位爲 4,5,6,7,12:
    0 1 1 ? 0 0 1 _ 1 0 1 0. 發現是奇數個1,於是設置校驗位4爲 0 1 1 1 0 0 1 _ 1 0 1 0
  • 第4組檢驗位爲 8,9,10,11,12:
    0 1 1 1 0 0 1 ? 1 0 1 0. 發現是偶數個1,於是設置校驗位8爲 0 1 1 1 0 0 1 0 1 0 1 0
  • 最後配置好的漢明碼: 011100101010.

校驗:
如我們傳送的011100101010,但接收到的是011100101110 (第10位出現錯誤)。

出錯位可以這樣確定:
對接受到的漢明碼011100101110 進行每一個校驗碼的驗算,即它所在的組(包括它,與之前不同的是,之前校驗碼是待定的,現在已經有值了)的1的個數應該是偶數個,這就暗示我們將要覆蓋這個值(實際上並不是,僅僅是驗算)的新校驗碼應該是0纔對,但是由於接收到的有錯誤,我們在驗算中發現有的校驗位要填1。

這裏,驗算後發現第2個和第8個校驗位要填1,那麼錯誤的數據位就是2+8=10。


海明碼編碼和校驗實現(C++\C)

#include <stdio.h>
#include <stdlib.h>//調用system函數
#include <malloc.h>
#include <math.h>
#include <dos.h>//注意pow(double,int)而不是pow(int,int)
#define M 100 //最大的數據位數
#define N 50//最大的校驗位數

struct Hamming{
	int flag;
	int value;//海明碼的數值
}H[M];//存放海明碼信息

int P[N];//存放校驗位信息
int C[N];//存放檢驗位信息
int m[M + N];//存放需要糾正的海明碼
int DC = 0, PC = 0, EC = 0, HC = 0, ERROR = 0, ERROR1 = 0;//DC是數據的位數,PC是校驗位的位數,不能在此更改這些數據的值,否則出錯

void showInf(){//顯示信息
	printf("-------------------------------------------------------------------------------\n");
	printf("*****************************************海明碼********************************\n");
	printf("***************************************輸入格式如下:***************************\n");
	printf("*****************************如果要輸入1101,需輸入 1 1 0 1 -1******************\n");
	printf("**************輸入數據 1 1 0 1 -1的過程中,不能回退刪除,否則會發生錯誤*********\n");
	printf("-----------------------------選擇操作:----------------------------------------\n");
	printf("1 輸入數據後生成海明碼 2 輸入海明碼  3 清屏  4 糾正海明碼 5 返回主頁 6 結束程序\n\n");
	printf("請選擇操作,只能輸入一個數: ");
}

int inputData(int choice){//輸入數據或海明碼,參數是1就表示輸入的是數據,參數是2就表示輸入的是海明碼。返回-1就表示輸入數據錯誤。
	int data, i = 1;
	scanf("%d", &data);
	if (data != 0 && data != 1){
		system("cls");
		printf("\n\n提示信息:輸入格式不正確,第一位只能是0或1,請重新選擇操作\n\n\n");
		return -1;
	}
	DC = 1;
	PC = 1;
	while (data != -1){
		if (pow(2.0, PC - 1) != i){//存放數據值到海明碼結構體中
			H[i].flag = 0;//數據位,位置標記爲0
			H[i].value = data;
			m[i] = data;
			DC++;
			scanf("%d", &data);
		}
		else if (pow(2.0, PC - 1) == i&&choice == 1){//設置校驗位
			H[i].flag = 1;//校驗位,位置標記爲1
			PC++;
		}
		if ((pow(2.0, PC - 1) == i) && choice == 2){//把海明碼中的校驗位放到校驗位數組中,以便用這個校驗位和利用數據生成的校驗位異或來計算哪一位出錯
			H[i].flag = 1;
			P[PC++] = data;
			m[i] = data;
			scanf("%d", &data);
		}
		i++;
	}
	//累計次數是多加了一次,減回來。
	DC--;
	PC--;
	if (PC + DC < 3)
		ERROR = 2;
	return 0;
}

void caculateHammingCode(){//根據海明碼結構體中數據位的數據值計算海明碼,並把正確的海明碼放到海明碼結構體中。
	int m, j, k, temp = 0;
	for (m = 3; m <= DC + PC; m++){
		temp = 0;
		if (H[m].flag == 0){//數據位
			for (j = PC; j >= 1; j--){
				k = (int)pow(2.0, j - 1);
				if (k < m){
					H[k].value = H[k].value^H[m].value;
					temp = k;
					break;
				}
			}
			for (k = j - 1; k >= 1; k--){
				if (H[k].flag == 1){//校驗位
					if (temp + pow(2.0, k - 1) == m){
						H[k].value = H[k].value^H[m].value;
						break;
					}
					if (temp + pow(2.0, k - 1) < m){
						H[k].value = H[k].value^H[m].value;
						temp = temp + pow(2.0, k - 1);
					}
				}
			}
		}
	}
}

void caculateC(int pc){//計算檢驗位的值,並放到檢驗位的數組中。接受的參數是校驗位的個數
	int i, k;
	for (i = 1; i <= pc; i++){
		k = (int)pow(2.0, i - 1);
		C[i] = P[i] ^ H[k].value;
	}
}

void initial(){//使海明碼結構體清零
	int i;
	for (i = 1; i <= DC + PC; i++){
		H[i].value = 0;
	}
}

void showHammingCode(int dc, int pc){//打印海明碼結構體中的值。接受的參數是dc爲數據位的個數,pc爲校驗位的個數。
	int i;
	printf("\n\n生成的海明碼爲:\n");
	for (i = 1; i <= dc + pc; i++){
		printf("第%d位:", i);
		printf("  %d\n", H[i].value);
	}
	printf("\n");
}
void judge(int pc){//判斷出哪一位出錯,並打印出這一位是數據位還是校驗位
	int i, result = 0, flag = 0;
	//system("cls");
	for (i = 1; i <= pc; i++){
		if (C[i] == 1) flag++;

	}
	for (i = 1; i <= pc; i++){
		if (flag == 0)
			result = result + C[i] * pow(2.0, pc - i);
		else result = result + C[i] * pow(2.0, i - 1);
	}
	if (result != 0){
		printf("\n\n\n提示信息:第%d位出錯,", result);
		EC = result;
		if (flag >= 2)
			printf(" 這一位是數據位!\n\n");
		else printf(" 這一位是校驗位!\n\n");
	}
	else printf("\n\n提示信息:海明碼正確!\n\n\n");
}
void mend(int mc){//糾正錯誤的海明碼。並打印出糾正前錯誤的海明碼和糾正後正確的海明碼
	int i, k = 0, p = 1;
	if (EC == 0){
		printf("\n\n提示信息:海明碼正確或者已經糾正或者海明碼爲空,請重新選擇操作\n\n\n");
		return;
	}
	system("cls");
	printf("\n提示信息:海明碼第%d位有錯誤\n\n", EC);
	printf("\n輸入的錯誤的海明碼是:\n\n");
	for (i = 1; i <= mc; i++)
		printf("第%d位%d\n", i, m[i]);
	m[EC] = !m[EC];
	printf("\n\n糾正後的正確的海明碼是:\n\n");
	for (i = 1; i <= mc; i++)
		printf("第%d位%d\n", i, m[i]);
}

void run(int command){//運行不同的操作模式,command爲1表示輸入的數據位,command爲2表示輸入的是海明碼
	int i;
	if (command == 1){//輸入的是數據
		printf("\n\n請輸入數據,以-1做結束符:");
		i = inputData(1);//返回輸入的數據位數
		if (i == -1) return;
		caculateHammingCode();
		showHammingCode(DC, PC);
		initial();
		HC = PC + DC;
		DC = 0;
		PC = 0;
	}
	else {//輸入的是海明碼
		printf("\n\n請輸入海明碼,以-1做結束符,只能有一位錯誤:");
		i = inputData(2);
		if (i == -1) return;
		caculateHammingCode();
		if (ERROR == 1 || ERROR == 2) {
			system("cls");
			printf("\n\n提示信息:輸入格式不正確,第一位只能是0或1,並且輸入的位數要大於等於3.請重選操作\n\n\n");
			ERROR = 0;
			PC = 0;
			return;
		}
		caculateC(PC);
		judge(PC);
		initial();
		HC = PC + DC;
		PC = 0;
		DC = 0;
	}
}
void main(){
	int function, flag = 1, command, md = 0, back = 0;
	showInf();
	scanf("%d", &function);
	while (flag){
		switch (function){
		case 1: command = 1;
			run(command);//輸入數據
			md = 0;
			break;
		case 2: command = 2;//輸入海明碼
			run(command);
			md = 1;
			break;
		case 3: system("cls");//清屏
			break;
		case 4:system("cls");
			if (md == 1)//糾正海明碼
				mend(HC);
			else printf("\n\n提示信息:您還沒有輸入海明碼或者輸入的海明碼已經被糾正了.\n\n\n");
			md = 0;
			EC = 0;
			break;
		case 5: system("cls");//返回主頁
			back = 1;
			break;
		case 6:exit(1);//退出
		default: system("cls");
			printf("\n\n\n提示信息:操作不正確,請重新選擇!\n\n");
			break;
		}
		if (back == 1){
			showInf();
			back = 0;
		}
		else {
			printf("\n1 輸入數據後生成海明碼 2 輸入海明碼  3 清屏  4 糾正海明碼 5 返回主頁 6 結束程序\n\n");
			printf("請選擇操作,只能輸入一個數: ");
		}
		scanf("%d", &function);
	}
}

測試:
以數據10011010進行測試,結果如下:
在這裏插入圖片描述
海明編碼結果爲:011100101010
如我們傳送的011100101010,但接收到的是011100101110 (第10位出現錯誤)。校驗如下:
在這裏插入圖片描述
糾正結果如下:
在這裏插入圖片描述
接收到的是011100101110 (第10位出現錯誤),但實際傳送的011100101010


以上博文整理自:

  1. williamgavin_存儲器(四)-- 漢明碼
  2. 劉揚俊_漢明碼 – 計算機網絡 – 全網最通俗的講解
  3. Vic_Chen_is_here_存儲器的校驗–漢明碼(Hanming Code)
  4. flysky2011_海明碼的改進算法,請多多指教!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章