• 算法


    递归

    递归有两大特性:
    
      1.自己调用自己。
    
      2.有结束条件。
    
      满足了以上两点的函数就是递归函数。

    实例1:

    def func(x):
        if x > 0:
            print(x)
            func(x - 1)
    
    
    func(3)  # 3,2,1

    图解:

     实例2:

    def func1(x):
        if x > 0:
            func1(x - 1)
            print(x)
    
    
    func1(3)  # 1,2,3

    图解:

    实例3:
     一段有n个台阶组成的楼梯,小明从楼梯的最底层向最高处前进,它可以选择一次迈一级台阶或者一次迈两级台阶。
    问:他有多少种不同的走法?
    分析:
    # 如果最后一步走一个台阶,一共有f(n-1)中走法
    # 如果最后一步走两个台阶,一共有f(n-2)中走法
    # 由此推出f(n)=f(n-1)+f(n-2)
    代码实现:
     def t1(n):
         if n==1:
             return 1
         elif n==2:
             return 2
         return t1(n-1)+t1(n-2)

    如果可以走三个台阶
     def t2(n):
         if n == 1:
             return 1
         elif n == 2:
             return 2
         elif n == 3:
             return 4
         return t2(n - 1) + t2(n - 2) + t2(n - 3)
    铺砖:
    有一块长度为N米,宽度为1米的地需要铺砖,现在有两种规则的砖可以使用,1*1,1*2,一共有多少种铺法。和上面解决方法一样
    汉诺塔问题
    问题背景:

    有三个柱子,在一根柱子上从下往上按照大小顺序摞着N片黄金圆盘,
    把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。
    在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
    解题思路:
    n个盘子时:
    1.把n-1个圆盘从A经过C移动到B
    2.把第n个圆盘从A移动到C
    3.把n-1个小圆盘从B经过A移动到C
    代码实现:(递归)
    def hnt(n, A, B, C):  # 将n个圆盘从A经过B移动到C。
        if n > 0:
            hnt(n - 1, A, C, B)  # 将n-1个圆盘从A经过C移动到B。
            print("从%s移动到%s" % (A, C))
            hnt(n - 1, B, A, C)  # 将n-1个圆盘从B经过A移动到C。
    
    
    hnt(3, "A", "B", "C")
    汉诺塔移动次数的递推式:h(x)=2h(x-1)+1,x是圆盘个数
    斐波那锲数列
    斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,.......
    这个数列从第3项开始,每一项都等于前两项之和。

    递归实现
    def fib(n):  # 默认从0开始
        if n==0 or n==1:
            return 1
        return fib(n-1)+fib(n-2)
    
    print(fib(5))
    字典实现
    def fib(n):
        l=[1,1]
        if n == 0 or n == 1:
            return 1
        for i in range(2,n+1):
            l.append(l[-1]+l[-2])
        return l[-1]
    
    print(fib(1))
    通过赋值实现
    def fib(n):
        if n == 0 or n == 1:
            return 1
    
        a = 3  # a刚开始赋的第一个值可以使任意数字,为了占据一个位置,方便后续变量向前赋值
        b = 1
        c = 1
        for i in range(2, n + 1):
            a = b
            b = c
            c = a + b
        return c
    
    
    print(fib(5))
    
    # 以上的核心思想都是递归 func(n)=func(n-1)+func(n-2)

     二分查找

    循环实现
    def bin_search(l, val):
        low = 0  # low和high,mid指的是索引
        high = len(l) - 1
        while high >= low:
            mid = (low + high) // 2  # 整除2
            if l[mid] > val:
                high = mid - 1
            elif l[mid] < val:
                low = mid + 1
            else:
                return l[mid]
        return "列表%s中没有%s这个值" % (l, val)
    
    
    l = [1, 2, 3, 4, 5, 6]
    print(bin_search(l, 5))
     递归实现,效果不好
    def bin_search_rec(data_set, value, low, high):
        if low <= high:
            mid = (low + high) // 2
            if data_set[mid] == value:
                return data_set[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 "列表%s中没有%s这个值" % (l, value)
    
    l = [1, 2, 3, 4, 5, 6]
    print(bin_search_rec(l,9,0,4))

    排序LB三人组:

    # 时间复杂度:O(n2)
    # 空间复杂度:O(1)
    检测性能的导入部分
    import random
    from test_time import cal_time
    检测时间装饰器
    import time
    
    
    def cal_time(func):
        def inner(*args, **kwargs):
            t1 = time.time()
            result = func(*args, **kwargs)
            t2 = time.time()
            print("%s running time:%s秒" % (func.__name__, t2 - t1))
            return result
    
        return inner

    冒泡排序

    # 简单冒泡
    # 思路
    #列表每两个相邻的数,如果前边的比后边的大,那么交换这两个数代码关键点:分清有序区和无序区
    @cal_time
    def bubble_sort(li):
        for i in range(len(li) - 1):  # i 表示第i次冒泡
            for j in range(len(li) - i - 1):  # j 表示索引位置
                if li[j] > li[j + 1]:  # 如果前面的值比后面大,那么这两个数换个位置
                    li[j], li[j + 1] = li[j + 1], li[j]
    
    # 优化冒泡
    @cal_time
    def bubble_sort_2(li):
        for i in range(len(li) - 1):  # i 表示第i次冒泡
            exchange = False
            for j in range(len(li) - i - 1):  # j 表示索引位置
                if li[j] > li[j + 1]:  # 如果前面的值比后面大,那么这两个数换个位置
                    li[j], li[j + 1] = li[j + 1], li[j]
                    exchange = True
            # 如果某一次冒泡期间没有元素互换位置,说明后面的元素已经是有序的了,可以直接返回
            if not exchange:
                return   
    插入排序
    思路
    # 列表被分为有序区和无序区两个部分。最初有序区只有一个元素。每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空。
    # 代码关键点:摸到的牌,手里的牌
    # 何时插入?
        1.j 位置的值小于tmp
        2.j=-1 此时tmp的值在当前的有序区里是个极限值
        li[j+1]=tmp 
    代码实现
    @cal_time
    def insert_sort(li):
        for i in range(1, len(li)):
            j = i - 1  # i 为要插入数的索引,j 为要插入的位置
            tmp = li[i]
            while j >= 0 and li[j] > tmp:  # 1,2 的反向条件成立,循环继续
                li[j + 1] = li[j]  # j位置的值向右移动一个位置
                j -= 1  # 要插入的位置向前移动一个
            li[j + 1] = tmp  # 不管条件1还是条件2成立,都是讲tmp插入到j+1的位置
    选择排序
    思路
    # 一趟遍历记录最小的数,放到第一个位置;再一趟遍历记录剩余列表中最小的数,继续放置;
    ……
    
    # 代码关键点:无序区
    最小数的位置
    代码实现
    # 找到无序区最小的数,放到最前面,变成有序区第一个数
    def find_min(li):
        min_val = li[0]  # 默认第一个数为最小的数
        for i in range(1, len(li)):  # 排除第一个数,所以循环从索引1开始
            if li[i] < min_val:
                min_val = li[i]
        return min_val
    
    # 找到无序区最小的数的位置
    def find_min_pos(li):
        min_pos = 0  # 默认第一个数为最小的数的位置
        for j in range(1, len(li)):
            if li[j] < li[min_pos]:
                min_pos = j
        return min_pos
    
    # 进行排序
    @cal_time
    def select_sort(li):
        for i in range(len(li) - 1):  # 最后一个数肯定是个极限值,不用选择,循环len(li)-1次即可
            # 无序区的范围[i,len(li)]  i 循环第i次
            min_pos = i
            for j in range(i + 1, len(li)):
                if li[j] < li[min_pos]:
                    min_pos = j  # 将j的位置赋值给min_pos
            li[i], li[min_pos] = li[min_pos], li[i]

    检测性能打印部分:

    li = list(range(1000))
    random.shuffle(li)  # 打乱顺序
    bubble_sort(li)
    bubble_sort_2(li)
    insert_sort(li)
    select_sort(li)
    # print(li)

     排序NB三人组

    检测性能导入部分
    import sys
    import random
    from test_time import cal_time
    sys.setrecursionlimit(1000)

    快排
    思路
    # 取一个元素p(第一个元素),使元素p归位;
    # 列表被p分成两部分,左边都比p小,右边都比p大;
    递归完成排序。
    
    # 代码关键点:整理,递归
    第一部分:整理
    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)
    测试时间的函数(可以不写)
    # 加上装饰器,检测时间,里面有递归,函数结构要改变
     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)
    
    
     @cal_time
     def quick_sort(li):
         _quick_sort(li, 0, len(li)-1)
    第二部分:递归
    def partition(li, left, right):  # 递归
    
        # 下面两行代码是优化用的,可以减少最坏情况出现的概率
        # 最坏情况,我们排序的结果和给的数据完全相反
        i = random.randint(left, right)
        li[left], li[i] = li[i], li[left]
    
        tmp = li[left]
        while left < right:
            # 目前是从小到大排序,从大到小的话,只需要将下面的两个>=,<=换个位置即可
            while left < right and li[right] >= tmp:
                right -= 1
            li[left] = li[right]
            while left < right and li[left] <= tmp:
                left += 1
            li[right] = li[left]
        # 无论从哪边结束,赋值操作都是一样的,此时li[left]和li[right],写那个都一样
        li[left] = tmp
        return left
    打印测试时间
    #系统排序(可以不写)
    @cal_time
    def sys_sort(li):
         li.sort()
    
    li = list(range(100000, -1, -1))
    random.shuffle(li)
    quick_sort(li, 0, len(li)-1) # 不写装饰器时时函数的调用
    quick_sort(li) # 写装饰器时函数的调用
    
    # print(li)
    堆排
    树与二叉树简介
    # 树是一种数据结构          比如:目录结构
    树是一种可以递归定义的数据结构
    树是由n个节点组成的集合:
    如果n=0,那这是一棵空树;
    如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。
    # 一些概念
    根节点、叶子节点
    树的深度(高度)
    树的度
    孩子节点/父节点
    子树

    特殊且常用的树——二叉树

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

    两种特殊二叉树

    满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。

    完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。

    二叉树的存储方式

    # 链式存储方式
    # 顺序存储方式(列表)
    
    # 父节点和左孩子节点的编号下标有什么关系?
    0-1 1-3 2-5 3-7 4-9
    i=2i+1
    
    # 父节点和右孩子节点的编号下标有什么关系?
    0-2 1-4 2-6 3-8 4-10
    i=2i+2
    # 父节点和子节点的编号下标的对应关系:i=(子节点下标-1)//2
    
    

     堆前传小结

    树二叉树完全二叉树
    
    二叉树是度不超过2的树
    满二叉树与完全二叉树
    (完全)二叉树可以用列表来存储,通过规律可以从父亲找到孩子或从孩子找到父亲。

    堆排序

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

                      

    堆的向下调整性质

    假设:节点的左右子树都是堆,但自身不是堆

     当根节点的左右子树都是堆时,可以通过一次向下的调整来将其变换成一个堆

    堆排序过程

    建立堆
    得到堆顶元素,为最大元素
    去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序。
    堆顶元素为第二大元素。
    重复步骤3,直到堆变空

    堆排序代码

    # 堆排(核心:调整)
    def sift(li, low, high):
        # low 表示根位置    high 表示最后元素的位置
        tmp = li[low]
        i = low # i指向空位
        j = 2 * i + 1 # j指向孩子
        # 把tmp写回来有两种条件 1. tmp > li[j]  2. j位置没有值 (也就是i已经是叶子了)
        while j <= high:    # 对应退出条件2
            if j + 1 <= high and li[j+1] > li[j]: # 如果右孩子存在且右孩子更大
                j += 1  # j指向更大的孩子
            if li[j] > tmp:
                li[i] = li[j]
                i = j
                j = 2 * i + 1
            else:       # 对应退出条件1
                break
        li[i] = tmp # 不管哪个条件退出,i这个位置都是空位,将根位置的值赋值给i
    
    @cal_time
    def heap_sort(li):
        n = len(li)
        # 1. 建立堆  low在不断变化
        for low in range(n//2-1, -1, -1):  # n//2-1-->n-1-1//2
            sift(li, low, n-1)  # low表示每个小堆的堆顶,n-1表示堆的最后一个元素的下标
        # print(li)
    
        # 2. 挨个出数 退休-棋子-调整
        # high在不断变化,0号位置不停的和high位置在做交换
        for high in range(n-1, -1, -1):
            li[0], li[high] = li[high], li[0]
            sift(li, 0, high-1) # 每调整一次,元素个数就少1
            # print(li)
    
    
    li = list(range(100000))
    random.shuffle(li)
    heap_sort(li)
    print(li)
    内置堆
    import heapq
    
    li = [2,5,7,8,9,6,1,4,3]
    heapq.heapify(li)
    print(li)
    heapq.heappush(li, 0)
    print(li)
    print(heapq.heappop(li))
    print(heapq.heappop(li))
    
    print(heapq.nlargest(5, li))
    print(heapq.nsmallest(5, li))

    归并

    思路

    # 假设现在的列表分两段有序,如何将其合成为一个有序列表,这种操作称为一次归并
    
    # 分解:将列表越分越小,直至分成一个元素
    终止条件:一个元素是有序的
    合并:将两个有序列表归并,列表越来越大
    时间复杂度:O(nlogn)
    空间复杂度O(n)

    将一个列表分成两段有序的列表

    # 假设现在列表分两段有序,将其合成一个有序列表
    def merge(li, low, mid, high):
        li_tmp = []  # 定义一个空列表,用来放置排序后的值
        i = low  # li[low:mid+1] 可以表示前半部分的有序列表
        j = mid + 1  # li[mid+1:high+1] 可以表示后半部分的有序列表
        while i <= mid and j <= high:  # 表示前后两部分都有值
            if li[i] <= li[j]:
                li_tmp.append(li[i])
                i += 1
            else:
                li_tmp.append(li[j])
                j += 1
    
        while i <= mid:  # 下面这两个while循环语句只会有一个执行
            li_tmp.append(li[i])
            i += 1
        while j <= high:
            li_tmp.append(li[j])
            j += 1
    
        for i in range(len(li_tmp)):
            li[low + i] = li_tmp[i]  # 将排好序的列表赋值回原来的列表

    归并排序

    def merge_sort(li, low, high):  # 归并排序
        if low < high:  # 两个以上元素
            mid = (low + high) // 2
            merge_sort(li, low, mid)
            merge_sort(li, mid + 1, high)
            # print(li[low:mid+1], li[mid+1:high+1])
            merge(li, low, mid, high)
            # print(li[low:high + 1])

    加装饰器的归并排序

    def _merge_sort(li, low, high):
        if low < high:  # 两个以上元素
            mid = (low + high) // 2
            _merge_sort(li, low, mid)
            _merge_sort(li, mid + 1, high)
            # print(li[low:mid+1], li[mid+1:high+1])
            merge(li, low, mid, high)
            # print(li[low:high + 1])
    
    
    @cal_time
    def merge_sort(li):
        _merge_sort(li, 0, len(li) - 1)

    函数的调用

    li = list(range(100000))
    random.shuffle(li)
    merge_sort(li,0,len(li)-1)  # 不带装饰器的调用
    # merge_sort(li)
    print(li)
    总结
    LB三人组总结
    时间复杂度都是O(n2)
    空间复杂度都是O(1)
    
    
    NB三人组总结
    三种排序算法的时间复杂度都是O(nlogn)
    一般情况下,就运行时间而言:快排<归并<堆排
    
    三种算法的缺点:
    快排:极端情况下排序效率低
    归并:需要额外的内存开销(内存栈的开销)
    堆排:在排序算法中相对较慢

    希尔排序

    # 思路
    
    # 希尔排序是一种分组插入排序算法。
    首先取一个整数d1=n/2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进行直接插入排序;
    取第二个整数d2=d1/2,重复上述分组排序过程,直到di=1,即所有元素在同一组内进行直接插入排序。
    
    # 希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。

     代码实现

    import random
    from timewrap import *
    
    
    def insert_sort_gap(li, d):
        for i in range(d, len(li)):
            # i 表示无序区第一个数
            tmp = li[i] # 摸到的牌
            j = i - d # j 指向有序区最后位置
            while j >= 0 and li[j] > tmp:
                #循环终止条件: 1. li[j] <= tmp; 2. j == -1
                li[j+d] = li[j]
                j -= d
            li[j+d] = tmp
    
    @cal_time
    def shell_sort(li):
        d = len(li) // 2
        while d > 0:
            insert_sort_gap(li, d)
            d = d // 2
    
    li = list(range(10000))
    li.sort()
    random.shuffle(li)
    #print(li)
    shell_sort(li)

    计数排序

    现在有一个列表,已知列表中的数范围都在0到100之间。设计算法在O(n)时间复杂度内将列表进行排序。

    创建一个列表,用来统计每个数出现的个数

    代码实现

    def count_sort(li, max_num):
    count = [0 for i in range(max_num + 1)]
    for num in li:
    count[num] += 1
    i = 0
    for num,m in enumerate(count):
    for j in range(m):
    li[i] = num
    i += 1

    桶排序

    在计数排序中,如果元素的范围比较大(比如在1到1亿之间),如何改造算法?
    桶排序(Bucket Sort):首先将元素分在不同的桶中,在对每个桶中的元素排序。
    
    
    桶排序的表现取决于数据的分布。也就是需要对不同数据排序时采取不同的分桶策略。
    平均情况时间复杂度:O(n+k)
    最坏情况时间复杂度:O(n2k)
    空间复杂度:O(nk)

     基数排序

    # 多关键字排序:加入现在有一个员工表,要求按照薪资排序,薪资相同的员工按照年龄排序。
    # 先按照年龄进行排序,再按照薪资进行稳定的排序。
    对32,13,94,52,17,54,93排序,是否可以看做多关键字排序?

     代码实现

    import random
    from timewrap import cal_time
    
    def int2list(num):
        li = []
        while num > 0:
            li.append(num % 10)
            num = num // 10
        li.reverse()
        return li
    
    
    def get_digit(num, i):
        return num // (10 ** i) % 10
    
    
    def list_to_bucket(li, i):
        buckets = [[] for _ in range(10)]
        for val in li:
            digit = val // (10 ** i) % 10
            buckets[digit].append(val)
        return buckets
    
    def bucket_to_list(buckets):
        li = []
        for bucket in buckets:
            for val in bucket:
                li.append(val)
        return li
        # return [val for bucket in buckets for val in bucket]
    
    @cal_time
    def radix_sort(li):
        max_val = max(li) # 10000
        i = 0
        while 10 ** i <= max_val:
            li = bucket_to_list(list_to_bucket(li, i))
            i += 1
        return li
    
    @cal_time
    def sys_sort(li):
        li.sort()
    
    
    # 0 10000000000000000000000000000
    li = list(range(100000))
    random.shuffle(li)
    #sys_sort(li)
    
    radix_sort(li)

    时间复杂度:O(kn)

    空间复杂度:O(k+n) (k表示数字位数)

  • 相关阅读:
    GO学习-(31) Go语言操作Elasticsearch
    GO学习-(30) Go语言操作kafka
    GO学习-(29) Go语言操作etcd
    Maven+Spring打Jar包
    话说Session
    Apache Server与多个独立Tomcat集成
    一套Tomcat处理多个域名请求
    Tomcat多实例
    Linux下安装MySQL
    Java Enum
  • 原文地址:https://www.cnblogs.com/daofaziran/p/10090706.html
Copyright © 2020-2023  润新知