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

news2024/11/19 13:29:59

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 <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;
}

输出结果:

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 <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;
}

输出结果:

7.png

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

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

10.png

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

11.png

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

12.png

13.png

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

#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 卡诺图实现

什么是卡诺图?

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

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

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

14.png

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

15.png

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

#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;
}

输出结果:

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

3. 总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/69825.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文投稿指南——中国(中文EI)期刊推荐(第2期)

&#x1f680; EI是国际知名三大检索系统之一&#xff0c;在学术界的知名度和认可度仅次于SCI&#xff01;&#x1f384;&#x1f388; 【前言】 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊。其中&#xf…

基于jsp+ssm闲置图书分享平台系统-计算机毕业设计

项目介绍 本闲置图书管理系统主要包括系统图书交换模块、图书分享管理模块、图书借阅管理模块、新闻公告管理模块、图书类别管理、图书信息管理、登录模块、和退出模块等多个模块,采用目前最流行的B/S结构和java中流行的MVC三层设计模式和eclipse编辑器、MySQL 数据库设计并实…

代码提速100倍,怎么实现的?

众所周知&#xff0c;Python的简单和易读性是靠牺牲性能为代价的 尤其是在计算密集的情况下&#xff0c;比如多重for循环。不过现在&#xff0c;大佬胡渊鸣说了&#xff1a; 只需import 一个叫做“Taichi”的库&#xff0c;就可以把代码速度提升100倍&#xff01; 不信&…

VTK - vtkPolyData数据的Remesh

欢迎加入我的VTK社区 雪易VTK社区-CSDN社区云 前言&#xff1a;在研究3-matic软件中smooth Edge和Local Smoothing功能时&#xff0c;先对数据的网格进行了重新的划分&#xff0c;即3-matic软件中的Remesh功能。本博文主要针对Remesh进行展开。 目录 网格质量 vtkMeshQuali…

【JavaScript】ESLint 深入浅出

▒ 目录 ▒&#x1f6eb; 导读需求开发环境1️⃣ 初体验安装lint配置.eslintrc.js忽略文件package.json中添加eslint脚本2️⃣ vscode中使用插件安装作用3️⃣ 规避报错问题行添加注释问题文件开头添加注释修改配置文件.eslintrc.js4️⃣ 常见错误汇总Cannot read property nam…

站在巨人的肩膀上,用Node+ChatGPT模块实现一个接口

目录 前言 准备工作 起步 写在最后 前言 蹭一下最近比较火的人工智能ChatGPT的热度&#xff0c;最近看到许多小伙伴都在调戏ChatGPT&#xff0c;看到这我就坐不住了&#xff0c;这种事怎么能少了我&#xff0c;于是闲&#xff08;划&#xff09;暇&#xff08;水&#xff0…

什么事Jupyter Notebook?

Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算&#xff1a;开发、文档编写、运行代码和展示结果。 简而言之&#xff0c;Jupyter Notebook是以网页的形式打开&#xff0c;可以在网页页面中直接编写代码和运行代码&#xff0c;代码的运行结果也…

【javascript】 初见浏览器端日志系统 log4js、bunyan

▒ 目录 ▒&#x1f6eb; 导读需求开发环境1️⃣ log4js配置实现2️⃣ bunyan自定义MyRawStream实现&#x1f6ec; 文章小结&#x1f4d6; 参考资料&#x1f6eb; 导读 需求 用习惯了python、java的日志系统&#xff0c;现在使用console.log等览器端js接口打印日志&#xff0c…

m基于FPGA的多级抽取滤波器组verilog设计,包括CIC滤波,HB半带滤波以及DA分布式FIR滤波

目录 1.算法描述 2.仿真效果预览 3.verilog核心程序 4.完整FPGA 1.算法描述 数字下变频中的低通滤波器是由多级抽取滤波器组实现的。信号的同相分量和正交分量再分别经由积分梳状滤波器(CIC)、半带滤波器(HB)和有限长单位脉冲响应(FIR)滤波器构成的多级抽取滤波器组进行滤波…

vcenter开机报错activating swap-devices in /etc/fstab

问题&#xff1a;昨天IDC机房一台存储断电了&#xff0c;恰巧vcenter在这台存储上&#xff0c;重启存储后再重启vcenter报了以下错误&#xff1a; 参考文档&#xff1a; https://www.virtualizestuff.com/2015/10/29/vcsa_fstab_failed/ https://kb.vmware.com/s/article/2069…

政企数智办公潮水里的融云「答卷」

在这张集合了党政机关、金融保险、交通、能源电力等中大型组织复杂办公需求的高难度答卷上&#xff0c;融云在扎实耐打的通信底层之上&#xff0c;保持灵活的身段和强大的进化能力&#xff0c;稳定而轻盈&#xff0c;在不断变化的环境中正在成为确定性本身。 作者|皮爷 出品…

手把手教你打造一款个人专属Android桌面

实现方式两种 1.从头到尾写一个apk然后把系统的属性加上去&#xff0c;然后启动的时候默认就指定到这个apk的包名&#xff0c;他就启动&#xff0c; 2.我们基于Androidlauncher3的源码去做一个定制化的修改 分析一下这两种的区别&#xff0c; 自定义&#xff0c;要有丰富的…

使用SuperMap iDesktopX如何去掉“耗子尾巴”

在项目中&#xff0c;通过会遇到一些错误的矢量数据&#xff0c;比如“耗子尾巴”。什么是“耗子尾巴呢”&#xff0c;我们所说的“耗子尾巴图斑”&#xff0c;是不规则图斑的一种形态。规则的图斑&#xff0c;应该形态接近圆、矩形、菱形或凸多边形的图斑。自然界中多数图斑应…

[附源码]计算机毕业设计JAVA政府项目管理平台

[附源码]计算机毕业设计JAVA政府项目管理平台 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

ASEMI整流桥MB10F、MB6S、ABS10参数对比

编辑-Z 封装和参数是工程师在选型整流桥时的两大重要依据&#xff0c;下面我们就把比较常见的ASEMI整流桥MB10F、MB6S、ABS10参数做个对比&#xff0c;方便大家选型。 整流桥MB10F参数&#xff1a; 型号&#xff1a;MB10F 封装&#xff1a;MBF-4 最大重复峰值反向电压&…

Redis - Linux下载与安装

1.通过apt方式安装Redis 在终端中输入&#xff0c;如下命令进行安装&#xff1a; # 更新软件源 sudo apt update # 安装redis-server sudo apt install redis-serverredis服务安装完成后&#xff0c;服务将自动启动。通过如下命令查看服务进程是否启动&#xff1a; ps aux |…

快速上手Django(八) -Django之 统一异常、Response处理

文章目录快速上手Django(八) -Django之 统一异常、Response处理一、统一Response处理二、统一异常处理1. 需求背景2. Django、drf统一异常处理3. Django、drf异常处理基础4. 纯django场景下5. 【重要】使用drf场景下&#xff0c;实现思路编写自定义异常处理方法在settings/dev.…

高端前端彻底搞定this指向(详解)

这篇文章只告诉你三件事情&#xff0c;this,this,还TM的是this。 1&#xff0c;this在javascript中是可有可无的 大家在学习javascript的时候&#xff0c;肯定或多或少看到过了很多代码。其中肯定会发现this的身影。我们知道他是代表的指向&#xff0c;可是让人迷惑的是this的…

Scheduled定时任务异步执行

1.使用配置 我在使用SpringBoot配置定时任务的过程中,使用Scheduled配置了多个定时任务,但是在项目启动的时候每次只会启动一个定时任务,只好搜索一波,直到看到了 ThreadPoolTaskScheduler的源码,才发现默认开启的线程数是 1 Configuration public class ScheduledPoolConfi…

如何用代码实现决策树来决策要不要相亲?

前言 上一篇我们了解了什么是决策树&#xff0c;知道了决策树构建的过程&#xff0c;同时聊了构建决策树的两种算法&#xff0c;那么我们今天来看下如何使用代码实现决策树的构建。 数据分析整体流程 数据分析一般是以下的分析流程 1、加载数据集 首先我们构建数据,提供训…