数值查找算法(二分搜索算法)

查找是指在大量数据中找到特定的目标元素。一般情况下,我们首先想到的最笨的方法就是,按照顺序进行遍历依次查找,但是当我们处理大数据时,这种方法就着实让人捉急了。那怎么办呢?

没事,我们有二分查找算法!

二分查找又称折半查找,对数查找,是指在有序的数值序列中,从中间开始查找,如果查找元素小于中间元素,则在中间元素的左边区间进行折半查找,相反,则从右边区间进行查找,直到取到的中间值等于查找值或者查找的值不存在。

折半查找的最差时间复杂度为O(log n),最优时间复杂度为O(1),平均时间复杂度为O(log n)。而通过最原始捉急的遍历查找,对应的平均时间复杂度则为O(n),相比而言是不是优势很大呢?

那么接下来我们来看一看如何进行二分查找吧。

步骤:

(1)确定该期间的中间位置K
(2)将查找的值T与array[k]比较。若相等,查找成功返回此位置;否则确定新的查找区域,继续二分查找。区域确定如下:
a.array[k]>T 由数组的有序性可知array[k,k+1,……,high]>T;故新的区间为array[low,……,K-1]
b.array[k]<T 类似上面查找区间为array[k+1,……,high]。每一次查找与中间值比较,可以确定是否查找成功,不成功当前查找区间缩小一半。递归找,即可。

"""
1. 二分查找是有条件的,首先是有序,其次因为二分查找操作的是下标,所以要求是顺序表
2. 最优时间复杂度:O(1)
3. 最坏时间复杂度:O(logn)
"""

def BinarySearch(array, value):
    """
    非递归解决二分查找
    返回下标
    """
    low = 0
    high = len(array)-1
    while low < high:
        mid = (low+high)/2
        if array[mid] < value:
            low = mid + 1
        elif array[mid] > value:
            high = mid - 1
        else:
            return mid
    return -1

def BinarySearch(array, value):
    """
    递归解决二分查找
    返回是否存在
    """
    n = len(array)
    if n < 1:
        return -1
    mid = n // 2
    if array[mid] > value:
        return BinarySearch(array[0:mid], value)
    elif array[mid] < value:
        return BinarySearch(array[mid+1:], value)
    else:
        return True

if __name__ == "__main__":
    print BinarySearch([1,2,3,34,56,57,78,87],57)

当然,有时候我们可能会遇到其他的变式情况。但解决的方法还是一样的。

举个栗子,

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。  输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。  NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

对待这个问题我们该怎么做呢?

我们来分析一下:

 传进去旋转数组,注意旋转数组的特性:

1. 包含两个有序序列 

2. 最小数一定位于第二个序列的开头 

3. 前序列的值都>=后序列的值

旋转之后的数组实际上可以划分成两个有序的子数组:前面子数组的大小都大于后面子数组中的元素 。我们注意到实际上最小的元素就是两个子数组的分界线。因此我们试着用二分查找法寻找这个最小的元素。

那么我们怎么应用二分查找的思想呢?

首先是根据中间元素确定跳转规则:

中间元素大于第一个元素,则中间元素位于前面的递增子数组,此时最小元素位于中间元素的后面。我们可以让第一个指针left指向中间元素。

移动之后,第一个指针仍然位于前面的递增数组中。

中间元素小于第一个元素,则中间元素位于后面的递增子数组,此时最小元素位于中间元素的前面。我们可以让第二个指针right指向中间元素。

按照以上思路,第一个指针left总是指向前面递增数组的元素,第二个指针right总是指向后面递增的数组元素。 最终第一个指针将指向前面数组的最后一个元素,第二个指针指向后面数组中的第一个元素,也就是说,他们将指向两个相邻的元素,而第二个指针指向的刚好是最小的元素,这就是循环的结束条件。 

具体而言就是:

mid = left+ (right- left)/2

需要考虑三种情况:

(1)array[mid] > array[right]:

出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。

left= mid + 1

(2)array[mid] == array[right]:

出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边还是右边,这时只好一个一个试 ,

right= right- 1

(3)array[mid] < array[right]:

出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左边。因为右边必然都是递增的。

right= mid

来看一下具体实现的代码吧

def minNumberInRotateArray(self, rotateArray):
    left = 0
    right = len(rotateArray)-1
    if not rotateArray:
        return 0
    else:
        while left <= right: 
            mid = left + (right-left)/2
            if rotateArray[mid] > rotateArray[right]:
                 left = mid+1
            elif rotateArray[mid] < rotateArray[right]:
                 right = mid
            else:
                 right -= 1
        return rotateArray[mid]

 

转载于:https://my.oschina.net/magicalee/blog/1928748

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值