阅读《算法第一步(Python版)》-查找算法

查找算法

查找算法,又叫搜索算法,字面意思上解决查找问题的算法。

另外两种说法:

  1. 检索存储在某种数据结构中的信息的算法;

  2. 在问题域的搜索空间进行计算的算法

要素

输入数据:待查数集合、目标数

目的:确认待查数集合中目标数的存在性、存在位置

在我们的现实中应用某种算法的时候,数据是如何组织的往往已经是既定事实。在一般情况下,我们不是根据算法选择数据结构,而是面对现实的数据结构选择与这种数据结构相适应的具体算法。

顺序查找

顺序查找适用于存储结构为序列式的数据结构「线性数据结构」

基本原理

从待查数列的一端开始,依次遍历其中的每个元素,将元素数值与目标数相比较,若相等则查找成功;若遍历结束仍没有找到与目标数一致的元素,则查找失败。

顺序查找
# -*- coding: utf-8 -*-
# @Time    : 2021/3/13 10:40 上午
# @Author  : zhongxin
# @Email   : 490336534@qq.com
# @File    : 顺序查找.py

def find_num(arr, tn):
    """

    :param arr: 待查数集合
    :param tn: 目标数
    :return:
    """
    i = 0
    while i < len(arr):
        if arr[i] == tn:
            print('查找成功')
            break
        else:
            i += 1
    else:
        print('查找失败')


if __name__ == '__main__':
    tn = 95
    arr1 = [1, 5, 8, 19, 3, 2, 14, 6, 7, 22, 44, 95, 78]
    arr2 = [1, 5, 8, 19, 3, 2, 14, 6, 7, 22, 44, 78]
    find_num(arr1, tn)  # 查找成功
    find_num(arr2, tn)  # 查找失败

衡量算法的性能

时间复杂度

算法需要消耗的时间资源

可以直观理解为算法中基本操作的个数「或叫作步数」

可以使用f(n)表示,其中n是问题规模

大O记号

选用一个特殊的符号来表达函数的量级,这个符号是O

大O是一个数学记号,描述了一个函数在其参数达到某一特定值或无穷大时的极限行为,这个记号体现了函数的增长率。

大O记号的作用是聚焦主要因素,忽略次要因素

几种时间复杂度

  • O(1) 常数时间

  • O(log(n)) 对数时间

  • O(n)线性时间

  • O(nlog(n))对数-线性时间

  • O(n^2)平方时间

  • O(n^3)立方时间

  • O(2^n)指数时间

  • O(n!)阶乘时间

# -*- coding: utf-8 -*-
# @Time    : 2021/3/13 11:04 上午
# @Author  : zhongxin
# @Email   : 490336534@qq.com
# @File    : 时间复杂度.png.py
import math
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0.1, 10, 99)
y0 = [1 for i in x]
y1 = x
y2 = x ** 2
y3 = x ** 3
y4 = [math.factorial(i) for i in range(10)]
y5 = [math.log2(i) for i in x]

plt.axis([0, 10, 0, 10])
plt.plot(x, y0, label='O(1)')
plt.plot(x, y1, label='O(n)')
plt.plot(x, y2, label='O(n^2)')
plt.plot(x, y3, label='O(n^3)')
plt.plot([i for i in range(10)], y4, label='O(n!)')
plt.plot(x, y5, label='O(log(n))')

plt.legend(loc='upper right')
plt.ylabel('y')
plt.xlabel('x')
plt.show()
时间复杂度
顺序查找的时间复杂度

顺序查找的时间复杂度是O(n)

假设待查数列n个元素

任何一个元素与目标数一致的可能性为1/(n+1)

n个待查数分别与目标数相等及不相等共有(n+1)种情况,那平均步数为:(1+2+3+...+n)/(n+1)=n/2

当我们只考虑运算的量级的时候可以说:「顺序查找的时间复杂度是O(n)

空间复杂度

算法需要消耗的存储空间资源

对于任何一个算法而言,只要它处理n个输入数据,就要把这些数据读入存储空间,所以对于任何问题规模为n的算法,它所需要消耗的存储空间至少O(n)

除了程序体控制流程和输入数据占据空间,还有在算法过程中临时存储数据的缓存空间

二分查找

二分查找是一种在有序数列中查找某个特定元素的查找算法

二分查找
# -*- coding: utf-8 -*-
# @Time    : 2021/3/13 11:59 上午
# @Author  : zhongxin
# @Email   : 490336534@qq.com
# @File    : 二分查找.py
import random


def find_num2(arr, tn):
    """

    :param arr: 待查数集合
    :param tn: 目标数
    :return: 索引
    """
    low = 0  # 查找范围开始的索引
    high = len(arr) - 1  # 查找范围结束的索引
    while low <= high:
        middle = int((high - low) / 2) + low  # 防止超越整型数据上限
        # middle = int((high + low) / 2)
        if arr[middle] == tn:
            print('查找成功!')
            return middle
        else:
            if arr[middle] < tn:
                low = middle + 1
            else:
                high = middle - 1
    else:
        print('查找失败!')


if __name__ == '__main__':
    arr = list(range(0, 1000, random.randint(0, 10)))
    tn = random.randint(0, 1000)
    print(f'待查找的数据是:{arr}')
    print(f'目标数字是:{tn}')
    tn_index = find_num2(arr, tn)
    if tn_index:
        print(f'查找到{tn}的索引是{tn_index}')

时间复杂度

第一次循环n=n/1=n/2^0

第二次循环n/2=n/2^1

第三次循环n/4=n/2^4

第k次循环n/2^(k-1)

假设到了第k次循环,查找区间为1

n/2^(k-1) >= 1
n >= 2^(k-1)
log(n) >= log(2^(k-1))
log(n) >= k-1
k <= log(n)+1

所以循环的次数必定小于等于log(n)+1

所以二分查找的时间复杂度为O(log(n))

空间复杂度

没有使用任何额外的存储空间,所以空间复杂度为O(1)

重复数列二分查找

查找重复数字串的「头」或「尾」

# -*- coding: utf-8 -*-
# @Time    : 2021/3/13 2:08 下午
# @Author  : zhongxin
# @Email   : 490336534@qq.com
# @File    : 重复数二分查找.py

def fund_num2_1(arr, tn, delta):
    """

    :param arr: 待查数集合
    :param tn: 目标数
    :param delta: 
        * -1 : 往前挪
        *  1 : 往后挪
    :return: 索引
    """
    low = 0  # 查找范围开始的索引
    high = len(arr) - 1  # 查找范围结束的索引
    while low <= high:
        middle = int((high - low) / 2) + low
        if arr[middle] == tn:
            print('查找成功!')
            while 0 <= middle + delta < len(arr) and arr[middle + delta] == tn:
                middle += delta
            return middle
        else:
            if arr[middle] < tn:
                low = middle + 1
            else:
                high = middle - 1
    else:
        print('查找失败!')


if __name__ == '__main__':
    arr = [3, 3, 3, 5, 5, 5, 5, 9, 7, 12, 15, 15, 18, 32, 66, 78, 94, 103, 269, 269]
    tn = 269
    tn_index = fund_num2_1(arr, tn, 1)
    print(tn_index)

找到目标数字后使用while继续逐个查找,这时候的时间复杂度是O(n)

while 0 <= middle + delta < len(arr) and arr[middle + delta] == tn:
    middle += delta

如果在这部分继续使用二分查找,那么就可以将时间复杂度降低为O(log(n))

if 0 <= middle + delta < len(arr) and arr[middle + delta] == tn:
    if delta < 0:
        high = middle - 1
    else:
        low = middle + 1
else:
    return middle

不包含重复元素旋转数列的二分查找

# -*- coding: utf-8 -*-
# @Time    : 2021/3/13 2:42 下午
# @Author  : zhongxin
# @Email   : 490336534@qq.com
# @File    : 不包含重复元素旋转数列的二分查找.py

def binary_search_in_rotated_sequence(arr, tn):
    low = 0
    high = len(arr) - 1
    while low <= high:
        m = int((high - low) / 2) + low
        if arr[m] == tn:
            return m
        else:
            if arr[m] < tn:
                if arr[m] < arr[low] <= tn:
                    high = m - 1
                else:
                    low = m + 1
            else:
                if arr[m] > arr[high] >= tn:
                    low = m + 1
                else:
                    high = m - 1
    return -1


if __name__ == '__main__':
    arr = [i for i in range(11, 21)] + [i for i in range(1, 11)]
    tnList = [1, 2, 7, 10, 11, 15, 20]
    for tn in tnList:
        r = binary_search_in_rotated_sequence(arr, tn)
        print(f'tn:{tn}在{arr}的第{r}个')

旋转点:旋转数组中最小的元素

  • 情况A,旋转点在中位数的右侧:

  1. 中位数以及它左侧的元素,全部是升序的。

  2. 最左侧元素,必定小于等于中位数。

查找目标出现在左侧的条件是:最左侧元素 <= 查找目标 < 中位数

  • 情况B,旋转点在中位数的左侧:

  1. 中位数以及它右侧的元素,全部是升序的。

  2. 最左侧元素,必定大于中位数。

查找目标出现在右侧的条件是:中位数 < 查找目标 <= 最右侧元素

不包含重复元素旋转数列的二分查找
# -*- coding: utf-8 -*-
# @Time    : 2021/3/13 2:42 下午
# @Author  : zhongxin
# @Email   : 490336534@qq.com
# @File    : 不包含重复元素旋转数列的二分查找.py

def binary_search_in_rotated_sequence(arr, tn):
    low = 0
    high = len(arr) - 1
    while low <= high:
        m = int((high - low) / 2) + low
        if arr[m] == tn:
            return m
        else:
            if arr[m] >= arr[low]:
                if arr[m] > tn >= arr[low]:
                    high = m - 1
                else:
                    low = m + 1
            else:
                if arr[m] < tn <= arr[high]:
                    low = m + 1
                else:
                    high = m - 1
    return -1


if __name__ == '__main__':
    arr = [i for i in range(11, 21)] + [i for i in range(1, 11)]
    tnList = [1, 2, 7, 10, 11, 15, 20]
    for tn in tnList:
        r = binary_search_in_rotated_sequence(arr, tn)
        print(f'tn:{tn}在{arr}的第{r}个')
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值