C++ 数学与算法系列之认识格雷码
创始人
2024-03-25 09:26:45
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. 总结

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

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...