• 算法


     算法

     时间复杂度

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

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

      常见的时间复杂度(安效率排序):

        O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n2logn) < O(n3)

      不常见的时间复杂度:

        O(n!) < O(2n) < O(nn)

    print('Hello World')  # 时间复杂度O(1)

      O(1)  O大约  1表示一个单位

    for i in range(n):
        print('Hello World')
    # 时间复杂度O(n)

      

    for i in range(n):
        for j in range(n):
            print('Hello World')
    # 时间复杂度O(n2)
    for i in range(n):
        for j in range(n):
            for k in range(n):
                print('Hello World')
    # 时间复杂度O(n3)
    print('Hello World')
    print('Hello Python')
    print('Hello Algorithm')
    # 时间复杂度O(1)
    for i in range(n):
        print('Hello World')
        for j in range(n):
            print('Hello World')
    # 时间复杂度O(n2)  根据打印的次数进行判断:n2 + n
    for i in range(n):
        for j in range(i):
            print('Hello World')
    # 时间复杂度O(n2)  根据打印的次数进行判断:0 + 1 + 2 + ... + (n-1)

      当两个代码的时间复杂度分别为O(n)与O(n2),谁的运行速度快?

        不一定。当n比较小的时候,不一定谁比较快。一般情况下,我们研究的是n足够大的时候,n足够大,O(n)运行速度快。

      当两段代码的时间复杂度都为O(n2)时,谁的运行速度快?

        不一定。因为其中还牵扯到一个常数项,但是我们计算时间复杂度时,都将常数项忽略了。

      当出现循环折半的时候,代码的时间复杂度就为O(logn).

    while n > 1:
        print(n)
        n = n // 2
    # 时间复杂度O(logn)

      如何判断时间复杂度?

        循环减半的过程:O(logn);

        几次循环就是n的几次方的复杂度。

    空间复杂度

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

      使用一个变量。空间复杂度O(1)

      使用了一个长度为n的列表。空间复杂度O(n)

      使用了n个长度为n的列表。空间复杂度O(n2)

      空间换时间

      一般不考虑空间复杂度,除非代码特别复杂,会吃爆内存或者有要求时考虑。

    递归

      递归的两个特点:

        调用自身

        结束条件

      假设x为3:

    def func(x):
        if x > 0:
            print(x)
            func(x-1)


    func(3)
    ''' 输出结果: 3 2 1 '''

      递归调用图解:

    def func(x):
        if x > 0:
            func(x-1)
            print(x)


    func(3)
    ''' 输出结果: 1 2 3 '''

     递归调用图解:

     斐波那契

      求斐波那契第n项

      1 1 2 3 5 8 13

      # 假设小规模的问题能解决的条件下,能设计步骤解决原问题

      # 当n比较大时就比较慢,重复计算子问题导致的。

      递归写法:

    def fib(n):
    '''
    递归写法
    '''
        if n == 0 or n == 1:
            return 1
        else:
            return fib(n-1) + fib(n-2)
    
    print(fib(10))

       依次调用原函数,给传入的数一次减一,直至递归找到第0项或者第1项。时间复杂度O(2n)

       不重复计算子问题写法:

    def fib(n):
    '''
    不重复计算子问题
    '''
        res = [1, 1]
        for i in range(2, n+1):
            res.append(res[-1] + res[-2])
         return res[-1]
    
    print(fib(100))

       将第0项和第1项放入列表,然后一次循环,每次取列表的后两项求和存入列表,循环n次,最后取最后一项即为第n个数。时间复杂度O(n) 空间复杂度O(n)

      没有空间复杂度写法:

    def fib(n):
        if n == 0 or n == 1:
            return 1
        a = 1
        b = 1
        c = 2  # 前两项之和
        for i in range(n, n+1):
            c = a + b
            a = b
            b = c
        return c
    
    print(fib(100))

       就是重复赋值。

    汉诺塔问题

    def hanoi(n, A, B, C):
        if n > 0:
            hanoi(n-1, A, C, B)
            print(f'{A}->{C}')
            hanoi(n-1, B, A, C)
    
    hanoi(3, 'A', 'B', 'C')

     习题

      一段有n个台阶组成的楼梯,小明从楼梯的底层向最高层前进,他可以选择一次迈一级台阶或者一次迈两级台阶。问:他有多少种不同的走法?

    列表查找

    列表查找

      从列表中查找指定元素。

      输入:列表、待查找元素

      输出:元素下标或未查找到元素

    顺序查找

      从列表第一个元素开始,顺序进行搜索,直到找到为止。

    二分查找

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

      非递归写法:

    def bin_search(li, val):
        low = 0
        high = len(li) - 1
        while low <= high:  # 候选区有值
            mid = (low + high) // 2
            if li[mid] == val:
                return mid
            elif li[mid] > val:
                high = mid - 1
            else:
                low = mid + 1
        return -1
    
    bin_search(li, 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 - 1
    
    def bin_search_rec(data_set, value, low, high)

     排序Low B三人组

    冒泡排序

      首先,列表每两个相邻的数,如果前边的比后边的大,那么交换着两个数......

    def bubble_sort(li):
        for i in range(len(li)-i):  # i表示第i趟
            for j in range(len(li)-i-1):
                if li[j] > li[j+1]:
                    li[j], li[j+1] = li[j+1], li[j]
    
    bubble_sort(li)

      优化版:

    def bubble_sort(li):
        for i in range(len(li)-i):
            exchange = False
            for j in range(len(li)-i-1):
                if li[j] > li[j+1]:
                    li[j], li[j+1] = li[j+1], li[j]
                    exchange = True
            if not exchange:
                 break
    
    bubble_sort(li)

    时间复杂度

      O(n2)

    空间复杂度

      O(1)

    选择排序

       一趟遍历记录最小的数,放到第一个位置;再一趟遍历记录剩余列表(无序区)中最小的数,继续放置到无序区的第一个位置;依次重复,直到结束。

    def select_sort(li):
        for i in range(len(li)-1):
            # 无序区的范围 i, n-1
            min_pos = i
            for j in range(i+1, len(li)):
                if li[j] < li[min_pos]:
                    min_pos = j
            li[i], li[min_pos] = li[min_pos], li[i]
            
    select_sort()

       选择排序没有最好的情况,即使是排好序的列表,时间复杂度也是不变。

    时间复杂度

      O(n2)

    空间复杂度

      O(1)

    插入排序

       列表元素分为有序区和无序区两部分。最初有序区只有一个元素。每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空。

    def insert_sort(li):
        for i in range(1, len(li)):
            j = i - 1
            tmp = li[i]
            while j >= 0 and li[j] > tmp:  # 这两个条件最好不要写反了,Python中支持负索引,其他语言中不支持,就会直接报错程序挂掉
                li[j+1] = li[j]
                j -= 1
            li[j+1] = tmp
    
    insert_sort()

      当列表为有序时,存在最好情况。

    时间复杂度

      O(n2)

    空间复杂度

      O(1)

     NB三人组

    快速排序 

      取一个元素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)

    quick_sort(li, 0, len(li)-1)

    方式一

    def partition(li, left, right):
        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] = tmp
        return left

    方式二

    def partition(li, left, right):
        tmp = li[right]
        i = left - 1
        for j in range(left, right):
            if li[j] <= tmp:  # 归到左半部分
                i += 1
                li[i], li[j] = li[j], li[i]
            li[right], li[i+1] = li[i+1], li[right]
            return i+1

     优点

      快

    时间复杂度

      O(nlogn)

    最坏情况

      每次都选到最大或者最小的数,超过最大递归深度。

    改进

      针对方式一,如果将列表改成降序,只需要将partition中的 li[right] >= tmp 改成 li[right] <= tmp和 li[left] <= tmp 改成 li[left] >= tmp即可。

    堆排序

     树

    def sift(li, low, high):
        '''
    
        :param li:
        :param low: 堆顶
        :param high: 最后一个
        :return:
        '''
        tmp = li[low]
        i = low
        j = 2 * i + 1
        while j <= high:  # 退出循环的第二种情况:i已经是最后一层了
            if j + 1 <= high and li[j+1] > li[j]:  # 右孩子存在并且大于左孩子
                j += 1
            if tmp < li[j]:
                li[i] = li[j]
                i = j
                j = 2 * i + 1
            else:
                break  # 退出循环的第一种情况:j位置比tmp小
        li[i] = tmp
    
    
    def heap_sort(li):
        '''
        建堆
        :param li:
        :return:
        '''
        n = len(li)
        for low in range(n//2-1, -1, -1):
            sift(li, low, n-1)
            # 挨个输出,退休-棋子-调整 重复n次或者n-1次
        for high in range(n-1, -1, -1):
            li[0], li[high] = li[high], li[0]
            sift(li, 0, high-1)
    
    heap_sort()

      比快排稍微慢点,但是还是挺快的。

    时间复杂度

      sift()  O(logn)

      deap_sort()  O(n)

      整体时间复杂度:O(nlogn)

    python内置堆排

      python拥有内置的堆排序模块heapq

      优先队列:一些元素的集合,POP操作每次执行都会从优先队列中弹出最大(或最小)的元素。

      堆——优先队列

    import heapq
    
    li = [5, 7, 9, 8, 4, 1, 6, 2, 3]
    
    heapq.heapify(li)  # 建堆(小根堆)
    print(li)  # [1, 2, 5, 3, 4, 9, 6, 8, 7]  小根堆
    
    heapq.heapify(li, 0)  # 给堆添加元素, 前边是列表,后边是要添加的元素
    print(li)  # [0, 1, 2, 5, 3, 4, 9, 6, 8, 7]
    
    num = heapq.heappop(li)  # 最小的元素出来
    print(num)  # [0]

    # 前n大的数
    num1 = heapq.nlagest(5, [1, 2, 5, 4, 7, 8, 9, 6, 3]) # 5为多少个数,列表为待查询列表
    print(num1) # [9, 8, 7, 6, 5]

    # 前n小的数
    num2 = heapq.nsmallgest(5,
    [1, 2, 5, 4, 7, 8, 9, 6, 3]) # 5为多少个数,列表为待查询列表
    print(num2)  # [1, 2, 3, 4, 5]

    使用heapq模块实现堆排序

    def heapq_sort(li):
        h = []
        for value in li:
            heapqpush(h, value)
        return [heappop(h) for i in range(len(h))]
    
    heapq_sort()

    应用

    优先队列
    topK问题。(n个数中找出前k大的数)
    思路

      取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数。依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整。遍历列表所有元素后,倒序弹出堆顶。

    def topk(li, k):
        heap = li[0: k]
    
        # 建堆
        for i in range(k // 2 - 1, -1, -1):
            sift(heap, i, k - 1)
    
        # 插进去调整
        for i in range(k, len(li)):
            if li[i] > heap[0]:
                heap[0] = li[i]
                sift(heap, 0, k - 1)
    
        # 挨个出数
        for i in range(k - 1, -1, -1):
            heap(0), heap[-1] = heap[i], heap[0]
            sift(heap, 0, i - 1)
    
    
    def sift(li, low, high):
        '''
    
        :param li:
        :param low: 堆顶
        :param high: 最后一个
        :return:
        '''
        tmp = li[low]
        i = low
        j = 2 * i + 1
        while j <= high:  # 退出循环的第二种情况:i已经是最后一层了
            if j + 1 <= high and li[j+1] < li[j]:  # 右孩子存在并且小于左孩子
                j += 1
            if tmp > li[j]:
                li[i] = li[j]
                i = j
                j = 2 * i + 1
            else:
                break  # 退出循环的第一种情况:j位置比tmp小
        li[i] = tmp
    
    topk()

    归并排序

       假设现在的列表分两段有序(例如一半升序,一半降序),将其合成一个有序列表。这种操作称为一次归并。

    一次归并

    def merge(li, low, mid, high):
        i = low
        j = mid + 1
        li_tmp = []
        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: 
            li_tmp.append(li[i])
            i += 1
        while j <= high:
            li_tmp.append(li[i])
            j += 1
    for k in range(low, high + 1): li[k] = li_tmp[k - low] # li[low: high + 1] = li_tmp

    使用

      拿到一个完全无序的列表,如何使用归并呢?首先,将列表越分越小,直至分成一个元素。一个元素是有序的,再将两个有序列表归并,列表越来越大。

    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 = [10, 4, 6, 3, 8, 2, 5, 7]
    merge_sort(li, 0, len(li) - 1)
    print(li)

    时间复杂度

      O(nlogn)

    空间复杂度

      O(n)

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

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

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

      三种排序算法的缺点:

        快速排序:

          极端情况下排序效率低

        归并排序:

          需要额外的内存开销

        堆排序:

          在快的排序算法中相对较慢

      前6种算法的简单总结

      python的排序算法是Timsort。使用归并和插入算法混合实现。

    没什么人用的排序

    计数排序

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

      思想:

        建一个列表,统计里边的每个元素的个数,统计每个数出现的次数,再根据统计的结果一次输出排序的结果。

    def count_sort(li, max_num=100):
        count = [0 for i in range(max_num+1)]
        for val in li:
            count[val] += 1
        li.clear()
        for i in range(len(count)):
            for _ in range(count[i]):
                li.append(i)
    
    count_sort()

    时间复杂度

      O(n)

    希尔排序

       希尔排序是一种分组插入排序算法。首先取一个整数d1=n/2,将元素分为d1个组,魅族相邻两元素之间距离为d1,在各组内进行直接插入排序;去第二个整数d2=d1/2,重复上述分组排序过程,知道di=1,即所有元素在同一组内进行直接插入排序。

      希尔排序每趟并不是某些元素有序,而是使整体数据越来越接近有序;最后一趟使得所有的数据有序。

    def shell_insert_sort(li, d):
        for i in range(d, len(li)):
            j = i - d
            tmp = li[i]
            while j >= 0 and li[j] > tmp:
                li[j + d] = li[j]
                j -= d
            li[j + d] = tmp
    
    def shell_sort(li):
        d = len(li) // 2
        while d > 0:
            shell_insert_sort(li, d)
            d = d // 2
    
    shell_sort()

    桶排序

       在技术排序中,如果元素范围比较大(比如在1到1亿之间),name这个算法就不在适用。这时可以使用桶排序。

      桶排序:

        首先将元素分在按不同的桶中,在对每个桶(列表)中的每个元素进行排序。

      桶排序的表现取决于数据的分布。也就是需要对不同数据排序时采取不同的分桶策略。

    时间复杂度

      平均时间复杂度:O(n+k)

      最坏情况时间复杂度:O(n2k)

    空间复杂度

      O(nk)

    基数排序

      多关键字排序:

        假如现在一个员工表,要求按照薪资排序,年龄形同的员工按照年龄排序。先按照年龄进行排序,再按照薪资进行稳定的排序。

    def list_to_buckets(li, i):
        buckets = [[] for _ in range(10)]
        for num in li:
            digit = num // (10 ** i) % 10
            buckets[digit].append(num)
        return buckets
    
    
    def radix_sort(li):
        max_val = max(li)
        i = 0
        while 10 ** i <= max_val:
            li = list_to_buckets(li, i)
            i += 1
        return li
    
    radix_sort()

    时间复杂度

      O(nk)

        k表示数字位数

    空间复杂度

      O(k + n)

        k表示数字位数

  • 相关阅读:
    解决centos yum源配置出现Couldn't resolve host 问题
    Centos7下MongoDB下载安装详细步骤
    PHP操作mongodb扩展的坑 及php7安装mongodb扩展
    阿里云 Composer 全量镜像
    centos beanstalkd 安装 与php调用
    centos与windows共享文件夹
    centos php 安装编译 常见报错
    [PHP] layui实现多图上传,图片自由排序,自由删除
    Vue-element-admin实现菜单根据用户权限动态加载
    迭代器的使用方法
  • 原文地址:https://www.cnblogs.com/ZN-225/p/10352973.html
Copyright © 2020-2023  润新知