格雷码算法和逆运算算法

我发现网络上很多格雷码的算法介绍废话连篇,写一大堆理论的东西,给的代码要么不完整,没有逆向算法,要么逆向算法太low,实在有点看不下去。我这篇格雷码的文章将直入主题,面向那些知道格雷码是什么,但不知道怎样编写高效程序的程序员。

先给一个正向算法,整数变格雷码:


java版:

        /**
	 * 整数变格雷码。<br/>
	 * 设有效位bits,若2<sup>n-1</sup> < bits ≤ 2<sup>n</sup>;<br/>
	 * 则,连续进行2<sup>n</sup>次计算后,变回原数。
	 * 
	 * @param nCode
	 * @return
	 */
	public static final int convertIntCodeToGrayCode(int nCode) {
		return nCode ^ (nCode >>> 1);
	}

C版:

unsigned convertIntCodeToGrayCode(unsigned nCode){
    return nCode ^ (nCode >> 1);//注意:java版的右移运算用三个'>'字符,C版的用两个,不能直接复制。
}

考虑到java版和C版代码只差一点,我目前主要用java,因此后面只提供java版的代码。

逆运算有两大方向:

1、周期运算

原理:对一个数进行连续格雷迭代运算后,经过一个周期后会变回原先的数。这个周期是2的n次方(1、2、4、8、16、32、64...)。如果需要的位数不足,则向上补全。比方我只需要用到其中的低17位,但是没有17这个周期,我就需要以32为周期了。

算法1对于有效位数刚好为2的n次方的情况能够比算法2少一次迭代。

public static final int convertGrayCodeToIntCode(int nCode){
    //由于int的位数为32位,所以它的周期是32。只要将格雷码连续再转换31次就可以回到原来的二进制码。
    //如果实际用到的位数只是其中的16位,则只需要再进行15次转换。转换的周期一定是2的某次方(1、2、4、8、16、32、64)
    for(int i=0;i<31;++i){ 
        nCode=convertIntCodeToGrayCode(nCode);
    }
 }

 

2、反向迭代法

 

原理是将y=X ^ (X>>>1);进行变换,得到x=Y^(x>>>1);这个迭代式就是逆运算迭代式了。用逆运算迭代式在最多等于有效位数次的迭代次数后将变得稳定。也就是会出现从某一次开始,后面的结果不管迭代多少次都是一样的。因此,可以通过比较相邻两个结果是否相同来判定是否终止迭代。

 

        /**
	 * 基于迭代法的格雷码变整数,通过测试结果稳定性来判定返回。通过随机测试结果可知,在大多数情况下,迭代次数都等于有效位数,只在少数情况下会小于有效位数。<br/>
	 * 格雷码变整数的另外一种算法是,连续进行1、2、4、8、16、……、2<sup>n</sup>-1次整数变格雷计算。<br/>
	 * 但是如果有效位不是2<sup>n</sup>位,也必须做2<sup>n</sup>-1次计算,如10000有效位是5位,但仍然需要进行7次计算。
	 * 
	 * @param nCode
	 * @return
	 */
	public static final int convertGrayCodeToIntCode(int nCode) {
		int b = 0, lastB = 0;
		for (;;) {
			b = nCode ^ (lastB >>> 1);
			if (b == lastB) {
				return b;
			}
			lastB = b;
		}
	}

对于某些特殊的数,所需的迭代次数小于有效位数,但这个概率随有效位数的增加而急减。对于有效位数16位的情况,迭代次数几乎很难小于16了。因此,有时为了保证时序的稳定,也会设置一个固定的迭代次数。这个固定的迭代次数为所需要的有效位数。

        /**
	 * 基于迭代法的格雷码变整数,通过运行指定的次数来保证结果正确。<br/>
	 * 格雷码变整数的另外一种算法是,连续进行1、2、4、8、16、……、2<sup>n</sup>-1次整数变格雷计算。<br/>
	 * 但是如果有效位不是2<sup>n</sup>位,也必须做2<sup>n</sup>-1次计算,如10000有效位是5位,但仍然需要进行7次计算<br/>
	 * 对于正好是2<sup>n</sup>位的格雷码, 进行2<sup>n</sup>-1次格雷变换的方法会节省一次计算,否则采用此方法计算量更少。
	 * 
	 * @param nCode
	 * @param bits
	 *            迭代次数,一般可填有效位数。超过有效位数的计算是没有意义的,只会浪费时间。如果使用者确定必须的迭代次数小于有效位数,也可填写小于有效位数的数字。
	 * @return
	 */
	public static final int convertGrayCodeToIntCode(int nCode, int bits) {
		int b = 0;
		for (int i = 0; i < bits; ++i) {
			b = nCode ^ (b >>> 1);
		}
		return b;
	}

 

3、按位运算,一位一位地算。

除非用是51单片机,可以按位寻址。否则一位一位地计算要消耗更多的机器周期。就算是可以按位寻址,它的性能也只能和算法2的固定次数计算一样。事实上算法2的来源就是一位一位地计算的,并不是我上面写的用迭代法。后来把位指令去除后我发现结果一样,有时甚至还更快速。于是我对此现象进行了研究,发现原来这是一个迭代公式,它可以更高效地还原格雷码。因为算法3是算法2的前身,代码已经被优化过了,我并没有保留原始代码。

实验二 递归算法设计与应用 一. 实验目的要求 1. 加深对递归算法的理解,并针对具体问题设计算法; 2. 分析算法的复杂性,寻找比较高效的算法,并实现。 3. 分析格雷码问题,并设计递归算法求解之。 二. 基本原理 递归是一种重要的程序设计方法。使用递归方法有时可使算法简洁明了,易于设计。 递归指算法自己调用自己, 有直接递归与间接递归两种。 递归方法用于解决一类满足递归关系的问题。即:对原问题的求解可转化为对其性质相同的子问题的求解。 三. 该类算法设计与实现的要点 1. 递归关系(特性):产生递归的基础。 当算法中某步骤要通过解性质相同的子问题实现时,该步骤用递归调用实现。 2. 递归出口(结束条件):确定递归的层数。 当子问题的规模充分小时可直接求解时,递归结束。 3. 参数设置:参数表示了原问题及其不同的子问题。 参数表示了子问题的大小状态,以区别原问题以及不同层次的子问题。 4. 算法功能的设定:严格规定递归算法要解决什么样的问题。 算法功能的正确设定是保证递归过程正确进行的前提。 四. 实验内容――格雷码问题 1.问题描述 对于给定的正整数n,格雷码为满足如下条件的一个编码序列: (1) 序列由2n个编码组成,每个编码都是长度为n的二进制位串。 (2) 序列中无相同的编码。 (3) 序列中位置相邻的两个编码恰有一位不同。 例如:n=2时的格雷码为:{00, 01, 11, 10}。 设计求格雷码的递归算法并实现。 2. 具体要求(若在ACM平台上提交程序,必须按此要求)――平台上1769题 输入:输入的第一行是一个正整数m,表示测试例个数。接下来几行是m个测试例的数据,每个测试例的数据由一个正整数n组成。 输出:对于每个测试例n,输出2n个长度为n的格雷码。(为方便查看,在每个格雷码内,两个位之间用一个空格隔开,如,00输出为:0 0)。两个测试例的输出数据之间用一个空行隔开,最后一个测试例后无空行。 3. 测试数据 输入:2 4 5 输出:0 0 0 0 0 0 0 1 0 0 1 1 0 0 1 0 0 1 1 0 0 1 1 1 0 1 0 1 0 1 0 0 1 1 0 0 1 1 0 1 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 1 1 0 0 0 1 1 1 0 0 1 0 1 0 0 1 0 0 0 1 1 0 0 0 1 1 0 1 0 1 1 1 1 0 1 1 1 0 0 1 0 1 0 0 1 0 1 1 0 1 0 0 1 0 1 0 0 0 1 1 0 0 0 1 1 0 0 1 1 1 0 1 1 1 1 0 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 0 1 0 0 1 0 1 0 1 1 0 1 1 1 1 0 1 1 0 1 0 0 1 0 1 0 0 1 1 1 0 0 0 1 1 0 0 0 0 4. 设计与实现的提示 长度为n的格雷码是由长度为n-1的格雷码变换而成的。 可以用数组或字符串来存储格雷码。注意:对于较大的正整数n,用数组存储容易引起死机。 按照定义2n个长度为n的格雷码序列是不唯一的,若在ACM平台上提交程序,要求输出的编码序列与给出的范例具有相同的规律。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值