C++ 数学与算法系列之认识格雷码
创始人
2024-03-25 02:36:36
0

1. 前言

程序中所涉及到的任何数据,计算机底层均需转换成二进制数值后方可存储,这个过程也称为编码。反之,把底层二进制数据转换成应用数据称为解码,

不同的数据类型需要不同的编(解)码方案,如音频数据编码、视频数据编码、图形数据编码……

即使是同类型的数据,根据不同的应用场景,也有多种编码方案可选。如字符编译就有ASCII、UTF-8、哈夫曼编码以及本文将要讲解的格雷码

讲解格雷码之前,首先了解一下格雷码的定义:

  • 对数据编码后,若任意两个相邻的码值间只有一位二进制数不同,则称这种编码为格雷码(Gray Code)
  • 由于最大数与最小数之间也仅只有一位数不同,即首尾相连,又称循环码反射码

格雷码的优点:

一种编码的出现,一定是为了弥补已有编码的不足,这里以ASCII编码中的A~Z字符的码值开始研究:

二进制十进制十六进制字符
010000016541A
010000106642B
010000116743C
010001006844D
010001016945E
010001107046F
010001117147G
010010007248H
010010017349I
01001010744AJ
01001011754BK
01001100764CL
01001110784EN
01001111794FO
010100008050P

G的编码是01000111H的编码是01001000

从宏观的计数角度而言,计数增长仅为1,但是有 4 个数据位发生了变化。从底层的存储硬件而言,每一位都需由电路控制 ,宏观世界里4 位数字的变化会引起微观世界里多个电路门变化,且不可能同时发生。

意味着中间会短暂出现其它代码,则在电流不稳或特定因素的影响下可能会导致电路状态变化错误的概率会增加很多。

而格雷码相邻编码只有一个数据位的变化,相对于计数编码,显然易见,其安全性和容错性要高很多。

格雷码可以有多种编码形式。如下图所示:

十进制数4位自然二进制码4位典型格雷码十进制余三格雷码十进制空六格雷码十进制跳六格雷码步进码
00000000000100000000000000
10001000101100001000100001
20010001101110011001100011
30011001001010010001000111
40100011001000110011001111
50101011111001110011111111
60110010111011010010111110
70111010011111011010011100
81000110011101001110011000
91001110110101000100010000
1010101111----------------
1110111110----------------
1211001010----------------
1311011011----------------
1411101001----------------
1511111000----------------

表中典型格雷码具有代表性,一般说格雷码就是指典型格雷码,它可从自然二进制码转换而来。

Tips: 格雷码是一种变权码,每一位码没有固定的大小,很难直接进行比较大小和算术运算。

2. 编码方案

2.1 递归实现

这种方法基于格雷码是反射码的事实,可以对直接使用递归算法构造。

流程如下:

  • 1位格雷码有两个编码。

4.png

  • (n+1)位格雷码中的前2^n个编码等于n位正序格雷码的前面 加0

5.png

  • (n+1)位格雷码中的后2^n个编码等于n位逆序格雷码的前面加1

6.png

2位格雷码3位格雷码4位格雷码4位自然二进制码
0000000000000
0100100010001
1101100110010
1001000100011
11001100100
11101110101
10101010110
10001000111
11001000
11011001
11111010
11101011
10101100
10111101
10011110
10001111

编码实现:

#include 
#include 
using namespace std;
/*
*实现格雷编码
*/
vector grayCode(int num) {//存储格雷码vector vec;if(num==1) {//出口:1位格雷码是已知的vec.push_back("0");vec.push_back("1");return vec;}//得到低位格雷码vector vec_= grayCode(num-1);//对低位格雷码正向遍历,添加前缀 0vector::iterator begin=vec_.begin();vector::iterator end=vec_.end();for(; begin!=end; begin++) {vec.push_back("0"+*begin);}//对低位格雷码反向遍历,添加前缀 1 vector::reverse_iterator rbegin=vec_.rbegin();vector::reverse_iterator rend=vec_.rend();for(; rbegin!=rend; rbegin++) {vec.push_back("1"+*rbegin);}return vec;
}
//测试
int main(int argc, char** argv) {vector vec=grayCode(4);cout<<"4 位格雷码:"<

输出结果:

4 位格雷码:
0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000

2.2异或转换

异或转换可以直接把n位二进制数字编码成对应的n位格雷码。当然,也可以把格雷码直接转换成对应的二进制。

编码流程如下:

  • n位二进制的数字,从右到左,以0n-1编号。

8.png

  • 如果二进制码字的第i位和i+1位相同,则对应的格雷码的第i位为0(异或操作),否则为1(当i+1=n时,二进制码字的第n位被认为是0,即第n-1位不变)。如下图,二进制 0101经过转换后的格雷码为0111
    9.png

编码表示

#include 
#include 
using namespace std;
/*
*异或转换格雷编码
*/
void  yhGrayCode(int num) {//二进制vector vec;//格雷码vector  gc;//存储进位值int jinWei=0;//初始二进制的每一位为 0for(int i=0; i=0; i--) {vec[i]	= vec[i]+jinWei;jinWei= vec[i] / 2;vec[i]=vec[i] % 2;}}
}
//仅测试 4 位格雷码
int main(int argc, char** argv) {cout<<"\ 4 位格雷码:"<

输出结果:

7.png

解码流程: 解码指把格雷码转换成二进制码。解码的基本思想基于异或运算的加密、解密特性,如果 AB异或得到C。则CB 异或得到ACA 异或得到B

  • 格雷码最左边一位保持不变。

10.png

  • 从左边第二位起,将每位与左边解码后的值异或,结果作为该位解码后的值。

11.png

  • 依次异或,直到最低位。依次异或转换后的值就是格雷码转换后的自然二进制。

12.png

13.png

**编码实现:**如下代码仅是对解码的基础逻辑的实现。不具有通用性,可以重构此逻辑,让其更有普遍性。

#include 
#include 
using namespace std;
/*
* 4 位格雷码转二进制
*/
int main(int argc, char** argv) {//格雷码int grayCode[4]= {0,1,1,1};//二进制int binary[4]= {0};//格雷码最左一位自动解码binary[0]=grayCode[0];//格雷码从左边第二位开始解码for(int i=1; i<4; i++) {if( grayCode[i]==binary[i-1] ) {binary[i]=0;} else {binary[i]=1;}}//输出二进制for(int i=0; i<4; i++) {cout<

2.3 卡诺图实现

什么是卡诺图?

卡诺图是逻辑函数的一种图形表示。卡诺图是一种平面方格图,每个小方格代表逻辑函数的一个最小项,故又称为最小项方格图。方格图中相邻两个方格的两组变量取值相比,只有一个变量的取值发生变化,按照这一原则得出的方格图(全部方格构成正方形或长方形)就称为卡诺方格图,简称卡诺图。

利用卡诺图生成格雷码的流程如下:

  • 使用卡诺图编码格雷码,总是由低阶生成高阶。可以绘制如下的表格,行号和列号均以低阶格雷码作为标题。

14.png

  • 从卡诺图的左上角以字形到右上角最后到左下角遍历卡诺图,依次经过格子的变量取值即为典型格雷码的顺序。

15.png

编码实现: 如上图所示,4 位格雷码可由 3 位和 1 位、也可以由 2 位和 2 位的格雷码构建成的卡诺图生成,为了让构建过程具有通用性,基础逻辑:n 位的格雷码都可以能过n-1阶和 1阶格雷码构建的卡诺图生成。如此便可以使用递归算法。

#include 
#include 
using namespace std;
/*
* 卡诺图编码 
* num 表示二进制位数
*/ 
string* krtGrayCode(int num) {string* gc;//一阶格雷码if(num==1) {gc=new string[2] {"0","1"};return gc;}//格雷码个数与二进制位数关系int res=pow(2,num);//存储格雷码的数组gc=new string[res];//得到低阶格雷码string* dgc= krtGrayCode(num-1);//一阶格雷码string* oneGc=krtGrayCode(1);//奇偶标志int idx=1;//低阶格雷码的个数int count=pow(2,num-1);int gjIdx=0;//以行优先扫描低阶格雷码for(int i=0; i=0; j--) {gc[gjIdx++]=dgc[i]+oneGc[j];}}idx++;}return gc;
}
//测试
int main(int argc, char** argv) {int num=4;int count=pow(2,num);string* gc= krtGrayCode(num);for(int i=0; i

输出结果:

0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000

3. 总结

本文讲解了格雷码的概念以及具体的编码实现方案。

相关内容

热门资讯

修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
【前端】‘??‘与‘||‘有什... 0 问题 经常写const data = res.data.a ?? ''或者const d...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
牛客计算器的改良(Python... 文章目录1.题目描述2.输入描述:3.输出描述:4.示例15.分析6.代码7.结语 链接࿱...
正大杯|市调大赛|2023备赛... 关键信息 同时随着精细化养宠趋势的深入,宠物消费类目日渐丰富。 本报告通过 Niuco...
QGIS下载在线地图(Goog... 前言 国内外有很多在线地图下载软件,但功能单一,基本上只能下载数据&#x...