利用二分查找在循环递增数组中检索一个元素
这是我在校园招聘面试时被问到的一个问题。问题是:
在一个循环递增数组中检索一个元素的时间复杂度较低的算法。
所谓循环递增数组就是,假设一个长度为n的数组A,存在一个有效下标r(下标从0开始),使得将子数组A[0...r]拼接到子数组A[r+1...n-1]后面得到一个严格递增数组(A[i...j]表示数组A的从下标i开始到下标j结束的所有元素组成的子数组)。严格递增数组是循环递增数组的一个特例。下面是一个循环递增数组的例子,
5,6,7,8,9,0,1,2,3,4
将子数组(5,6,7,8,9)拼接到子数组(0,1,2,3,4)后面就得到一个严格递增数组(0,1,2,3,4,5,6,7,8,9)。
在一个数组中检索一个元素,最普通的算法就是时间复杂度为O(n)的顺序检索。要降低时间复杂度,并结合循环递增数组的元素已有一定程度顺序的性质,很容易想到二分查找。
我最初的想法是:朴素的二分查找在是下标区间[0 n-1]内进行二分,那么利用循环递增的性质,我们可以在数组的有效下标区间[0 n-1]的一个偏移区间[r+1 n+r-1]里进行二分(其中r是上述循环递增数组定义中的那个分界下标r)。但是,这个想法的一个最大问题是:给定一个循环递增数组,我如何去确定其分界下标r?最明显的做法就是顺序扫描一遍数组,然后确定其分界下标r。但是,既然你都扫描一遍了,也就能确定检索元素是否在该数组中了,何必再去利用分界下标r去检索呢?!
上述想法不成功,我们再去深入地去思考二分查找方法的一些原理特性。在一个严格递增的数组中,我们将要检索的元素和数组中间的元素进行比较,然后根据要检索的元素与数组中间的元素的大小关系来确定该元素落在那个范围内,然后递归地在该范围内进行检索。
现在在循环递增数组中,我们不能简单地通过与数组中间元素的大小关系来确定要检索的元素所落在的区间范围。要确定范围,我们可以再加上要检索的元素与数组两端的元素的大小关系。
循环递增数组有这么一个性质:以数组中间元素将循环递增数组划分为两部分,则一部分为一个严格递增数组,而另一部分为一个更小的循环递增数组。当中间元素大于首元素时,前半部分为严格递增数组,后半部分为循环递增数组;当中间元素小于首元素时,前半部分为循环递增数组;后半部分为严格递增数组。
记要检索的元素为ele,数组的首元素为b-ele,中间元素为m-ele,末尾元素为e-ele。则当ele不等于m-ele时,
1.m-ele > b-ele,即数组前半部分为严格递增数组,后半部分为循环递增数组时,若ele小于m-ele并且不小于b-ele时,则ele落在数组前半部分;否则,ele落在数组后半部分。
2.m-ele < b-ele,即数组前半部分为循环递增数组,后半部分为严格递增数组时,若ele大于m-ele并且不大于e-ele时,则ele落在数组后半部分;否则,ele落在数组前半部分。
通过上面利用数组首元素,中间元素和末尾元素确定要检索的元素所在范围,我们就可以使用修改后的二分查找算法了。
下面是此算法的scheme实现:
1 (define (recycle-inc-search vec ele)
2 (define (rec begin end)
3 (cond ((> begin end) -1)
4 (else
5 (let ((mid (floor (/ (+ begin end) 2))))
6 (let ((b-ele (vector-ref vec begin))
7 (m-ele (vector-ref vec mid))
8 (e-ele (vector-ref vec end)))
9 (cond ((= ele m-ele) mid)
10 ((> m-ele b-ele)
11 (cond ((and (< ele m-ele)
12 (>= ele b-ele))
13 (rec begin (- mid 1)))
14 (else
15 (rec (+ mid 1) end))))
16 (else
17 (cond ((and (> ele m-ele)
18 (<= ele e-ele))
19 (rec (+ mid 1) end))
20 (else
21 (rec begin (- mid 1)))))))))))
22 (rec 0 (- (vector-length vec) 1)))
posted on 2012-02-15 15:40 lienhua34 阅读(1773) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端