• C++ 数学与算法系列之认识格雷码


    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);
    	//对低位格雷码正向遍历,添加前缀 0
    	vector::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 位格雷码:"<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    输出结果:

    4 位格雷码:
    0000
    0001
    0011
    0010
    0110
    0111
    0101
    0100
    1100
    1101
    1111
    1110
    1010
    1011
    1001
    1000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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;
        //初始二进制的每一位为 0
    	for(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 位格雷码:"<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    输出结果:

    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. 总结

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

  • 相关阅读:
    使用JAVA-api读取HDFS下文件内容,出现报错信息
    java98-线程join使用中断进行另一个
    升级Spring Cloud最新版后,有个重要的组件被弃用了。。。
    springboot导入excel(POI)
    这应该是关于回归模型最全的总结了(附原理+代码)
    计算机视觉算法——基于Transformer的语义分割(SETR / Segmenter / SegFormer)
    第二次上机作业 大连理工大学
    分布式锁之防止超卖 --mysql原子操作,乐观锁,redis事务,乐观锁
    [centos]centos镜像ISO下载地址
    【Filter 过滤器、Listener 监听器基础】
  • 原文地址:https://blog.csdn.net/y6123236/article/details/128196732