前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++ 数学与算法系列之认识格雷码

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

作者头像
一枚大果壳
发布2022-12-20 20:16:38
6630
发布2022-12-20 20:16:38
举报
文章被收录于专栏:编程驿站编程驿站

1. 前言

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

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

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

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

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

格雷码的优点:

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

二进制

十进制

十六进制

字符

01000001

65

41

A

01000010

66

42

B

01000011

67

43

C

01000100

68

44

D

01000101

69

45

E

01000110

70

46

F

01000111

71

47

G

01001000

72

48

H

01001001

73

49

I

01001010

74

4A

J

01001011

75

4B

K

01001100

76

4C

L

01001110

78

4E

N

01001111

79

4F

O

01010000

80

50

P

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

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

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

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

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

十进制数

4位自然二进制码

4位典型格雷码

十进制余三格雷码

十进制空六格雷码

十进制跳六格雷码

步进码

0

0000

0000

0010

0000

0000

00000

1

0001

0001

0110

0001

0001

00001

2

0010

0011

0111

0011

0011

00011

3

0011

0010

0101

0010

0010

00111

4

0100

0110

0100

0110

0110

01111

5

0101

0111

1100

1110

0111

11111

6

0110

0101

1101

1010

0101

11110

7

0111

0100

1111

1011

0100

11100

8

1000

1100

1110

1001

1100

11000

9

1001

1101

1010

1000

1000

10000

10

1010

1111

----

----

----

----

11

1011

1110

----

----

----

----

12

1100

1010

----

----

----

----

13

1101

1011

----

----

----

----

14

1110

1001

----

----

----

----

15

1111

1000

----

----

----

----

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

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

2. 编码方案

2.1 递归实现

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

流程如下:

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

2位格雷码

3位格雷码

4位格雷码

4位自然二进制码

00

000

0000

0000

01

001

0001

0001

11

011

0011

0010

10

010

0010

0011

110

0110

0100

111

0111

0101

101

0101

0110

100

0100

0111

1100

1000

1101

1001

1111

1010

1110

1011

1010

1100

1011

1101

1001

1110

1000

1111

编码实现:

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

输出结果:

代码语言:javascript
复制
4 位格雷码:
0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000

2.2异或转换

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

编码流程如下:

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

编码表示

代码语言:javascript
复制
#include <iostream>
#include <vector>
using namespace std;
/*
*异或转换格雷编码
*/
void  yhGrayCode(int num) {
    //二进制
 vector<int> vec;
    //格雷码
 vector<int>  gc;
 //存储进位值
 int jinWei=0;
    //初始二进制的每一位为 0
 for(int i=0; i<num; i++) {
  vec.push_back(0);
 }
    //循序递增二进制,并且得到对应的格雷码
 while (jinWei!=1) {
  jinWei=0;
  gc.clear();
  //第一位不做异或操作
  gc.push_back(vec[0]);
  //反序遍历,求相邻两个数字的异或结果
  int i=0;
  for(; i<num-1; i++) {
   if(vec[i]==vec[i+1]) {
    gc.push_back(0);
   } else {
    gc.push_back(1);
   }
  }
        //输出格雷码
  for( i=0; i<gc.size(); i++) {
   cout<<gc[i]<<"";
  }
  cout<<endl;
  //更新二进制,递增 1 ,遇到 2 进位
  jinWei= (vec[num-1]+1) /  2;
  vec[num-1]=(vec[num-1]+1) % 2;
  for( i=num-2; 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 位格雷码:"<<endl;
 yhGrayCode(4);
 return 0;
}

输出结果:

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

  • 格雷码最左边一位保持不变。
  • 从左边第二位起,将每位与左边解码后的值异或,结果作为该位解码后的值。
  • 依次异或,直到最低位。依次异或转换后的值就是格雷码转换后的自然二进制。

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

代码语言:javascript
复制
#include <iostream>
#include <stack>
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<<binary[i];
 }
 return 0;
}

2.3 卡诺图实现

什么是卡诺图?

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

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

  • 使用卡诺图编码格雷码,总是由低阶生成高阶。可以绘制如下的表格,行号和列号均以低阶格雷码作为标题。
  • 从卡诺图的左上角以字形到右上角最后到左下角遍历卡诺图,依次经过格子的变量取值即为典型格雷码的顺序。

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

代码语言:javascript
复制
#include <iostream>
#include <cmath>
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<count; i++) {
  if(idx % 2==1) {
   //奇数行,从左向右和 1 阶格雷码合并
   for(int j=0; j<=1; j++) {
    gc[gjIdx++]=dgc[i]+oneGc[j];
   }
  } else {
   //偶数行,从右向左和 1 阶格雷码合并合并
   for(int j=1; j>=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<count ; i++) {
  cout<<gc[i]<<endl;
 }
 return 0;
}

输出结果:

代码语言:javascript
复制
0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000

3. 总结

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程驿站 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 编码方案
    • 2.1 递归实现
      • 2.2异或转换
        • 2.3 卡诺图实现
        • 3. 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档