01_Python算法+数据结构笔记-时间/空间复杂度-汉诺塔-顺序查找-二分查找

b站视频:路飞IT学城
清华计算机博士带你学习Python算法+数据结构_哔哩哔哩_bilibili

文章目录

#01 算法入门概念

# 算法(Algorithm):一个计算过程,解决问题的方法
# 程序 = 数据结构 + 算法    数据结构 静态     算法 动态

#02 估计算法运行效率与时间复杂度

# 用什么方式体现算法运行的快慢?   (不能使用时间)
# for循环执行n次 n在算法里叫做问题的规模(问题的复杂度)
# 时间复杂度:用来评估算法运行效率的一个式子

print('Hello World')    # 注:时间复杂度定义为 O(1)   1个print 1就是一个单位 基本操作是1

for i in range(n):       # 注:O(n)   运行n次
    print('Hello World')

for i in range(n):       # 注:O(n2)
    for j in range(n):
        print('Hello World')

for i in range(n):       # 注:O(n3)
    for j in range(n):
        for k in range(n):
            print('Hello World')
print('Hello World')        # 注:O(1)    打印1次和3次差距不大 没有上升到n这么大
print('Hello Python')       # 注:表示近似
print('Hello Algorithm')
#--------------------------

for i in range(n):           # 注:O(n2)  忽略 n 忽略小单位
    print('Hello World')    # 注:类比生活的问几个小时 不涉及分钟
        for j in range(n):
            print('Hello World')

#--------------------------

while n > 1:
    print(n)                # 注:问题规模少一半 但不能写n/2
    n = n // 2
# 时间复杂度记为O(log2n)或 O(logn)     # 注:以对数形式表示
# 把2省略掉,依据是计算机里面都是二进制的
# 当算法过程出现循环折半的时候, 复杂度式子中会出现logn
# 注:当算法出现循环减半的时候,就一定会出现logn (问题规模缩小一半)
# n=64 输出:                  2**6=64
# 64                          log2(64)=6
# 32
# 16
# 8
# 4
# 2
### 时间复杂度小节
# 时间复杂度是用来估计算法运行时间的一个式子(单位)。
# 一般来说,时间复杂度高的算法比复杂度低的算法慢。  
# 注:为什么是一般来说,1因为和机器有关系,2因为和n有关系


# 常见的时间复杂度(按效率排序)
#       O(1)<O(logn)<O(n)<O(nlogn)<O(n**2)<O(n**2logn)<O(n**3)


# 理解:O(1) 只执行1次或几次


# <O(logn)<O(n) logn比n小  log64 < 6
# n < n方 < n立方


# O(n)<O(nlogn)<O(n**2)<O(n**2logn)<O(n**3)
# 复杂问题的时间复杂度 O(n!) O(2**n) O(n**n) …    n的阶层…… 非常难解决的问题



#可以把logn想成0.5n  (自己的理解)

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

 O(1)<O(logn)<O(n)<O(nlogn)<O(n**2)<O(n**2logn)<O(n**3)

#03 简单判断时间复杂度

### 如何简单快速地判断算法复杂度
# 快速判断算法复杂度(适用于绝大多数简单情况):
#       确定问题规模n          # 注:循环次数 列表进行排序就是列表的长度
#       循环减半过程→logn      # 注:循环折半的情况
#       k层关于n的循环→n**k    # 注:几层循环就是n的几次方  列表长度的循环次数
# 复杂情况:根据算法执行过程判断

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

确定问题规模n

 循环减半过程→logn 

k层关于n的循环→n**k

#04 空间复杂度

### 空间复杂度
# 空间复杂度:用来评估算法内存占用大小的式子
# 空间复杂度的表示方式与时间复杂度完全一样
#       算法使用了几个变量:O(1)          # 注:1个变量是0(1)   2个变量也是O(1)
#       算法使用了长度为n的一维列表:O(n)
#       算法使用了m行n列的二维列表:O(mn)    # 注:m*n     n行n列 n*n
# “空间换时间”   (目前公司常采取的策略)
# 注:研究1个算法,时间比空间重要  (远远)    因为现在内存造价便宜了 让用户等待时间到短
    # 注:规则 空间换时间    所以有很多分布式允许  1个机器上运算的东西放到多个机器上运算

#注:跑起来看一看   涉及到n的问题,n不同,内存占用就不一样

 空间复杂度:用来评估算法内存占用大小的式子
 空间复杂度的表示方式与时间复杂度完全一样:
#       算法使用了几个变量:O(1)        
#       算法使用了⻓度为n的一维列表:O(n)
#       算法使用了m行n列的二维列表:O(mn) 
“空间换时间”   (目前公司常采取的策略)

#05 递归

#复习:递归
### 递归的两个特点:
#       调用自身
#       结束条件
#---------------------------------------------------------
def func1(x):   # 注:不是合法递归 因为没有结束条件 (死递归)
    print(x)    # 注:比如x=3进去,会无线循环下去
    func1(x-1)
#---------------------------------------------------------
def func2(x):   # 注:不是合法递归
    if x>0:     # 注:看似合法条件 但是最后是x+1
    print(x)    # 注:2,3,4,5……
    func2(x+1)
#---------------------------------------------------------
def func3(x):   # 注:合法递归
    if x>0:
    print(x)
    func3(x-1)
# 传进去x=3 会打印3 2 1
#---------------------------------------------------------
def func4(x):
    if x>0:
    func4(x-1)
    print(x)
# 传进去x=3 会打印1 2 3
#---------------------------------------------------------
# 可以用框表示函数的调用   这样好理解
# func3先是打印 后是递归    先小后大
# func4先递归   后打印      先大后小  最里层print 是 x=1 先打印

### 递归的两个特点:

# 调用自身

# 结束条件

#06 汉诺塔问题

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

# 注:小圆盘只能放在大圆盘的上面 不能放在下面
#------------------------------------------------------------
### n=2时:
# 1.把小圆盘从A移动到B
# 2.把大圆盘从A移动到C
# 3.把小圆盘从B移动到C
#------------------------------------------------------------
### n个盘子时:                    # 注:把它看作是递归的过程
# 1.把n-1个圆盘从A经过C移动到B
# 2.把第n个圆盘从A移动到C
# 3.把n-1个小圆盘从B经过A移动到C
#------------------------------------------------------------
### 注:把它看作是递归的过程
#注:把上面 n - 1 个盘子看成整体
#注:把下面 1 个盘子看成1个部分
#n 个盘子时
# 1.把 n-1 个圆盘从A经过C移动到B    # 注:中间的过程 不管怎么经过C     第1、3步  移动 n-1个盘子
# 2.把第n个圆盘从A移动到C           # 注:最底下的盘子 从A移动到C      只有第2步 是移动一步的情况
# 3.把n-1个小圆盘从B经过A移动到C       # 注:第1、3步  移动 n-1个盘子

#注:它不是一个只能移动一个盘子的合法的步骤,但是它是一个比原问题规模小了1的同样的1个问题  (n-1)
#注:递归:第1步和第3步是原问题的递归的子问题 (比它规模更小的问题)
#-----------------------------------------------------------------------
#注:(n, a, b, c) --> 把n个盘子从a移动到c 中间的b是经过b 第3个参数是经过
#注:参数意义:n把几个盘子 a 从a,b 经过b,c 移动到c
def hanoi(n, a, b, c):  # 注:参数n 表示 n个盘子 ; a、b、c表示这3个参数表示这三个盘子的名字
    if n > 0:           # 注:递归终止条件是 n = 0 的时候  一步都不需要移动(没有盘子)
        # 注:第一步 (n-1, a, c, b) --> n-1个盘子 从a经过 b 移动到c
        hanoi(n-1, a, c, b) # 注:把n-1个盘子从A经过C移动到B    调用自己 n-1个盘子 从a经过b移动到c
        # print("#%d: moving from %s to %s." % (num, a, c))   # 注:第2步 最大的盘子 从a移动到c
        print("moving from %s to %s." % (a, c))
        hanoi(n-1, b, a, c) # 注:n-1个盘子 从 b 经过 a 移动到 c
#注:每次移动都只是柱子最上面那个
hanoi(3, 'A', 'B', 'C')
#结果为
# moving from A to C.
# moving from A to B.
# moving from C to B.
# moving from A to C.
# moving from B to A.
# moving from B to C.
# moving from A to C.

#注:理解
#当 n = 0 时 什么都不打印
#当 n = 1 时 打印 from a to c    只打印中间那句话 从a --> c  (理解时可以忽略中间b)
#当 n = 2 时 先把1个盘子从a-->b ,再把1个盘子从 a-->c, 再把 从b -->c
#当 n = 3 是 前面3步是2个盘子的问题 后面3步也是2个盘子的问题
#-----------------------------------------------------------------------
###假设n个盘子 移动需要 h(n)次
# 先需要h(n-1)步 + 1步 +h(n-1)步  h大概是2的n次方

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

汉诺塔问题是一个递归的举例,方便理解递归

#07 顺序查找

###列表查找
# 什么是列表查找
# 顺序查找
# 二分查找

#查找
# 查找:在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程。
# 列表查找(线性表查找):从列表中查找指定元素 输入:列表、待查找元素
# 输出:元素下标(未找到元素时一般返回None或-1)    # 注:因为下标一般从0 开始 所以返回-1表示没有查到 或返回None
#
# 内置列表查找函数:index()

#注:列表查找是1个算法
#---------------------------------------------------------


### 顺序查找 (Linear Search)
# 顺序查找:也叫线性查找,从列表第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止。
#注:就是从头走到尾  中间找到停掉 返回下标  或者走到最后一个 找不到了 返回-1或者None
# 时间复杂度:O(n)


# 示例:线性搜索/顺序查找
def Linear_search(li, val):     # 注:li列表 ;val待查找的元素
    for ind, v in enumerate(li):       # 注:因为要返回个下标 所以用 enumerate index和值都需要
        if v == val:    # 注:如果v == 我们要找的那个值 那就返回 它的index
            return ind
    else:   # 如果for循环结束后还没有找到 返回None
        return None
# 注:时间复杂度是 O(n)
#n 就是列表的长度
#第2步:没有循环减半的过程
#第3步:有1个跟n 相关的循环    所以时间复杂度是O(n)

'''
补:

以下是 enumerate() 方法的语法:

enumerate(sequence, [start=0])

以下展示了使用 enumerate() 方法的实例:

>>> seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(enumerate(seasons, start=1))       # 小标从 1 开始
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
'''





#顺序查找是把列表从头到尾走了一遍 所以最多走n步  复杂度0(n)
def linear_search(data_set, value):
    for i in range(range(data_set)):
        if data_set[i] == value:
            return i
    return      # 理解为 默认返回None

首先理解什么是查找,

输入:列表、待查找元素

输出:元素下标  (index())

1、线性搜索/顺序查找    O(n)

就是从到到尾的匹配找到就输出,没找到就下一个

2、二分查找     O(logn)

列表是有序的,然后每次折半找,比中间值呗,超好理解

#08 二分查找介绍

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

#注:比如公司要选模特 身高必须175
#注:从头找 是顺序查找
#注:按大小个站好,量中间那个人的身高,假如是165,说明175的人在165后面
# 可以让左边所以人走了,量过的那个人 也可以走了 还剩大概50个人 再找50个人中间的
# 他是178 , 所以178后面那些人又不要了,
# 还剩165 - 178之间的那些人,依次找中间的,每一次让整个人数缩小一半
# 剩下1个人如果都没有 说明整个都没有

#留在这里的人叫候选区(值可能出现在这里)
#-------------------------------------------------------------------------------
### 从列表中查找元素3:
# 1  2  3  4  5  6  7  8  9
# left        mid         right
#注:传入的是 列表和要找的元素3
#注:维护候选区  用2个变量  列表不要了  不能把不要的值扔掉
#注:初始时 left = 0 (指向第1个元素)       right = n-1 (指向最后1个元素)
#注:求中间的元素  (left + right) / 2  = mid	地板除
#判断中间元素5 和3 进行 比较 发现比3  大  候选区在左边
#注:如何维护候选区?: 让right到4这边来 (mid=5的左边1个)
#即 right = mid - 1  循环了 再计算新的 mid = (0 + 3) / 2 = 1 (下标计算) 结果 地板除
#发现 mid = 2 比 3 小  说明在mid 的右边
#所以 left = mid + 1  移动left 到 mid 右边
#注:left = 2 ;right = 3 (下标) (2 + 3) / 2 = 2  所以 mid 还是只想3 索引是2
#注:mid 索引是2 mid 和我们要查的元素一样了  那就终止算法 输出mid的值 3 (输出下标)

#注:可以理解为 2 和 6 中间的数 是 4  所以(2 + 6) / 2 = 4

#如果最后1步  mid的值不为3 为 4 ,  比想要的值3 大  那么 right = mid -1  候选区就没有值了

#left小于right 的时候 候选区有值
#left = rigth 的时候 也有值 就1个值
#left 如果大于 right 说明候选区没有值 , 就结束算法  说明找不到了

#09 二分查找代码

def binary_search(li, val): # li 列表  val待查找的值
    left = 0
    right = len(li) - 1
    while left <= right:    # 注:循环条件 说明候选区有值
        mid = (left + right) // 2   # 注:中间值(下标) 是整除2
        if li[mid] == val:  # 注:== 找到的情况   每次对比都是对比的li[mid]
            return mid      # 注:返回下标 mid
        elif li[mid] > val:  # 注:代表 待查找的值val在 mid左边
            right = mid - 1  # 注:更新候选区了 这种情况就可以继续循环
        else:               # 注:第3种情况 li[mid] < val  待查找的值在mid右侧
            left = mid + 1   # 注:更新候选区  继续循环
    else:
        return None         # 注:如果找不到的情况 即不满足 left <= right 时

li = [1,2,3,4,5,6,7,8,9]
print(binary_search(li,3))       # 注:调用二分查找 查3
#结果为 2
def bin_search(data_set, value):
    low = 0
    high = len(data_set) - 1
    while low <= high:
        mid = (low+high)//2
        if data_set[mid] == value:
            return mid
        elif data_set[mid] > value:
            high = mid - 1
        else:
            low = mid + 1

#注:算法执行过程中 通过 left 和 right 维护候选区
#注:不管什么情况 都会是整个候选区缩小一半  最后缩小到1个或者2个
#所以 时间复杂度:O(logn)   logn 比n 小
#所以二分查找比线性查找 效率高

列表有序、折半找,这里写代码思路:

定义左边数,右边数,中间数为这两个数和 、、2然后和val比

先比是不是,再比大了咋办,小了咋办

#10 二分查找与线性查找的比较

#写的 cal_time的模块 里面有个函数  这个函数是装饰器
import time

def cal_time(func):
    def wrapper(*args, **kwargs):
        t1 = time.time()
        result = func(*args, **kwargs)
        t2 = time.time()
        print("%s running time: %s secs." % (func.__name__, t2 - t1))
        return result

    return wrapper
#-----------------------------------------------------
上面是一个记时装饰器
'''
补:args和kwargs组合起来可以传入任意的参数
args 是 arguments 的缩写,表示位置参数;kwargs 是 keyword arguments 的缩写,表示关键字参数
arguments	英[ˈɑːgjʊmənts]
美[ˈɑrgjəmənts]
n.	争论; 辩论; 论据; 论点; 参数;

简单理解:*arg,**kwargs 单独用分别是可变参数,可变关键字参数,合在一起,代表任何参数
'''


from cal_time import *   # 注:导入写的模块
#注:对这2个函数加了2个装饰器
@cal_time
def linear_search(li, val):
    for ind, v in enumerate(li):
        if v == val:
            return ind
    else:
        return None

@cal_time
def binary_search(li, val):
    left = 0
    right = len(li) - 1
    while left <= right:
        mid = (left + right) // 2
        if li[mid] == val:
            return mid
        elif li[mid] > val:
            right = mid - 1
        else:
            left = mid + 1
    else:
        return None

li = list(range(10000000)) # 注:大一点的数组
linear_search(li, 38900000)
binary_search(li, 38900000)
#结果为
# 2
# linear_search running time: 0.601691722869873 secs.
# binary_search running time: 0.0 secs.

#二分查找肯定非常快  2的32次方长度的列表 最多只要查32、33次 但线性查找要查42亿次
#列表内置函数 index() 就是线性查找
#因为二分查找有个要求:列表必须是有序的列表,但是  列表不一定是有序的,它只能用线性查找
#二分查找必须先排序  排序的时间复杂度大于O(n)

#有序的用二分查找,无序 如果以后要一直查  先排序  以后直接查 (注意排序的时间复杂度)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老板来片烤面包

君子博学于文,赠之以礼,谢君~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值