现在的位置: 首页 > 综合 > 正文

算法设计:如何求出一个64位数中哪一位是‘1’

2017年10月06日 ⁄ 综合 ⁄ 共 2224字 ⁄ 字号 评论关闭

问题描述

            今天在写游戏的时候,有这么一个需求:

            我需要根据一个标示符的值,标示符的值一般都是用这样的数值来表示:

             如用(1 << 2) 这个来标示一个可以被渲染的对象, (1 << 3) 来标示一个可以进行碰撞的对象,诸如此类的。

             所以,我需要根据上面的标示符来获取上面标示符中哪一位为1?

             也就是说,对于(1 << 2) 这个值, 它的第2位为1(从下标为0开始计算)。

算法设计

            对于这样的一个算法问题,很容易想到,对每一个位使用逻辑运算来判断下,这样就能够简单的获取到哪一位为1了。

           对,采用这样的方法,在算法设计上,称为蛮力法。蛮力法是一个问题的最直接,最容易想到,也是最标准的做法了。但是,由于游戏的效率问题,我这个函数要频繁的调用,所以我想将它稍微的提高一下效率。

           最近浏览了下《算法设计与分析基础》,大体的内容没有仔细的看,只是简单的浏览了下,设计算法时的几种有效工具,以及他们的思想。

           所以,我一下就想到,使用分治法来设计这个算法。

           下面算法的概要描述:

           我们假设,这个标示符,使用64位无符号整形来表示,那么,我们每一次将这个值折半,也就是说,将64位分为两个部分,分别是高位部分,和低位部分,他们都是有32位组成的。然后在对这32位的值,不停的进行折半,知道最后折半成为1位的数,然后我们在来对这个1位的数判断,是否为1,这样我们就能够计算出哪一个值为1了。

          (感觉上,好像和蛮力法的效果一样啊???由于对于算法复杂度分析还不是很了解,所以就暂时不管是否真的比蛮力法高效了^_^)

代码

          最后给出程序的代码,很显然,使用分治法,需要用到递归,所以,我使用递归来设计这个函数,而且,读者请注意,这个算法只是针对整形数据中,只有一位为1的情况,如果有多个位为1,这种行为,程序未对其进行考虑。以下是最终的代码:

unsigned int getbitone64(unsigned _int64 value, unsigned int number, int index)
{
	if(value == 1)
		return index ;
	else if(number == 1)
	{
		return -1 ;
	}
	else
	{
		unsigned int result = -1 ;
		unsigned _int64 temp = value ;
		unsigned _int64 half_num = number/2 ;
		int offset = 64 - half_num - index ;
		temp = temp << offset ;
		temp = temp >> offset ;

		result = getbitone64(temp, half_num, index);
		if(result  != -1)
			return result ;

		result = getbitone64(value >> half_num, half_num, index + half_num);
		if(result  != -1)
			return result ; 
	}
}// end for getbitone64

            很简短的一段代码。

注意点

           上面的代码有几个地方需要注意,这些地方都是我写代码的时候犯错的地方。

  •  对于获取值的左半部分,和右半部分,我是采用移位的方式来进行的。获取左半部分,如0x0010 0000中的0010,这个很容易,只要将这个值右移16位就好了。所以,同样的,对于64位的值,我们需要右移32位来获取左半部分的值,对于32位的时候,我们右移16位,依次类推。对于获取右半部分,就有点混乱了。我才开始的时候想到的算法是这样的,在64位值情况下,只要将值先左移32位,然后在右移32位,就可以了。所以,我就觉得,对于上面问题中,所有的折半情况,都是先左移值的一半,然后在右移。后来发现,这个方法,只是在才开始的时候,值为64位时有效。一旦进行了折半,比如还剩下32位了,那么如果我先左移16位,再右移16位,实际上是没有用的。为什么了?读者如果细心的话,就会发现,我在上面的代码中一直都是使用64位的unsigned
    _int64来保存折半后的值,也就是说,不管折半以后还剩下多少,我总是用64位值来保存。所以,比如,在折半后,最后还剩下10,这两个值,如果用上面的算法来求右半部分的话,我们先左移1位,等于100,再右移1位等于10,最后的结果不是0而是10(2)。所以这就是使用64位值来保存时,没有移出64位的范围,最高位的1没有被清0。所以,我想到,不应该总是移动一半的值,而是64 - 一半的值。还是拿10来说明,现在要移动的数据应该是64 - 1 = 63 位,左移63位得到的就是0了,然后再右移63位得到的还是0,这样就准确的获取了右半部分。
  • 第二个问题是,我才开始设计时,没有考虑符号位的关系。所以使用的是_int64的值。但将1移到最高位的时候,它变成了符号位。我们知道,在有符号的数中,不管怎么移动,它的符号位都是保存不变的,所以这就坑了,进行判断的时候,总是是个负数。

           好了,上面两点就是在设计过程中遇到的主要问题,明白了这些细节,也就不是很麻烦了。

           上面的算法,很容易移植到32位,16位中去,感兴趣的读者,可以自行进行移植。

抱歉!评论已关闭.