Python 算法 | 简介

什么是算法?
  算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。

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

算法复杂度分为时间复杂度和空间复杂度。其作用: 时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间。(算法的复杂性体现在运行该算法时的计算机所需资源的多少上,计算机资源最重要的是时间和空间(即寄存器资源,因此复杂度分为时间和空间复杂度)。当然,不作特殊说明,我们一般讨论的是该算法的最坏时间复杂度和最坏空间复杂度,即分析最坏情况以估算算法的执行时间的上界。

1. 时间复杂度

我们一般采用大O渐进表示法描述一个算法的时间复杂度。时间复杂度主要讨论的是算法执行的次数。****时间复杂度是用来估计算法运行时间的一个式子(单位)。一般来说,时间复杂度高的算法比复杂度低的算法慢。

一般算法时间复杂度O(n)的表示方法:
(1)用常数1取代运行时间中的所有加法常数 ;
(2)在修改后的运行次数函数中,只保留最高阶项;(大单位后面不挂小单位)
(3)如果最高阶项系数存在且不是1,则去除与这个项相乘的常数;(去掉系数
(4)递归算法的时间复杂度 ==** 递归总次数每次递归的次数*
  空间复杂度 == 递归的深度(即树的高度)
(5)常见的时间复杂度(按效率排序)

O(1)

(6)不常见的时间复杂度(看看就好)

O(n!) O(2n) O(nn) …

如何一眼判断时间复杂度?

  • 没有循环O(1);
  • 循环减半的过程O(logn);
  • 几层循环就是n的几次方的复杂度(舍弃小项);
image

复杂度通称为O(n)
T(n):时间复杂度Time
S(n):空间复杂度Space

名词解释:
  n:问题的规模,重复执行的次数
  T(n):一段程序运行,各种操作代码所执行的总次数
  f(n): 存在的某个函数,使得T(n)/f(n)=非零常数, 那么f(n)称为T(n)的同数量级函数
  O:大O符号,一种符号,表示渐进于无穷的行为

穿起来:
  算法中各种代码操作所执行的总次数用T(n)表示,存在某个函数f(n),使得T(n)/f(n)=非零常数,那么f(n)称为T(n)的同数量级函数(类想一下,在坐标轴中,当入参n趋于无穷时,两条曲线的商为常数),即:T(n)=O(f(n)),O(f(n))就是时间复杂度.O符号表示一个渐进常数. 在这个函数中可以忽略低阶项和首项系数,中文例二解释.

image
image

时间复杂度只是估计,结果O(n2) 相同,不一定时间相同规模,计算机运算速度。

image

O(logn)是非常快的一种算法,以上面64为例,更接近O(1)。所以,一个算法如果能优化成O(logn),是非常快的。

注意:循环减半和数值减一的区别

# 循环减半,每次运行,循环次数就减掉一半log2n
while n > 1:
    print(n)
    n = n // 2

# 数值减一。因为步长为2,所以,是减掉了1,时间复杂度为1/2n,不要系数,所以时间复杂度为O(n)
for i in range(0,n,2):
    print('Hello World')
# 时间复杂度O(n3logn)
for i in range(n):
    for i in range(n):
        for i in range(n):
                print('Hello World')     # 到这是三层
    while n:
        n = n // 2
        for i in range(n):
            for i in range(n):
                print('Hello World')    # 到这是四层

解释:while的时间复杂度是O(nlogn),比O(1)大

2. 空间复杂度

空间复杂度:即程序中变量的个数

3. 排序的稳定性

稳定性
① 定义:能保证两个相等的数,经过排序之后,其在序列的前后位置顺序不变。(A1=A2,排序前A1在A2前面,排序后A1还在A2前面)
② 意义:稳定性本质是维持具有相同属性的数据的插入顺序,如果后面需要使用该插入顺序排序,则稳定性排序可以避免这次排序。

比如,公司想根据“能力”和“资历”(以进入公司先后顺序为标准)作为本次提拔的参考,假设A和B能力相当,如果是稳定性排序,则第一次根据“能力”排序之后,就不需要第二次根据“”资历

排序了,因为“资历”排序就是员工插入员工表的顺序。如果是不稳定排序,则需要第二次排序,会增加系统开销。

分类
① 稳定性排序:冒泡排序,插入排序、归并排序、基数排序
② 不稳定性排序:选择排序、快速排序、希尔排序、堆排序

例析
(1)稳定性排序
① 冒泡排序
  冒泡排序本质是,从左到右开始不断把大的元素往后调(或者,从右往左开始不断把小的元素往前调)。比较是比较相邻的两个元素,交换也是发生在这两个元素之间。所以,如果两个元素相等,你总不会无聊地把它俩交换一下吧。如果两个相等元素没有相邻,那么也会经过一波两两交换使他们相邻,此时也不会发生交换。

② 插入排序
  插入排序本质是,在一个已经有序的小序列基础上,通过从右往左比较,一次插入一个元素。刚开始这个小序列只有一个元素。比较是从有序序列的末尾开始,想插入的元素和已经有序的最大者开始比较,如果比它大则直接插在其后面,否则一直往前比较,直到找到比它小的或与它相等的,然后插在其后面。

③ 归并排序
  归并排序本质是,把序列递归地分成短序列,递归出口是短序列只有1个元素(此时认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的短序列合并成一个有序的长序列,不断合并直到原序列全部排好序。当存在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,因此不会破坏稳定性。那么在短的有序序列合并的过程中,稳定是否受到破坏?没有。合并过程中当两个当前元素相等时,处在前面序列的元素依然保存在结果序列的前面。

④ 基数排序
  基数排序本质是,按照低位先排序,然后收集,再按照高位排序,然后再收集,依次类推,直到最高位。比如序列“171(1),331,171(2)”,低位排序(个位)结果是“171(1),331,171(2)”,高位排序(十位)结果是“331171(1),171(2)”,高位排序(百位)结果是“171(1),171(2),331”。

(2)不稳定性排序
① 选择排序
  选择排序本质是,给每个位置选择剩下元素中最小的。比如给第一个位置选择最小的,给第二个位置选择剩余元素里面第二小的,依次类推。例如序列“5(1),8,5(2),2,9”,5(1)会和2交换,破坏稳定性。

② 快速排序
  快速排序本质是,选取第一个元素为中枢元素a[center_index](index=0)。从两个方向入手,首先左边的i下标一直往右走,当a[i] > a[center_index]停止,由右边的j下标开始往左走,当a[j] <= a[center_index]停止,由左边i下标开始刚才的表演,重复上面的过程,直到i>j,交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 “5,3(1),4,3(2),8,11,9”, 现在中枢元素5和3(2)交换就会把元素3的稳定性打乱。不稳定发生在中枢元素和a[j]交换的时刻

③ 希尔排序
  希尔排序本质是,按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

④ 堆排序
  堆排序本质是,我们知道堆的结构是节点i的孩子为2i和2i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n/2-1, n/2-2, ...1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。

排序算法时间的复杂度和空间复杂度

image.png

你可能感兴趣的:(Python 算法 | 简介)