带你直观地感受二分查找

带你直观地感受二分查找

写在前面

​ 二分查找(Binary Search)被认为是极具效率的查找算法,其 O ( l o g n ) O(logn) O(logn) 的时间复杂度在大规模数据下具有极高的效率。比如,试想要从70亿人中找到1人,计算次数可以被降到32次。这使得二分查找成为了一个相当具有研究价值的算法。

​ 本文就二分查找进行了少量优化,并利用可视化的软件包对实验结果进行直观展示。

​ 本文的是以学习二分查找算法原理,直观呈现并感受其效率为目的撰写。

1. 二分查找

1.1 原理

二分查找的基本思想,是一种分治的思想。

二分查找的基本原理:

为一个有序可随机访问的抽象数据结构创建三个指针,高位指针、低位指针与中间指针。

具体来讲,以有序数组为例,我们创建三个整数,看作指针,分别称作高位、低位、中间位。

我们有一个目标数 t t t

每次将 t t t 与中间位进行比较,不断压缩查找范围。

这样就可以提高查找效率,并达到 O ( l o g n ) O(logn) O(logn) 的时间复杂度。
在这里插入图片描述
(图片来源:极客时间)

1.2 流程

Created with Raphaël 2.2.0 开始 low<=high 算中间指针 t大于mid low=mid+1 t小于mid high=mid-1 mid==0? or [mid-1] !=t ? 返回下标 结束 返回-1 yes no yes no yes no yes no

2. 实验

2.0 实验环境

Anaconda 3.0

Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-101-generic x86_64)

JupyterLab

2.1 核心代码

'''
A: 输入数组
k: 目标数字
'''
def binary_search(A , k):
    left = 0 
    right = len(A) - 1
    find = False
    while left<=right:
      # 利用移位操作,提高运算效率
        mid = int(left +((right - left)>>1))
        if k < A[mid]:
            right = mid -1
            continue
        elif k > A[mid]:
            left = mid + 1 
            continue 
        else:
            if mid==0 or A[mid-1]!=k:
                return mid
            else:
                right = mid - 1
    return -1

2.2 实验代码

  • 导入软件包
from matplotlib import pyplot as plt
import numpy as np
import time as t
import pandas as pd
from scipy.optimize import curve_fit
import math	
  • 产生一个随机的指定大小的numpy数组
def generateArray(n):
    A = np.random.randint(0,n,size=n)
    A = np.sort(A,axis=-1,kind='quicksort',order=None)
    return A
  • 核心的实验代码,测试各个数量级的数据,每组100次
# 结果存入数组
size=100000000
log_size=int(math.log(size,10))
n_time=100

print(log_size)
rs = np.zeros([log_size,n_time])
i =1
m = 0
while i<size:
    #i:数据规模
    #确定一个目标值
    k = np.random.randint(0,i)
    print(i)
    # j:测试次数(每个数据规模测100次,暂时)
    for j in range(n_time):
        A = generateArray(i)
        # profile CPU time
        t1 = float(t.time())   
        binary_search(A,k)        
        t2 = float(t.time())
        
        delta_time = t2 - t1
        rs[m][j]= delta_time
    i=i * 10
    m = m+1

2.3 绘图

问题

在绘图时,由于是采样实验,所绘制出的曲线可能会出现一定的波动,导致结果的展示不够清晰。

解决

导入 scipy 软件包,利用软件包的 scipy.optimize 组件的 curve_fit 方法

由于本实验的期望曲线呈对数类型,在利用curve_fit方法时,需要一些特殊拟合方法,具体请参考StackOverflow的回答或官方文档

以下给出代码:

#对数拟合
def func(x,a,b):
    print(x)
    y = np.log(x)+b
    return y 
# draw the graph
x = np.arange(log_size)
y = rs
# 行:数据规模   列:实验次数
df = pd.DataFrame(y,index=x,columns=np.arange(n_time))
df["mean"] = df.mean(axis=1)
df['var'] =  df.std(axis=1)

fig = plt.figure()
# x坐标
x =[1,1e1,1e2,1e3,1e4,1e5,1e6,1e7]    
# y坐标是均值序列
y = df["mean"]
# popt返回拟合优化后的参数序列
popt, pcov = curve_fit(func,x,y)
# 这里放大了 标准差 使得图上的表达更加直观
plt.errorbar(x,func(x,popt[0],0)*1e-5,yerr=df["var"]*2,fmt='bo:',
              label='proformance in differenct data sizes')
plt.legend()

结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9uGdEB5E-1602584464500)(/Users/alex/Desktop/CSDN/blogs/Algo/curve.png)]

可以看到,运行时间的曲线,在不同数量级的数据规模下基本呈现类似对数函数的形状。

3. 结论

3.1 算法上的反思

二分查找本身虽然在查找效率上拥有极高的效率,但是也存在诸多局限:

  1. 需要以支持随机查找顺序表结构为基础。
  2. 需要有序的数据结构,因此在面对无序数据时,需要消耗时间进行排序的预处理。
  3. 数据量过小时,无法发挥其效能。
  4. 数据量过大时,由于其以顺序表的基础,需要连续的内存空间,导致二分查找在这个场景下不能很好地应用。

3.2 实验上的反思

本次实验的收获主要体现在如下几点:

首先,对二分查找的高效率有了直观清晰的理解;

其次,在代码实践中学会了数据参数的优化、拟合,errorbar曲线的绘制;

最后,想感谢程振波老师提供的此次机会,让本人能够以偏向于学术的方式研究一个算法的效能,从中学到的不仅是算法思想与技术。

更多的,是科学研究的方法论。


参考

《Design and Analysis of Algorithm Using Python》 程振波等著 清华大学出版社

《数据结构与算法之美》 王争

Matplotlib官方文档

numpy官方文档

Scipy官方文档

StackOverflow关于曲线拟合的回答

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值