• 算法笔记(1)—— 搜索算法:线性搜索(LS)、二分搜索(BS)、记忆化搜索(MS)


    本文章会介绍多种常见的搜索算法,包括线性搜索二分搜索记忆化搜索,并用Python语言、C/C++语言实现

    线性搜索(LinearSearch

            线性搜索,从字面意思上理解就是按顺序地线性搜索东西,简单点说就是一个一个地去找。在 C/C++ 中判断某个数组里面是否有某元素时,就会用到它。在Python里判断一个元素是否在一个列表中时,可以用 in(成员运算符)运算符(猜测它的原理就是线性搜索)。

    【适用情形】搜索序列不是很大的情况

    【序列顺序】顺序、乱序均可

    【时间复杂度】O(n)

    【空间复杂度】O(1)

    【动画演示】

    线性搜索(LinearSearch)[动画由Python tkinter模块制作]

    【Python实现】

    1. def LinearSearch(lis: list[int], obj: int): #lis:搜索序列 obj:目标值
    2. for index, value in enumerate(lis): #遍历搜索序列
    3. if value == obj: #判断是否为目标值
    4. return index #找到目标值,返回索引
    5. return None #未找到值,返回None

    【C/C++实现】

    1. int LinearSearch(int arr[], int length, int obj) //arr:搜索数组 length:数组长度(或搜索长度) obj:目标值
    2. {
    3. for(int i = 0; i < length; i++) //遍历数组
    4. {
    5. if(arr[i] == obj) //判断是否为目标值
    6. return i; //返回索引
    7. }
    8. return -1; //没有目标值,返回-1
    9. }

    【练手好题】 

     【洛谷】P3383 【模板】线性筛素数

    二分搜索(BinarySearch)

            二分搜索,也叫二分查找,可以解决线性搜索在搜索时间上的不足。顾名思义,每搜索一次,就二分一次。如果说线性搜索是常数速度,那么二分搜索就是指数速度!

    【适用情形】搜索序列单调递增或单调递减(以下实现代码以单调递增为例

    【序列顺序】顺序(顺序增大或顺序减小)

    【时间复杂度】O(logn)

    【空间复杂度】O(1)

    【动画演示】

    二分搜索(BinarySearch)[动画由Python tkinter模块制作]

    【Python实现】 

    【循环型二分搜索】

    1. def BinarySearch(lis: list[int], obj: int): #lis:搜索序列 obj:目标值
    2. max_index, min_index = len(lis)-1, 0 #初始化搜索的上下边界(索引)
    3. while True: #无限循环
    4. middle = (max_index+min_index)//2 #把上下边界(索引)二分,得中间索引
    5. if max_index < min_index: #设定循环终止条件(上边界小于下边界,这是不应该出现的情况,所以终止)
    6. return None #循环终止,没有目标值,返回None
    7. if lis[middle] > obj: #判断中间值是否大于目标值
    8. max_index = middle-1 #中间值大于目标值,说明目标值的索引应该在中间索引之下,但不包括中间索引,所以上边界(索引)缩小至中间索引-1
    9. elif lis[middle] < obj: #判断中间值是否小于目标值
    10. min_index = middle+1 #中间值小于目标值,说明目标值的索引应该在中间索引之上,但不包括中间索引,所以下边界(索引)增大至中间索引+1
    11. else: #中间值恰好等于目标值
    12. return middle #返回中间索引

    【递归型二分搜索】

    1. ##【递归型】二分搜索 ##
    2. def BinarySearch(lis: list[int], obj: int, max_index: int, min_index: int=0): #lis:搜索序列 obj:目标值 max_index:上索引 min_index:下索引
    3. middle = (max_index+min_index)//2 #把上下边界(索引)二分,得中间索引
    4. if max_index < min_index: #设定递归终止条件(上边界小于下边界,这是不应该出现的情况,所以终止)
    5. return None #递归终止,没有目标值,返回None
    6. if lis[middle] > obj: #判断中间值是否大于目标值
    7. return BinarySearch(lis, obj, middle-1, min_index) #中间值大于目标值,说明目标值的索引应该在中间索引之下,但不包括中间索引,所以上边界(索引)缩小至中间索引-1,递归函数
    8. elif lis[middle] < obj: #判断中间值是否小于目标值
    9. return BinarySearch(lis, obj, max_index, middle+1) #中间值小于目标值,说明目标值的索引应该在中间索引之上,但不包括中间索引,所以下边界(索引)增大至中间索引+1,递归函数
    10. else: #中间值恰好等于目标值
    11. return middle #返回中间索引

    【C/C++实现】

    【循环型二分搜索】

    1. int BinarySearch(int arr[], int length, int obj) //arr:搜索数组 length:数组长度(或搜索长度) obj:目标值
    2. {
    3. int max_index = length-1, min_index = 0, middle; //初始化搜索的上下边界(索引)及中间索引
    4. while(true) //无限循环
    5. {
    6. middle = (max_index+min_index)/2; //把上下边界(索引)二分,得中间索引
    7. if(max_index < min_index) //设定循环终止条件(上边界小于下边界,这是不应该出现的情况,所以终止)
    8. return -1; //循环终止,没有目标值,返回-1
    9. if(arr[middle] > obj) //判断中间值是否大于目标值
    10. max_index = middle-1; //中间值大于目标值,说明目标值的索引应该在中间索引之下,但不包括中间索引,所以上边界(索引)缩小至中间索引-1
    11. else if(arr[middle] < obj) //判断中间值是否小于目标值
    12. min_index = middle+1; //中间值小于目标值,说明目标值的索引应该在中间索引之上,但不包括中间索引,所以下边界(索引)增大至中间索引+1
    13. else //中间值恰好等于目标值
    14. return middle; //返回中间索引
    15. }
    16. }

    【递归型二分搜索】 

    1. int BinarySearch(int arr[], int obj, int max_index, int min_index=0) //arr:搜索数组 obj:目标值 max_index:上边界(索引) min_index:下边界(索引)
    2. {
    3. int middle = (max_index+min_index)/2; //把上下边界(索引)二分,得中间索引
    4. if(max_index < min_index) //设定递归终止条件(上边界小于下边界,这是不应该出现的情况,所以终止)
    5. return -1; //递归终止,没有目标值,返回-1
    6. if(arr[middle] > obj) //判断中间值是否大于目标值
    7. return BinarySearch(arr, obj, middle-1, min_index); //中间值大于目标值,说明目标值的索引应该在中间索引之下,但不包括中间索引,所以上边界(索引)缩小至中间索引-1
    8. else if(arr[middle] < obj) //判断中间值是否小于目标值
    9. return BinarySearch(arr, obj, max_index, middle+1); //中间值小于目标值,说明目标值的索引应该在中间索引之上,但不包括中间索引,所以下边界(索引)增大至中间索引+1
    10. else //中间值恰好等于目标值
    11. return middle; //返回中间索引
    12. }

    【练手好题】 

    【洛谷】P2249 【深基13.例1】查找

    【LeetCode】704. 二分查找

    记忆化搜索(MemorySearch)

            记忆化搜索,就是在搜索的过程中同时记住一些值(有动态规划的思想),然后在后面要再次用到该值时可以直接进行调用(就避免重复计算了),有点像递推,但递推是直接对数组元素(或者列表元素)进行操作了,是在记录值的同时往后一步一步推导值,每一步之间都有联系,或者说,递推的规律性比较强,而记忆化搜索就比较普适了(个人认为)。记忆化搜索可以解决递归计算速度慢的问题。

    【适用情形】搜索过程中会重复计算某值或者重复进行某一过程

    【序列顺序】一般事先未知序列

    【时间复杂度】不确定,取决于搜索函数(但同问题下,绝对比递归方法小)

    【空间复杂度】不确定,取决于搜索函数

    【动画演示】

    记忆化搜索(MemorySearch)[动画由Python tkinter模块制作]

    【Python实现】

    【记忆化搜索一般操作】

    1. MemLis = [None]*1001 #记忆化列表,用于存储搜索过程中的数据
    2. def MS(n): #定义MS函数,便于读取记忆和存储数据
    3. if not MemLis[n]:MemLis[n]=MemorySearch(n) #如果MemLis[n]的值为None,说明该位置还没有数据被记录,则计算应计算的值并赋值给MemLis[n]
    4. return MemLis[n] #返回MemLis[n]的值
    5. def MemorySearch(n:int): #n:第n项
    6. if :return #直接返回数字的情况
    7. elif :return MS()+MS()+... #递归调用的情况
    8. else:... #其它情况

    【Python特有高级操作】 

    1. from functools import cache #引入记忆化搜索装饰器(旧版为lru_cache)
    2. @cache #装饰函数
    3. def object_function(*args, **kwargs): #待装饰的目标函数
    4. ...
    5. '''
    6. 装饰完之后的目标函数就是记忆化搜索的函数了,它会在运行的时候申请大量内存来“记忆”
    7. 不过该方法不能保证函数递归不超过最大递归深度(1000)
    8. '''

    【举个栗子】


            现在要我们求斐波那契数列的第500项,读者不妨先用递归试试!再用上面的模板试试!比较一下速度!

            记忆化搜索一般解法

    1. MemLis = [None]*1001 #记忆化列表
    2. def MS(n): #定义MS函数,便于读取记忆和存储数据
    3. if not MemLis[n]:MemLis[n]=MemorySearch(n) ##如果MemLis[n]的值为None,说明该位置还没有数据被记录,则计算应计算的值并赋值给MemLis[n]
    4. return MemLis[n] #返回MemLis[n]的值
    5. def MemorySearch(n:int): #n:第n项
    6. if n==1:return 0 #斐波那契数列第1项
    7. elif n==2:return 1 #斐波那契数列第2项
    8. else:return MS(n-1)+MS(n-2) #斐波那契数列其它项
    9. print(MemorySearch(500)) #输出结果
            记忆化装饰器解法
    1. from functools import cache #引入装饰器
    2. @cache #装饰函数
    3. def fibonacci(n:int): #斐波那契函数
    4. if n == 1:return 0 #斐波那契数列第1项
    5. elif n == 2:return 1 #斐波那契数列第2项
    6. else:return fibonacci(n-1)+fibonacci(n-2) #斐波那契数列其它项
    7. print(fibonacci(500)) #输出结果

    【C/C++实现】

    【记忆化搜索一般操作】

    1. int MemArr[1001]; //用于记忆的数组在内存的要求之下尽可能开大
    2. int MS(int n) //定义MS函数用于读取记忆和存储数据
    3. {
    4. int MemorySearch(int n); //声明搜索函数
    5. if()MemArr[n] = MemorySearch(n); //如果MemArr[n]的值不满足某种条件,说明MemArr[n]还未存储数据,则计算其应有的值并赋值给MemArr[n]
    6. return MemArr[n]; //返回MemArr[n]的值
    7. }
    8. int MemorySearch(int n) //n:第n项
    9. {
    10. if()return ; //直接返回数字的情况
    11. else if()return MS()+MS()+...; //递归调用的情况
    12. else ...; //其他类似的情况
    13. }

    【C/C++特有高级操作】 

    1. #define MS(n) (?MemArr[n]:MemArr[n]=MemorySearch(n)) //记忆化的宏函数,使代码结构简单清晰
    2. int MemArr[1001]; //用于记忆的数组在内存的要求之下尽可能开大
    3. int MemorySearch(int n) //n:第n项
    4. {
    5. if()return ; //直接返回数字的情况
    6. else if()return MS()+MS()+...; //递归调用的情况
    7. else ...; //其他类似的情况
    8. }

    【举个栗子】

            现在要我们求斐波那契数列的第20项,读者不妨先用递归试试!再用上面的记忆化搜索的模板试试!

            记忆化搜索一般解法

    1. #include
    2. using namespace std;
    3. int MemArr[1000]; //记忆数组
    4. int MS(int n) //定义MS函数用于读取记忆和存储数据
    5. {
    6. int MemorySearch(int n); //声明搜索函数
    7. if(!MemArr[n])MemArr[n] = MemorySearch(n); //如果MemArr[n]的值不满足条件,说明MemArr[n]还未存储数据,则计算其应有的值并赋值给MemArr[n]
    8. return MemArr[n]; //返回MemArr[n]的值
    9. }
    10. int MemorySearch(int n) //n:第n项
    11. {
    12. if(n==1)return 0; //斐波那契数列第1项
    13. else if(n==2)return 1; //斐波那契数列第2项
    14. else return MS(n-1)+MS(n-2); //斐波那契数列其他项
    15. }
    16. int main(){
    17. cout<<MemorySearch(20); //输出斐波那契数列第20项
    18. return 0;
    19. }

            记忆化宏函数解法

    1. #include
    2. #define MS(n) (MemArr[n]?MemArr[n]:MemArr[n]=MemorySearch(n)) //记忆化的宏函数
    3. using namespace std;
    4. int MemArr[1001]; //记忆数组
    5. int MemorySearch(int n) //n:第n项
    6. {
    7. if(n==1)return 0; //斐波那契数列第1项
    8. else if(n==2)return 1; //斐波那契数列第2项
    9. else return MS(n-1)+MS(n-2); //斐波那契数列其它项
    10. }
    11. int main()
    12. {
    13. cout<<MemorySearch(20); //输出斐波那契数列第20项
    14. return 0;
    15. }

    【练手好题】

    【洛谷】P1464 Function


    【都看到这里了,点个赞再走吧!】

  • 相关阅读:
    【GAMES202】A Glimpse of Industrial Solution—实时渲染中常用的工业界技术
    优化gin表单的错误提示信息
    # sudo和su
    有创意且简美大气的图表-堆叠极扇图
    京东按关键字搜索商品 API
    双十一买什么蓝牙耳机?价廉物美的蓝牙耳机推荐
    linux本地yum源配置
    2023二建建筑施工备考第二天Day03
    linux内存空间深度清理
    跟羽夏学 Ghidra ——工具
  • 原文地址:https://blog.csdn.net/weixin_62651706/article/details/125952752