格雷码算法和逆运算算法

我发现网络上很多格雷码的算法介绍废话连篇,写一大堆理论的东西,给的代码要么不完整,没有逆向算法,要么逆向算法太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的前身,代码已经被优化过了,我并没有保留原始代码。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值