二分浅谈(一)

上世纪60年代,曾经有一位学者在书中这样写道:二分搜索是一种非常基础和实用的算法,但是如果让他的大学同事在5分钟之内写一个搜索出来的话,会有八成的同事会得零分。他的名字我已经不复记忆,这倒不是对他的不敬,只是记忆力衰弱,时间久远的缘故,不过当时我确实将这句话牢记在心,每逢写到二分算法必然毕恭毕敬,不敢放肆。

二分的实用性自然不必多说。最近做一个短信编码的项目,刚好涉及一个转编码算法;有一些转码尚有规律可循,如果没有规律可循也可以开一个Hash,但如果连Hash也很难编码,那还不如先排序再搜索来的简单方便。二分的基础是有序,无论是单调上升还是单调不下降;归根到底,最基本的数学模型就是寻找已排序好的元素 :有自变量x与因变量y,函数为y=F(x),且当x1>x2时y1>y2(或者y1>=y2),现已知y,求x(或求最小/最大的x)。

最简单的模型

以UNICODE转GBK编码为例:(当然这有直接的规律,百度一下就知道,这里拿来只是演示所用)

我们开两个数组,GBK数组GBK_Code[7000],UNC数组Unicode_Code[7000],并将其整合到同一个数组GB_TO_UNI[7000][2]中

以Unicode数组进行排序,现在我们就有了一个有序数列:GB_TO_UNI[0][0], GB_TO_UNI[1][0], GB_TO_UNI[2][0], GB_TO_UNI[3][0], GB_TO_UNI[4][0]....在这个数列里面,自变量是下标0,1,2,....,应变量是Unicode编码,现在的任务就是倒过来已知编码求下标。(这个项目里原来是GB转到Unicode,所以数组名字未曾更改,特注明)

int low = 0, high = m_iDictLength; // 长度
	int mid;
	while(low < high)
	{
		mid = (low + high) / 2;
		UINT16 midCode = GB_TO_UNI[mid][1];
		if(midCode == unCode)
		{
			return GB_TO_UNI[mid][0];
		}if(unCode < midCode)
			high = mid - 1;
		else
			low = mid + 1;
	}         
	return GB_TO_UNI[low][0];


当midcode==code,自然不用多说;

当midcode<code,也就是说正解落在(mid,high],左开右闭。

当midcode>code,请读者自行理解。

这是一种比较激进的写法,因为Code两两各不相同,而且我可以确定每个Unicode编码都能在字典中寻找到。当条件允许的时候,可以适当放宽二分在边界处的写法;但是如果不能确定每个编码都能被找到,那就要小心从事了。说到底,每种算法都需要视输入条件所定,做一定的调整以达到最高的效率,而预处理也是很重要的一个步骤。


唯一的问题就是,为什么可以这样写,确实不会出现死循环之类的问题么,确认边缘数据不会被遗漏么?分析一下一个小数据:lo = 1, hi = 4时的情况。

假设正解是1:

 mid = 2; code<midcode(想想看为什么); high =1; low = 1; 跳出return 1

正解是2:

mid = 2; code = midcode; return 2;

正解是3:

mid = 2; code > midcode; low = 3;

mid = 3; code = midcode; return 3;

正解是4:

mid = 2; code > midcode; low = 3;

mid = 3; code > midcode; low = 4; 跳出 return 4

因为当return low的时候,必然发生low>=high,换言之,在前一步发生low = mid + 1或者high = mid - 1,这两种情况下均有low=high=answer。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值