二分浅谈(一)

来源:互联网 发布:win7网络登录不见了 编辑:程序博客网 时间:2024/05/07 23:35
上世纪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;elselow = 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。


原创粉丝点击