• 算法基础


    算法

    算法(Algorithm):一个计算过程,解决问题的方法。

    时间复杂度

    时间复杂度是用来估计算法运行时间的一个式子(单位)。

    当循环少一半的时候 时间复杂度O(logn)
    几次关于n的循环就是n的几次方的复杂度

    print('Hello World')           #假如说这行代码运行时间是一个单位O(1)
    
    for i in range(n):             # 这段代码的时间是O(n),因为执行了n次
        print('Hello World')   
    
    for i in range(n):             # 这段代码是O(n*n),因为在执行了n*n次
        for j in range(n):        
            print('Hello World')
    
    for i in range(n):             #这代码是O(n*n*n),执行了n的立方次
        for j in range(n):       
            for k in range(n):          
                print('Hello World')

    一般来说,时间复杂度高的算法比复杂度低的算法慢。

    常见的时间复杂度(按效率排序): O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)

    空间复杂度

    空间复杂度:用来评估算法内存占用大小的一个式子

    空间换时间:多给它一些空间或内存,让它运行速度更快

    递归

    特点:

    • 1.调用自身
    • 2.有结束条件
    def func(x):    
        if x>0:       
            print(x)       
            func(x-1)
    print(4)
    # 打印结果 4 3 2 1
    #因为先打印再递归
    def func(x):
        if x > 0:
            func(x-1)
            print(x)
    func(4)
    # 打印结果 1 2 3 4
    # 因为先递归,再打印

    那么递归前先打印,和递归后再打印有什么不同?

     

    如果先递归再打印,那么打印的内容先不被执行,直到递归跳出的时候才从内到外的进行打印.

    打印抱着抱着抱着我的小可爱的我的我的我

    def text(n):
        if n>0:
            print('抱着',end='')
            text(n-1)
            print('的我',end='')
    
        else:
            print('我的小可爱',end='')
    
    text(3)

    二分查找

    关键点:

    候选区data[0:n]

    def bin_search(li, val):         # li是传入的列表 val是要查找的值
        low = 0                      # low是起始的索引值
        high = len(li) - 1           # high是末尾的索引值
        while low <= high:           # 满足起始索引小于末尾索引的条件就执行循环
            mid = (low + high) // 2  # mid是列表的中间数的索引
            if li[mid] == val:       # 正好找到要查找的值的索引
                return mid
            elif li[mid] < val:      # 中间数的值小于被查找的值
                low = mid + 1        # 说明val在中间数的右边
            else:
                high = mid - 1       # 说明val在中间数的左边

    尾递归二分查找:

    def bin_search_rec(data_set, value, low, high):
        if low <= high:
            mid = (low + high) // 2
            if data_set[mid] == value:
                return mid
            elif data_set[mid] > value:
                return bin_search_rec(data_set, value, low, mid - 1)
            else:
                return bin_search_rec(data_set, value, mid + 1, high)
        else:
            return

    二分查找:从有序列表的候选区data[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半。

    输出查询的列表值的下标

    既然二分查找那么快为什么不全部都用二分查找呢? 

    因为二分查找的前提是列表是有序的,那么如何让无序的列表变成有序才是最大的问题.

    列表排序

    冒泡排序

    冒泡排序:两层遍历,相邻的两个值,如果左边的数大于右边的数,则交换位置。

    def bubble_sort(li):
        '''冒泡排序'''
        for i in range(0,len(li)-1):
            exchange=False
            for j in range(0,len(li)-i-1):  # 设置终点值
                if li[j]>li[j+1]: # 判断是否大于
                    li[j],li[j+1]=li[j+1],li[j] # 交换位置
                    exchange=True  # 优化,如果不为true说明了没有进行交换,列表是有序的
            if not exchange:
                return
    li=list(range(0,10000))
    random.shuffle(li)
    bubble_sort(li)
    print(li)

    时间复杂度为O(n**2)

    选择排序

    选择排序:遍历一趟记录最小的数,放到第一位。再遍历剩下的数,找的最小的数,继续放置。

    关键点:无序区和最小数的位置。

    def select_sort(li):
        '''
        选择排序,比较无序区最小的一个,放在有序曲后一个
        :param li: 列表
        :return:
        '''
        for i in range(0,len(li)-1):
            min_local=i
            for j in range(i+1,len(li)):
                if li[min_local] > li[j]:
                    min_local=j
            li[i],li[min_local]=li[min_local],li[i]
    
    li=list(range(0,10000))
    random.shuffle(li)
    select_sort(li)
    print(li)

    时间复杂度为O(n**2)

    插入排序

    列表被分为有序区和无序区两个部分。最初有序区只有一个元素。

    每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空。

    代码关键点:如何找到无序区数,如何插到有序区中。

    def inser_sort(li):
        '''插入排序,
            从i开始表示摸牌的位置,
            与前面的数进行相比,如果比摸的牌大则挪一位'''
        for i in range(1,len(li)):
            j=i-1
            temp=li[i]
            while j>=0 and li[j]>temp:
                li[j+1] = li[j]
                j-=1
            li[j+1]=temp
    
    li=list(range(0,10000))
    random.shuffle(li)
    inser_sort(li)
    print(li)

    类似于摸牌然后插入

    时间复杂度为O(n**2)

    快速排序

    快速排序:取一个元素p(第一个元素),使元素p归位,列表被p元素分为两部分,左边都比p小,右边都比p大,然后递归完成排序。

    import random
    def partition(li, left, right):
        i=random.randint(left,right)
        li[left],li[i] = li[i],li[left]
        tem=li[left]
        while left < right:  # 列表里至少两个元素才满足这个条件
            while left < right and li[right] >= tem:  # 从右边边找小于tem的数
                right -= 1      # 如果不小于,继续找
            li[left]= li[right] # 找到比tem小的数,挪到左边
            while left < right and li[left] <= tem:  # 从左边找比tem大的数
                left += 1
            li[right]= li[left]
        li[left] = tem
        return left  # 这里返回left和right是一样的
    def quick_sort(li, left, right):
        if left < right:  # 列表里至少两个元素才满足这个条件
            mid = partition(li, left, right)
            quick_sort(li, left, mid-1)  # 递归
            quick_sort(li, mid+1, right)  # 递归
    
    li=list(range(10000,0,-1))
    random.shuffle(li)
    
    ret=quick_sort(li,0,len(li)-1)
    print(li)

    快速排序的时间复杂度 为O(nlogn)

    堆排序

    知识点介绍

    • 根节点、叶子节点
      • 没有父亲的节点叫根节点,比如A, 没有孩子的节点叫叶子节点,比如BCHPQ等等
    • 树的深度
      • 有多少层,图示是4层
    • 树的度
      • 树的度势节点度的最大值比如A的度是6,E的度是2,F的度是3
    • 孩子节点/父节点
    • 子树
      • 在同一个分支上的是子树,只有一个的也是子树

    二叉树

    度不超过2的树(节点最多有两个)

    • 满二叉树:一个二叉树
      • 如果每一层的节点都达到了最大值,则这个二叉树就是满二叉树
    • 完全二叉树
      • 叶节点只能出现在最下层和次下层,并且最下层的节点都集中在最左边的若干位置的二叉树

    父亲节点和左孩子节点有什么位置关系?

    0-1 1-3 2-5 3-7 4-9

    i=2i+1

    右父亲节点和孩子节点有什么位置关系?

    0-2 1-4 2-6 3-8 3-10

    i=2i+2

    • 大根堆
      • 一颗完全二叉树,满足任一节点都比孩子大
    • 小根堆
      • 一颗完全二叉树,满足任一节点都比孩子小

     

    如何构建一个堆

    挨个出数

    • 结果就是成立一个新的堆

     再然后就是把8放到有序列表中,把最后的子节点替代根节点然后向下调整

    堆排序的代码 

    def sift(li,low,high):
        temp=li[low] #根节点
        i=low
        j=2* i + 1      # i是他的孩纸坐标
        while j <= high:
            if j < high and li[j+1]>li[j]: #如果右孩子存在,且右孩子的值大于左孩子
                j+=1
    
            if temp<li[j]:     #如果根节点的值小于他的孩子
                li[i] = li[j]  # 将孩子放置到根节点上
                i=j            #更新i的坐标值
                j= 2* i +1     #更新i的坐标值
    
            else:           # 如果根节点大于两个孩子节点
                li[i]=temp  #放置在根节点上 结束.
                break
        else:
            li[i] = temp     # 第二种跳出情况,temp找到自己的位置跳出,放置到i
    
    
    def heap_sort(li):
        n=len(li)
        # 1.建堆的过程
        for i  in range(n//2-1,-1,-1): # 最后一个非叶子节点的位置是n//2 -1
            sift(li,i,n-1)
        # 2. 诶个出数
        for i in range(n-1,-1,-1):
            li[0],li[i]=li[i],li[0]
            sift(li,0,i-1)
    
    import random
    li=list(range(0,100000))
    random.shuffle(li)
    heap_sort(li)
    print(li)

     归并排序

    归并算法的核心就是拆分列表,在合并的过程中进行排序

    def merge(li, low, mid, high):  # high是右边片段最后一个数的索引
        li_tem = []
        i = low  # low是左边片段第一个数的索引
        j = mid + 1  # mid是左边片段最大的数的索引
        while i <= mid and j <= high:  # 如果左右两个片段都有值,就继续取值。只要其中一个片段没值,就跳出循环。
            if li[i] < li[j]:  # 如果左边片段的最小值小于右边的最小值
                li_tem.append(li[i])  # 就将最小的那个值取出来放到一个列表中。
                i += 1  # 然后继续比较剩下的最小值
            else:  # 反之,就是右边的最小值小于左边的最小值
                li_tem.append(li[j])
                j += 1  # 然后继续比较剩下的最小值
        # 跳出第一个while循环的条件是:如果左边的片段取完了,其索引i不小于mid了
        # 如果右边的片段取完了,其索引j就不小于high了
        # 下面的两个while循环只可能有一个执行
        while i <= mid:  # 这里是左偏片段还有值
            li_tem.append(li[i])
            i += 1  # 继续添加到列表中
        while j <= high:  # 这里是右边片段还有值
            li_tem.append(li[j])
            j += 1  # 继续添加到列表中
        li[low:high+1] = li_tem
    
    
    def merge_sort(li, low, high):
        if low < high:  # 保证列表至少有两个元素
            mid = (low + high) // 2  # 取中间数的索引
            merge_sort(li, low, mid)  # 将左边的通过递归编程有序列表
            merge_sort(li, mid+1, high)  # 将右边的通过递归编程有序列表
            merge(li, low, mid, high)  # 将左右两个片段合并成一个有序列表
    
    
    li = [2, 5, 7, 8, 9, 1, 3, 4, 6, 10]
    merge_sort(li, 0, len(li)-1)
    print(li)

    后三种算法总结

    三种排序算法的时间复杂度都是O(nlogn)

    一般情况下,就运行时间而言:

    • 快速排序<归并排序<堆排序

    三种排序算法的缺点:

    • 快速排序:极端情况下,排序效率低
    • 归并排序:需要额外的内存开销
    • 堆排序:在快的排序算法中相对较慢

    那么稳定与不稳定是什么呢?

    稳定的排序是指在排序相同数字的时候不打乱原有的顺序,

    比如[ (1,'alex') ,(1,'egon'),(2,'ming')  ]

    在排序的时候不打乱原有的数据顺序.

    [ (1,'egon'),(1,'alex') ,,(2,'ming')]如果不确定是否会打乱原有顺序的则是不稳定的.

    相邻比较的都是稳定的.

  • 相关阅读:
    js问题记录
    css问题记录
    vscode配置java+gradle开发环境
    js插件
    nginx笔记
    vue刷新当前路由
    koa踩坑记录
    react踩坑笔记
    ts踩坑笔记
    vue源码阅读笔记
  • 原文地址:https://www.cnblogs.com/chenxuming/p/9463364.html
Copyright © 2020-2023  润新知