python常用代码_Python常用算法学习(3)(原理+代码)——最全总结

1,什么是算法的时间和空间复杂度

  算法(Algorithm)是指用来操作数据,解决程序问题的一组方法,对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但是在过程中消耗的资源和时间却会有很大的区别。

  那么我们应该如何去衡量不同算法之间的优劣呢?

  主要还是从算法所占用的时间空间两个维度取考量。

  • 时间维度:是指执行当前算法所消耗的时间,我们通常使用时间复杂度来描述。
  • 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用空间复杂度来描述

  因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况,然而,有的时候时间和空间却又是鱼与熊掌,不可兼得,那么我们就需要从中去取一个平衡点。

  下面分别学习一下时间复杂度和空间复杂度的计算方式。

1.1 时间复杂度

  我们想要知道一个算法的时间复杂度,很多人首先想到的方法就是把这个算法程序运行一遍,那么它所消耗的时间就自然而然的知道了。这种方法可以吗?当然可以,不过它也有很多弊端。

  这种方式非常容易受运行环境的影响,在性能高的机器上跑出来的结果与在性能低的机器上跑的结果相差会很大。而且对测试时使用的数据规模也有很大关系。再者我们再写算法的时候,还没有办法完整的去运行呢,因此,另一种更为通用的方法就出来了:大O符号表示法,即T(n) = O(f(n))。

  我们先看一个例子:

for(i=1; i<=n; ++i){   j = i;   j++;}

  通过大O符合表示法,这段代码的时间复杂度为O(n),为什么呢?

  在大O符号表示法中,时间复杂度的公式是:T(n) = O( f(n) ),其中f(n)表示每行代码执行次数之和,而O表示正比例关系,这个公式的全称是:算法的渐进时间复杂度。

   我们继续看上面的例子,假设每行代码的执行时间都是一样的,我们用1颗粒时间来表示,那么这个例子的第一行耗时是1个颗粒时间,第三行的执行时间是n个颗粒时间,第四行执行时间也是n个颗粒时间(第二行和第五航是符号,暂时忽略),那么总时间就是1颗粒时间+n颗粒时间+n颗粒时间,即 T(n) = (1+2n)*颗粒时间,从这个结果可以看出,这个算法的耗时是随着n的变化而变化,因此,我们可以简化的将这个算法的时间复杂度表示为:T(n) = O(n)。

  为什么可以这么去简化呢,因为大O符号表示法并不是用于来真实代表算法的执行时间的,它是用来表示代码执行时间的增长变化趋势的。

  所以上面的例子中,如果n无限大的时候,T(n) = time(1+2n)中的常量1就没有意义了,倍数2也意义不大。因此直接简化为T(n) = O(n) 就可以了。

常用的时间复杂度量级有:

  • 常数阶O(1)
  • 对数阶O(N)
  • 线性阶O(logN)
  • 线性对数阶O(nlogN)
  • 平方阶O(n2)
  • 立方阶O(n3)
  • K 次方阶O(n^k)
  • 指数阶(2^n)

  从上之下依次的时间复杂度越来越大,执行的效率越来越低。

  下面选取一些较为常用的来说一下。

1,常数阶O(1)

  无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1),如:

int i = 1;int j = 2;++i;j++;int m = i + j;

  上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。

2,对数阶O(N)

for(i=1; i<=n; ++i){   j = i;   j++;}

  这段代码,for循环里面的代码会执行N遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。

3,线性阶O(logN)

  先看代码

int i = 1;while(i

  从上面代码可以看到,在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n  也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)

4,线性对数阶O(nlogN)

  线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)。

  就拿上面的代码加一点修改来举例:

for(m=1; m

5,平方阶O(n2)

  平方阶O(n2)更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。  举例:

for(x=1; i<=n; x++){   for(i=1; i<=n; i++)    {       j = i;       j++;    }}

  这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(n*n),即 O(n²)   如果将其中一层循环的n改成m,即:

for(x=1; i<=m; x++){   for(i=1; i<=n; i++)    {       j = i;       j++;    }}

  那它的时间复杂度就变成了 O(m*n)

6,立方阶O(n3)及K 次方阶O(n^k)

  参考上面O(n2)去理解就好了,相当于三层n循环,其他的类似。

  除此之外,其实还有 平均时间复杂度、均摊时间复杂度、最坏时间复杂度、最好时间复杂度 的分析方法,有点复杂,这里就不展开了。

1.2 空间复杂度

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

  既然时间复杂度不是用来计算程序具体耗时的,那么我们也应该明白,空间复杂度也不是用来计算程序实际占用的空间的。空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用S(N)来定义。

  空间复杂度的表示方式与时间复杂度完全一样:

  1. 算法使用了几个变量:O(1)
  2. 算法使用了长度为 n 的一维列表: O(n)
  3. 算法使用了m 行 n 列的二维列表:O(mn)

  空间复杂度比较常用的有:O(1),O(n),O(n2),我们来看看:

空间复杂度O(1)

  如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可以表示为O(1)。

  举例:

int i = 1;int j = 2;++i;j++;int m = i + j;

  代码中的i,j,m所分配的空间都不随着处理数据量变化,因此他的空间复杂度S(n) = O(1)

空间复杂度O(n)

  我们先看一个代码:

int[] m = new int[n]for(i=1; i<=n; ++i){   j = i;   j++;}  

这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2~6行,虽然有循环,但是没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即S(n)=O(n)。

空间换时间:常会为了追求时间复杂度而牺牲空间复杂度。

1.3 如何简单快速的判断算法复杂度

快速判断算法复杂度(适用于绝大多数简单情况):

  1. 确定问题规模 n
  2. 循环减半过程 ——> logN
  3. k层关于 n 的循环——> nk

复杂情况:根据算法执行过程判断

2,常用的算法总结

2.1 递归

2.1.1 递归概念

  递归的两个特点:1:,调用自身 2,结束条件

  举个例子学习下面函数是不是递归:

# 这个是一个死递归,只调用自身,但是没有结束条件def func1(x):    print(x)    func1(x-1) # 这个是一个死递归,看似有结束条件,但是却无法结束def func2(x):    if x > 0:        print(x)        func2(x+1) # 这是一个递归函数,满足调用自身,并且有结束条件def func3(x):    if x > 0:        print(x)        func3(x-1)  # 这个也是一个递归,但是和func3有区别def func4(x):    if x > 0:        func4(x-1)        print(x)

  func3和func4的区别是什么?

  假设x为3,那么func3输出的结果为:3,2,1 而 func4输出的结果为: 1,2,3。

  为了方便理解,我们利用如下图:

c689546f7a0f415e14121cc7d9b2ed26.png

2.1.2 递归实例:汉诺塔问题

  问题描述:大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从上往下按照大小顺序摞着64片黄金圆盘。大梵天命名婆罗门把圆盘从下面开始按大小顺序重新摆放在另一个柱子上。在小圆盘上不能放大圆盘,在三根柱子之间只能移动一个圆盘。64根柱子移动完毕之日,就是世界毁灭之时。

  问题分析

f854e712ee736cf78382558c632a501b.png
03f23a2facfb3fdcd8cb7e1d68818e9c.png

  代码实现

def hanoi(x,a,b,c):    if x>0:        # 除了下面最大的盘子,剩下的盘子从a移动到b        hanoi(x-1,a,c,b)        # 把最大的盘子从a移到c        print('%s->%s'%(a,c))        # 把剩余的盘子从b移到c        hanoi(x-1,b,a,c)  hanoi(3,'A','B','C')  # 计算次数def cal_times(x):    num = 1    for i in range(x-1):        # print(i)        num = 2*num +1    print(num)  cal_times(3)

  递归总结

  • 1,汉诺塔移动次数的递推式: h(x) = 2h(x-1)+1
  • 2,h(64) = 18446744073709551615
  • 3,假设婆罗门每秒钟搬一个盘子,则总共需要 5800亿年

 证明:为什么汉诺塔的计算次数是2n+1呢?

  对于一个单独的塔,可以进行以下操作:

  • 1:将最下方的塔的上方的所有塔移动到过渡柱子
  • 2:将底塔移动到目标柱子
  • 3:将过渡柱子上的其他塔移动到目标柱子

所以f(3)=f(2)+1+f(2)=7然后以此类推

  • f(4)=f(3)+1+f(3)=15
  • f(5)=f(4)+1+f(4)=31
  • f(6)=f(5)+1+f(5)=63
  • f(7)=f(6)+1+f(6)=127
  • f(8)=f(7)+1+f(7)=255
  • f(9)=f(8)+1+f(8)=511

  f(x+1)=2*f(x)+1再进一步,可以得到通项公式为  f(x)=2^x-1

2.2 查找算法

  查找:在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程。

  列表查找(线性表查找):从列表中查找指定元素

  1. 输入:列表,待查找元素
  2. 输出:元素下标(未找到元素时一般返回None或 -1)

内置列表查找函数:index()

2.2.1 顺序查找(Linear Search)

  顺序查找:也叫线性查找,从列表第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止。

  时间复杂度为:O(n)

def linear_search(data_set, value):    for i in data_set:        if value == data_set[i]:            return i    return None

2.2.2 二分查找(Binary Search)

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

185e4a35a832c6bc914ce7897b552cb7.png
1de255089f0e2a7c03e94e697346a61c.png
918cd9bcf43bc40198860eb6494c1abb.png

  时间复杂度:O(logN)

def binary_search(data_list, val):       low = 0                         # 最小数下标       high = len(data_list) - 1       # 最大数下标       while low <= high:               mid = (low + high) // 2     # 中间数下标               if data_list[mid] == val:   # 如果中间数下标等于val, 返回                       return mid               elif data_list[mid] > val:  # 如果val在中间数左边, 移动high下标                       high = mid - 1               else:                       # 如果val在中间数右边, 移动low下标                       low = mid + 1    else:           return None    # val不存在, 返回None ret = binary_search(list(range(1, 10)), 3)print(ret)

2.3 列表排序算法

  排序:将一组“无序”的记录序列调整为“有序” 的记录序列。

  列表排序:将无序列表变为有序列表

  • 输入:列表
  • 输出:有序列表

  升序与降序

  内置排序算法:sort()

2.3.1 常见排序算法

  如下图所示:

f6b50d74357567466444f0a59899462b.png

  部分排序算法的时间复杂度和空间复杂度及其稳定性如下:

d24b915d68be61b955adcb12a0e923db.png
1711844bdb9167a85ce81a69e24bd041.png

2.3.2 冒泡排序(Bubble Sort)

  冒泡排序:像开水烧气泡一样,把最大的元素冒泡到最上面。一趟就是把最大的冒到最上面。冒到最上面的区域叫有序区。下面叫无序区。

  列表每两个相邻的数,如果前面比后面大,则交换这两个数。

  一趟排序完成后,则无序区减少一个数,有序区增加一个数。

  代码关键点:趟,无序区范围

  时间复杂度:O(n2)

def bubble_sort(li):    for i in range(len(li)-1):        for j in range(len(li)-1-i):            if li[j] > li[j+1]:                li[j], li[j+1] = li[j+1], li[j]    return li

  冒泡排序降序排列:

# 降序排列import random def bubble_sort(li):    for i in range(len(li)-1):  # 第 i 趟        for j in range(len(li)-1-i):            if li[j] < li[j+1]:                li[j], li[j+1] = li[j+1], li[j] li = [random.randint(0, 10) for i in range(10)]print(li)bubble_sort(li)print(li)'''[5, 2, 10, 5, 5, 2, 5, 4, 3, 2][10, 5, 5, 5, 5, 4, 3, 2, 2, 2]'''

  当然也可以打印每一趟,看看冒泡排序的过程:

# 升序排列,打印每一趟,看看其过程def bubble_sort(li):    for i in range(len(li)-1):  # 第 i 趟        for j in range(len(li)-1-i):            if li[j] > li[j+1]:                li[j], li[j+1] = li[j+1], li[j]        print(li) li = [9,8,7,6,5,4,3,2,1]print('origin:',li)bubble_sort(li) '''origin: [9, 8, 7, 6, 5, 4, 3, 2, 1][8, 7, 6, 5, 4, 3, 2, 1, 9][7, 6, 5, 4, 3, 2, 1, 8, 9][6, 5, 4, 3, 2, 1, 7, 8, 9][5, 4, 3, 2, 1, 6, 7, 8, 9][4, 3, 2, 1, 5, 6, 7, 8, 9][3, 2, 1, 4, 5, 6, 7, 8, 9][2, 1, 3, 4, 5, 6, 7, 8, 9][1, 2, 3, 4, 5, 6, 7, 8, 9]'''

  冒泡排序的最差情况,即每次都交互顺序的情况下,时间复杂度为O(n**2)。

  存在一个最好情况就是列表本来就是排好序,所以可以加一个优化,也就是一个标志位,如果没有出现交换的情况,说明列表已经有序,可以直接结束算法,则直接return。

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

2.3.3 选择排序(Select Sort)

  一趟排序记录最小的数,放到第一个位置

  再一趟排序记录记录列表无序区最小的数,放到第二个位置。。。。

  算法的关键点:有序区和无序区,无序区最小数的位置

  先看一个简单的选择排序

def select_sort_simple(li):    li_new = []    for i in range(len(li)):        min_val = min(li)        li_new.append(min_val)        li.remove(min_val)    return li_newli = [4,3,2,1]print(select_sort_simple(li))# [1, 2, 3, 4]

  我们会发现首先他生成了两个列表,那么就占用了两份内存,而算法的思想则是能省则省,能抠则抠,所以我们需要改进一下。

def select_sort(li):    for i in range(len(li)-1):  # i 是第几趟        min_loc = i        for j in range(i+1, len(li)):            if li[j] < li[min_loc]:                min_loc = j        li[i], li[min_loc] = li[min_loc], li[i]    return li li = [1,2,3,4]print(select_sort(li))# [1, 2, 3, 4]

2.3.4 插入排序(Insertion Sort)

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

def insert_sort(li):    for i in range(1, len(li)):  # i表示摸到的牌的下标        temp = li[i]        j = i - 1  # j指的时手里的牌的下标        # if j > 0 and temp < li[j]:  # 找到一个合适地位置插进去        while j >= 0 and li[j] > temp:            li[j + 1] = li[j]            j -= 1        li[j + 1] = temp    return li

  简单形象的一张图:

79331c56a431148adc8f57c39aee43d5.png

  时间复杂度是 O(n2)

  如果目标是 n 个元素的序列升序排列,那么采用插入排序存在最好情况和最坏的情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需要(n-1)次即可。最坏的情况就是序列是降序排列,那么此时需要进行的比较共有 n(n-1)/2次。插入排序的赋值操作时比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2),因而插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如量级小于千,那么插入排序还是一个不错的选择。

2.3.5 快速排序

  原理:让指定的元素归位,所谓归位,就是放到他应该放的位置(左边的元素比他小,右边的元素比他大),然后对每个元素归位,就完成了排序。

  可以参考下面动图来理解代码:

859dbd0cdc0e3b1b2200132ad9f679c3.gif

  左边空位置,从右边找,右边空位置,从左边找。当左边和右边重合的时候,就是mid。

651485642d1d69d7fe9a0015de6dff78.png

    下图来自百度百科

36267edf55e59ecfed027246191e6a86.gif

  快速排序——框架函数

def quick_sort(data, left, right):    if left < right:        mid = partition(data, left, right)        quick_sort(data, left, mid-1)        quick_sort(data, mid+1, right)

  完整代码如下:

def partition(data, left, right):    # 把左边第一个元素赋值给tmp,此时left指向空    tmp = data[left]    # 如果左右两个指针不重合,则继续    while left < right:        while left < right and data[right] >= tmp:            right -= 1   # 右边的指标往左走一步        # 如果right指向的元素小于tmp,就放到左边目前为空的位置        data[left] = data[right]        print('left:', li)        while left < right and data[left] <= tmp:            left += 1        # 如果left指向的元素大于tmp,就交换到右边目前为空的位置        data[right] = data[left]        print('right:', li)    data[left] = tmp    return left  # 写好归位函数后,就可以递归调用这个函数,实现排序def quick_sort(data, left, right):    if left < right:        # 找到指定元素的位置        mid = partition(data, left, right)        # 对左边的元素排序        quick_sort(data, left, mid - 1)        # 对右边的元素排序        quick_sort(data, mid + 1, right)    return data  li = [5, 7, 4, 6, 3, 1, 2, 9, 8]print('start:', li)partition(li, 0, len(li) - 1)print('end:', li) '''start: [5, 7, 4, 6, 3, 1, 2, 9, 8]left: [2, 7, 4, 6, 3, 1, 2, 9, 8]right: [2, 7, 4, 6, 3, 1, 7, 9, 8]left: [2, 1, 4, 6, 3, 1, 7, 9, 8]right: [2, 1, 4, 6, 3, 6, 7, 9, 8]left: [2, 1, 4, 3, 3, 6, 7, 9, 8]right: [2, 1, 4, 3, 3, 6, 7, 9, 8]end: [2, 1, 4, 3, 5, 6, 7, 9, 8]'''
25069daad63a8c999ac5de151df508cc.png

  正常的情况,快排的复杂度是O(nlogn)

  快排存在一个最坏情况,就是每次归位,都不能把列表分成两部分,此时复杂度就是O(n2)了,如果要避免设计成这种最坏情况,可以在取第一个数的时候不要取第一个了,而是取一个列表中的随机数。

2.3.6 堆排序(Heap Sort)

  本质是使用大根堆或小根堆来对一个数组进行排序。所以首先要理解树的概念。

  关于树的理解请参考博客:https://www.cnblogs.com/wj-1314/p/11631934.html

  堆简单来说:一种特殊的完全二叉树结构

  • 大根堆:一种完全二叉树,满足任一节点都比其孩子节点大
  • 小根堆:一种完全二叉树,满足任一节点都比其孩子节点小
1418e161275a86c30a331024cea96c34.png

堆排序——堆的向下调整性质

  假设根节点的左右子树都是堆,但根节点不满足堆的性质,可以通过一次向下的调整来将其变成一个堆

39a67dceb334c1af7b6dfbbd67767963.png

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

堆排序过程

1,建立堆

2,得到堆顶元素,为最大元素

3,去掉堆顶,将堆最后一个元素放到堆顶,此时可以通过一次调整重新使堆有序。

4,堆顶元素为第二大元素

5,重复步骤3,直到堆变为空

  代码如下:

def sift(data, low, high):    i = low    j = 2*i+1    tmp = data[i]    while j <=high:        if j < high and data[j] < data[j+1]:            j+=1        if tmp < data[j]:            data[i] = data[j]            i = j            j = 2*i+1        else:            break    data[i] = tmp def heap_li(li):    n = len(li)    for i in range((n - 2) // 2, -1, -1):        # i表示建堆的时候调整的部分的跟的下标        sift(li, i, n - 1)    # 建堆完成了    print('建堆完成后的列表:',li)    for i in range(n-1, -1, -1):        # i 指向当前堆的最后一个元素        li[0], li[i] = li[i], li[0]        sift(li, 0, i-1)  # i-1是新的high    print(li) li = [i for i in range(12)]import randomrandom.shuffle(li)print(li) heap_li(li)print(li)'''[7, 2, 4, 3, 8, 9, 0, 5, 11, 6, 10, 1]建堆完成后的列表: [11, 10, 9, 5, 8, 4, 0, 2, 3, 6, 7, 1][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]'''

堆排序应用——topK问题

  问题描述:现在有n个数,设计算法得到前k大的数( k

  解决思路

  1. 排序后切片 时间复杂度为:O(nlogn)
  2. 排序(选择,插入,冒泡) 时间复杂度为:O(mn)
  3. 堆排序思路 时间复杂度为:O(mlogn)

  代码如下:

def sift(li, low, high):    i = low    j = 2 * i + 1    tmp = li[low]    while j <= high:        if j + 1 <= high and li[j + 1] < li[j]:            j = j + 1        if li[j] < tmp:            li[i] = li[j]            i = j            j = 2 * j + 1        else:            break        li[i] = tmp  def topk(li, k):    heap = li[0:k]    for i in range((k - 2) // 2, -1, -1):        sift(heap, i, k - 1)    # 1,建堆    for i in range(k, len(li) - 1):        if li[i] > heap[0]:            heap[0] = li[i]            sift(heap, 0, k - 1)    # 2,遍历    for i in range(k - 1, -1, -1):        heap[0], heap[i] = heap[i], heap[0]        sift(heap, 0, i - 1)    # 3,出数    return heap  import random li = list(range(1000))random.shuffle(li)print(topk(li, 10))

2.3.7 归并排序(Merge Sort)

  归并:假设现在的列表分两段有序,如何将其合成为一个有序列表,这种操作叫做一次归并。

  如下图所示:虚线分开,两个箭头分别指向列表的第一个元素。然后从左边开始比较两边的元素,小的出列。然后继续循环,这样就排出来一个有序列表。

c18312e9030c270931d7c60536bc85c4.png
09ee4cdfd71b00cadcd4149f1eeec98f.png
e14a6f22472ad42129cf9c24f0a17f44.png

  应用到排序就是把列表分成一个元素一个元素的,一个元素当然是有序的,将有序列表一个一个合并,列表越来越大,最终合并成一个有序的列表。

  归并排序如图所示:

6241e1d06d54981688b861d478cce8a0.png

  归并排序代码如下:

def merge(li, low, mid, high):    i = low  # i为左边列表开头元素的坐标    j = mid + 1  # j为右边列表开头元素的坐标    ltmpd = []  # 临时列表    # 只要两边都有数    while i <= mid and j <= high:        if li[i] < li[j]:            ltmpd.append(li[i])            i += 1        else:            ltmpd.append(li[j])            j += 1    # while执行完,肯定有一部分没数字了,就是两个箭头肯定有一个指向没数了    while i <= mid:        ltmpd.append(li[i])        i += 1    while j <= high:        ltmpd.append(li[j])        j += 1    li[low:high + 1] = ltmpd    # return ltmpd   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 = list(range(10))import randomrandom.shuffle(li)print(li)merge_sort(li, 0, len(li)-1)print(li)

  归并排序的时间复杂度:O(nlogn)

  归并排序的空间复杂度:O(n)

2.3.8 快速排序,堆排序,归并排序三种算法的总结

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

2,一般情况下,就运行时间而言:快速排序 < 归并排序 < 堆排序

3,三种排序算法的缺点

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

2.3.9 希尔排序(Shell Sort)

  希尔排序(Shell Sort)是一种分组插入排序算法。

  其算法步骤如下:

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

  图解如下:

  1,数组(列表)如下:

feae8edff16bf20c1af294019ba46420.png

   2,d=4(即d=len(li)/2):

9e0e51aaee3aa2cc3667814c1cc0648c.png

  3,d=2:

5c7ac2da47114a6f9839973335344298.png

   4,d=1:

3d7a5dde26a823cd297739472aebbd58.png

  在wiki查找地址如下:https://en.wikipedia.org/wiki/Shellsort#Gap_sequences

  希尔排序的时间复杂度讨论比较复杂,并且和选取的gap序列有关。

2.3.10 计数排序(Count Sort)

  简单来说如下图所示:

71de3b964429cf4c6c107272f496facd.png

  出现那个数,就给那个数的数量加一。

  代码如下:

def count_sort(li, max_count=100):    count = [0 for _ in range(max_count+1)]    for val in li:        count[val] += 1    li.clear()  # 原列表清空,这样就不用建新列表,省内存    for ind, val in enumerate(count):        for i in range(val):            li.append(ind) import randomli = [random.randint(0, 19) for _ in range(30)]print(li)count_sort(li)print(li)'''[1, 4, 13, 6, 19, 4, 9, 14, 10, 15, 7, 1, 1, 9, 3, 8, 17, 3, 18, 1, 8, 17, 14, 2, 10, 0, 5, 8, 12, 15][0, 1, 1, 1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 8, 8, 9, 9, 10, 10, 12, 13, 14, 14, 15, 15, 17, 17, 18, 19]'''

  对列表进行排序,已知列表中的数范围都在0到100之间,设计时间复杂度为O(n)的算法。就可以使用此算法,即使列表长度大约为100万,虽然列表长度很大,但是数据量很小,会有大量的重复数据,我们可以考虑对这100个数进行排序。

2.3.11 桶排序(Bucket Sort)

  桶排序也叫计数排序,简单来说,就是将数据集里面所有元素按顺序列举出来,然后统计元素出现的次数,最后按照顺序输出数据集里面的元素。

  在计数排序中,如果元素的范围比较大(比如在1到1亿之间),如何改造算法?

  桶排序(Bucket Sort):首先将元素分在不同的桶中,在对每个桶中的元素排序。

6e61666b09068313af04be7d40fb7940.png

  如上图,列表为 [29, 25, 3, 49, 9, 37, 21, 43]排序,我们知道数组的范围是0~49,我们将其分为5个桶,然后放入数字,一次对桶中的元素排序。

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

  • 平均情况时间复杂度为:O(n+k)
  • 最坏情况时间复杂度为:O(n2k)
  • 空间复杂度为:O(nk)

  代码如下:

#_*_coding:utf-8_*_ def bucket_sort(li, n=100, max_num=10000):    buckets = [[] for _ in range(n)]  # 创建桶    for var in li:        # 0 -》 0, 86        i = min(var // (max_num // n), n-1)  # i表示var放到几号桶里        buckets[i].append(var)  # 把var加入到桶里        # [0, 2, 4]        # 保持桶内的顺序        for j in range(len(buckets[i])-1, 0, -1):            if buckets[i][j] < buckets[i][j-1]:                buckets[i][j], buckets[i][j-1] = buckets[i][j-1], buckets[i][j]            else:                break     sorted_li = []    for buc in buckets:        sorted_li.extend(buc)    return sorted_li import randomif __name__ == '__main__':    li = [random.randint(0, 10000) for i in range(10000)]    # print(li)    li = bucket_sort(li)    print(li)

2.3.12 基数排序

  多关键字排序:加入现在有一个员工表,要求按照薪资排序,年龄相同的员工按照年龄排序。

  方法:先按照年龄进行排序,再按照薪资进行稳定的排序。

  比如对 [32, 13, 94, 52, 17, 54, 93] 排序是否可以看成多关键字排序?

  实现示例如下:

  1,首先按照个位分桶:

48dc73b83c5d582d07a9fbbd6eeac5c4.png

  2,按照个位数分好,桶,然后摆回原位

3afb50373f5277367d2b19e2b75f4228.png
1e99cac6f2a613e6d64306d07ec6e50f.png

  3,按照十位数进行分桶,然后将桶里的数排序

8f916538e177edf25211eaa90832f536.png

  基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按照每个位数分别比较。由于整数也可以表达字符串(比如名称或日期)和特定格式的浮点数,所以基础排序也不是只能使用于整数。

  基数排序的时间复杂度为:O(kn)

  基数排序的空间复杂度为:O(k+n)

  基数排序中 k 表示数字位数

     由于基数排序使用了桶排序,所以空间复杂度和桶排序的空间复杂度是一样的。

  代码如下:

def list_to_buckets(li, base, iteration):    buckets = [[] for _ in range(base)]    for number in li:        digit = (number // (base ** iteration)) % base        buckets[digit].append(number)    return buckets def buckets_to_list(buckets):    return [x for bucket in buckets for x in bucket] def radix_sort(li, base=10):    maxval = max(li)    it = 0    while base ** it <= maxval:        li = buckets_to_list(list_to_buckets(it, base, it))        it += 1    return li

2.3.13 基数排序 VS 计数排序 VS 桶排序

  这三种排序算法都利用了桶的概念,但对于桶的使用方法上有明显差异。

  • 基数排序:根据键值的每位数字来分配桶
  • 计数排序:每个桶只存储单一键值
  • 桶排序:每个桶存储一定范围内的数值

三,几道查找排序习题

  这一节是对前面学习的算法的应用,也就是习题练习。

1,给两个字符串s和t,判断 t是否为s的重新排列后组成的单词

  s = 'anagram' t='nagaram' return true

  s='rat', t='car', return false

  两种方法一种直接使用Python的list排序,当然这种时间复杂度可能高一些。另一种方法使用字典记录list中出现字母的次数。代码如下:

def isAnagram0(s, t):    return sorted(list(s)) == sorted(list(t))def isAnagram1(s, t):    dict1 = []  # ['a':1, 'b':2]    dict2 = []    for ch in s:        dict1[ch] = dict1.get(ch, 0) + 1     for ch in t:        dict2[ch] = dict2.get(ch, 0) + 1    return dict1 == dict2

2,给定一个 m*n 的二维列表,查找一个数是否存在,列表有下列特性:

  • 每一行的列表从左到右已经排序好
  • 每一行第一个数比上一行最后一个数大
8d928c841f7e4d5265e846596c00bba7.png

  思路如下:有两个方法,第一个是暴力遍历法,但是这种时间复杂度会很高,而相对来说改进的方法是二分查找。

  实现代码如下:

def searchMatrix(matrix, target):    for line in matrix:        if target in line:            return True    return False  def searchMatrix1(matrix, target):    h = len(matrix)    if h == 0:        return False  # h=0 即为 []    # 当然也出现一种可能就是 [[],[]]    w = len(matrix[0])    if w == 0:        return False    left = 0    right = w * h - 1    # 直接使用二分查找的代码    while left <= right:        mid = (left + right) // 2        i = mid // w        j = mid % w        if matrix[i][j] == target:            return True        elif matrix[i][j] > target:            right = mid - 1        else:            left = mid + 1    else:        return False  matrix = [[1, 2, 3], [5, 6, 7], [9, 12, 23]]target = 32res = searchMatrix1(matrix, target)print(res)

3,给定一个列表和一个整数,设计算法找到两个数的下标,使得两个数之和为给定的整数。保证肯定仅有一个结果。例如,列表[1,2,5,4] 与目标整数3,1+2=3,结果为(0,1)

  代码如下:

def TwoSum(nums, target):    '''     :param nums:  nums是代表一个list    :param target: target是一个数    :return: 结果返回的时两个数的下标    '''    for i in range(len(nums)):        for j in range(i + 1, len(nums)):            if nums[i] + nums[j] == target:                return (i, j)  def TwoSum1(nums, target):    # 新建一个空字典用来保存数值及在其列表中对应的索引    dict1 = {}    for i in range(len(nums)):        # 相减得到另一个数值        num = target - nums[i]        if num not in dict1:            dict1[nums[i]] = i        # 如果在字典中则返回        else:            return [dict1[num], i] def binary_search(li,left, right, val):    while left <= right: # 候选区有值        mid = (left + right) // 2        if li[mid] == val:            return mid        elif li[mid] < val:            left = mid +1        else:            right = mid -1    else:        return None def TwoSum2(nums, target):    for i in range(len(nums)):        a = nums[i]        b = target - a        if b >=a:            j = binary_search(nums, i+1, len(nums)-1, a)        else:            j = binary_search(nums, 0, i-1, b)        return (i, j)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/455506.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据监测驱动下的信息流广告优化

信息流广告是什么 “今日头条和百度必有一战”&#xff0c;相信不少的互联网人在过去几个月都听到过类似的断言。定位于信息分发平台的今日头条和主营搜索业务的百度会产生如此大的利益冲突&#xff0c;最核心的点其实就是信息流广告。 信息流广告指的是在用户使用互联网产品或…

在idea中使用git管理你的项目

起步 idea是十分智能的Java集成开发环境 而我们在用idea写项目的时候经常遇到版本控制的问题,而git工具如果你只会在终端中的git命令来进行控制,可能会使得效率低下 今天小编就教大家在idea中使用git来管理你的项目 首先创建一个项目 点击create new projects 这里选择默认…

偏好设置

转载于:https://www.cnblogs.com/xufengyuan/p/6959424.html

keyshot环境素材文件_快速学会keyshot基础渲染的步骤

KeyShot是基于CPU为三维数据进行渲染和动画操作的独立渲染器。意为“The Key to Amazing Shots”&#xff0c;是一个互动性的光线追踪与全域光渲染程序&#xff0c;无需复杂的设定即可产生相片般真实的 3D 渲染影像。KeyShot超强的渲染能力广泛的应用于工业产品、机械工程、CG行…

传统数据中心如何实现向云的平滑升级

1.引言 众所周知&#xff0c;云计算是近年来发展最快的互联网技术&#xff0c;被称为第四次IT革命。据权威机构预测&#xff0c;到2016年&#xff0c;2/3的IT应用服务将建立在云架构上 [1]。作为云计算核心的基础承载设施&#xff0c;数据中心在网络中所扮演的角色也愈加重要。…

上位机与基恩士plc以太网通讯_2020湛江AB罗克韦尔PLC主机回收二手或全新

2020湛江AB罗克韦尔PLC主机回收二手或全新专业回收基恩士光电传感器回收&#xff0c;基恩士安全光栅回收&#xff0c;基恩士对射开关回收&#xff0c;基恩士工控配件回收&#xff0c;基恩士视觉相机回收&#xff0c;发那科伺服驱动电机回收&#xff0c;发那科控制系统回收&…

生物信息学概论_大学专业详解系列83——生物信息学(理学学士)

生物信息学(理学学士)毕业生应具备的知识和能力(1)掌握扎实的数学、物理、化学基础理论和基本知识&#xff1b;(2)掌握生物学专业基础知识和信息处理的专门知识&#xff1b;(3)掌握普通生物学、细胞生物学、遗传学、分子生物学、生物数据库管理系统、生物信息学、基因组学、蛋白…

Butterknife全方位解析

概述 Butterknife是供职于Square公司的JakeWharton大神开发的开源库&#xff0c;使用这个库&#xff0c;在AS中搭配Android ButterKnife Zelezny插件&#xff0c;可以大大提高开发的效率&#xff0c;从此摆脱繁琐的findViewById(int id)&#xff0c;也不用自己手动bind(int id)…

论文笔记 Aggregated Residual Transformations for Deep Neural Networks

这篇文章构建了一个基本“Block”&#xff0c;并在此“Block”基础上引入了一个新的维度“cardinality”(字母“C”在图、表中表示这一维度)。深度网络的另外两个维度分别为depth&#xff08;层数&#xff09;、width&#xff08;width指一个层的channel的数目&#xff09;。 首…

matlab 归一化_机器学习中如何用Fscore进行特征选择(附Matlab代码)

作者&#xff1a;kervin编辑&#xff1a;阿吉 目前&#xff0c;机器学习在脑科学领域的应用可谓广泛而深入&#xff0c;不论你是做EEG/ERP研究&#xff0c;还是做MRI研究&#xff0c;都会看到机器学习的身影。机器学习最简单或者最常用的一个应用方向是分类&#xff0c;…

IOS安装CocoaPods完整流程

作为一个底层系统大菜鸟,又搞过几年ios来说,安装一个CocoaPods是一件蛋痛的事~ 说懂又懂,说不懂又不懂. 由于安装过程比較复杂,步骤较多,而网上教程又比較零散,并且有一些是扯蛋的,所以本篇文章主要从头到位依据自身安装经历记录每一条终端指令,至于里面的原理和一些概念性的东…

linux 修改时区_教你在Centos8中更改时区

对于许多与系统相关的任务和进程&#xff0c;使用正确的时区是必不可少的。例如&#xff0c;cron守护进程使用系统的时区执行cron作业&#xff0c;日志文件中的时间戳基于同一系统的时区。环 境CentOS 8检查现在的时区timedatectl是一个命令行实用程序&#xff0c;允许您查看和…

vb6在后台将窗体保存到图片_如何将寺库网多个商品图片一键分类保存到一个目录...

寺库网是全球最大的奢侈品网上在线购物平台&#xff0c;那么我们怎样可以从寺库网上一键批量采集到多个宝贝商品图片&#xff0c;并分类保存到电脑呢&#xff1f;今天小编给大家带来一款专业电商图片链接采集软件【载图助手】&#xff0c;它支持平台高达141个&#xff0c;均可支…

浮动与定位

2019独角兽企业重金招聘Python工程师标准>>> 一.浮动:float:一个元素浮动时,其他内容会"环绕"该元素. 浮动元素的外边距不会合并浮动的元素不能超出其包含快的内边界浮动元素彼此会避免重叠浮动元素的顶端不能比之前所有浮动元素或块级元素的顶端更高如果…

驱动级的自动按键_Aqara全自动智能推拉锁D100,体验全自动开门的便捷

大家好&#xff0c;我是梦想是个猪&#xff0c;今天为大家带来的是一篇智能门锁的使用体验。前言家里的这张门陆陆续续的换了好几把智能门锁了&#xff0c;也体验了好几种不同的开锁方式。最开始开发商给安装的是一把指纹和把手分离的那种款式&#xff0c;开锁的时候需要先输入…

深度学习综述

摘要&#xff1a; 深度学习可以完成需要高度抽象特征的人工智能任务&#xff0c;如语音识别、图像识别和检索、自然语言理解等。深层模型是包含多个隐藏层的人工神经网络&#xff0c;多层非线性结构使其具备强大的特征表达能力和对复杂任务建模能力。训练深层模型是长期以来的难…

mac svn工具_Cornerstone 4 for mac(svn管理工具)

Cornerstone 4 for mac是全新版本的svn管理工具&#xff0c;使用cornerstone for mac 特别版建立的版本控制更利于使用&#xff0c;而且cornerstone 4 特别版全面支持Subversion的功能&#xff0c;这里准备了最新版本的cornerstone for mac 特别版&#xff0c;无需激活&#xf…

webgl获取鼠标形状_三模无线搭配对称手型设计,游戏致胜利器,ROG烈刃2无线鼠标...

要想有效地提升游戏体验&#xff0c;我认为除了电脑主机本身的硬件配置要尽可能的硬核之外&#xff0c;玩游戏时所选配的鼠标、键盘等外设的作用也是不可忽视的&#xff0c;所以很多比较注重游戏体验的游戏爱好者都会选择一款自己用着比较顺手的游戏外设装备。我这次入手的华硕…

qmlcanvas绘制3d图形_透视Matplotlib核心功能和工具包 - 绘制3D图形

关联知识MatplotlibPython线图在此&#xff0c;我们将学习如何创建3D线图。 它类似于2D等效折线图&#xff0c;并且2D折线图的许多属性都结转到3D。我们将在相同的轴上绘制凹凸曲线&#xff0c;并从不同角度查看它们&#xff0c;例如平行视图&#xff0c;顶视图以及围绕z轴的旋…

雷军晒3亿估值,意欲“收编”台湾硬件创业者?

1月13日&#xff0c;在台湾的CSMIC 2015移动互联网两岸年会上&#xff0c;雷军以猎豹移动董事长的身份做了《给年轻创业者的两大方向性建议》演讲。 演讲中雷军表示&#xff0c;“在未来5到10年&#xff0c;特别适合台湾年轻创业者做的两个方向&#xff0c;一个是移动互联网&am…