☰
30天python算法学习,从0到1第一1章
2026/10/28 2:01:38 网站建设 项目流程

30天python算法学习,从0到1第一章

  • 前言:
  • day1初识算法
    • 1.1 算法无处不在
    • 1.2 算法是什么
      • 1.2.2 数据结构定义
      • 1.2.3 数据结构与算法的关系
  • day2复杂度分析
    • 2.1 算法效率评估
    • 2.2 时间复杂度
    • 2.3 空间复杂度
    • 2.4 测试代码
    • 2.5 练习题(仅仅算时间复杂度)
  • day3 数据结构
    • 3.1 数据结构分类
      • 3.1.1 逻辑结构:线性与非线性
      • 3.1.2 物理结构:连续与分散
    • 3.2 基本数据类型
  • day4 数组与链表
  • day5 栈与队列
      • 栈:
      • 队列:
      • 双向队列(两边都可以进,也都可以出,双向队列甚至可以当作栈来用):
    • 例题:
    • 1 栈
      • 小鱼的数字(入门):
      • [NOIP 2003 普及组] 栈(普及)
    • 2.队列
      • 【深基4.例11】数列求和
      • [NOIP 2006 普及组] 数列

前言:

1.在我学习期间,很多大佬开源免费的知识对我帮助很大,本博客也基于其的开放共享理念而写出,希望其他热爱算法的人们可以跟好的学习。因为本博客仅记录了我学习内容中的部分(重点是做的题里面的重难点),若大家想要真正深入学习,还是推荐大家系统学习看看开源免费的Hello 算法一书,其由github大佬书写,下面是链接:https://www.hello-algo.com/chapter_stack_and_queue/
2.因为可能存在部分人还没有过python基础,这里推荐一些学习渠道:
廖雪峰老师的python3:https://www.liaoxuefeng.com/books/python/

day1初识算法

1.1 算法无处不在

(1)查字典 :下面是一个字典,很多时候,我们在查字典时,会发现每个汉字都对应一个拼音,字典按拼音字母顺序排列。查询时,一页一页地翻太慢,我们通常先按首字母定位到大致范围,再依次比较后面的字母,逐步缩小范围,直到找到目标字。这种“先定位范围、再逐步缩小”就类似于算法里面的二分查找,都是缩小查找范围以便更快进行寻找;当然,严格来说,二分查找是在有序序列中每次取中间位置进行比较,并根据比较结果排除一半范围。

(2)整理扑克:打牌的时候,我们每局都需要整理手中的扑克牌,使其从小到大排列。整理时,我们可以把手中的牌看成两部分:左边是已经排好序的牌,右边是尚未排序的牌。每次从未排序部分取出一张牌,在已排序部分中从后往前比较,直到找到合适位置后插入,使已排序部分仍然有序。而一直重复这一过程,直到未排序部分为空。这个过程本质上就是插入排序算法。
(3)货币找零:收银员通常很忙,因为找零时要需尽快把钱找给顾客。因此,找零时一般会从最大面值的钱币开始考虑:如果当前面值不超过剩余金额,就取用;否则就考虑下一种面值。比如找98元:100元超过98元,不能用;取50元,剩48元;再取50元会超过,不能取;取20元,剩28元;再取20元,剩8元;10元超过8元,不能取;取5元,剩3元;取2元,剩1元;最后取1元。这样每次都在当前情况下选择最大可用面值,直到凑出98元。这种“每一步都选择当前最优方案”的策略,而就是贪心算法的基本思想。

1.2 算法是什么

算法(algorithm)是在有限时间内解决特定问题的一组指令或操作步骤,它具有以下特性。
• 问题是明确的,包含清晰的输入和输出定义。
• 具有可行性,能够在有限步骤、时间和内存空间下完成。
• 各步骤都有确定的含义,在相同的输入和运行条件下,输出始终相同。

1.2.2 数据结构定义

数据结构(Data Structure)是计算机中组织和存储数据的方式,主要涵盖数据的逻辑结构、存储结构以及数据的运算(操作)。其核心设计目标如下:
• 空间效率:尽量减少内存空间的占用。
• 时间效率:使数据操作(如增、删、改、查)尽可能高效。
• 结构清晰:提供简洁的数据表示和清晰的逻辑关系,从而支撑算法的高效运行。
数据结构设计是一个充满权衡的过程。如果想在某方面取得提升,往往需要在另一方面作出妥协。下面举两个例子。
• 链表相较于数组,在数据添加和删除操作上更加便捷,但牺牲了数据访问速度。
• 图相较于链表,提供了更丰富的逻辑信息,但需要占用更大的内存空间。

1.2.3 数据结构与算法的关系

数据结构是算法的基石。数据结构为算法提供了结构化存储的数据,以及操作数据的方法。
• 算法为数据结构注入生命力。数据结构本身仅存储数据信息,结合算法才能解决特定问题。
• 算法通常可以基于不同的数据结构实现,但执行效率可能相差很大,选择合适的数据结构是关键。
**小结:**正如生活中无处不是算法,世界上每时每刻也都是正当我们学习之时。愿阅读本文的读者可以找到学习的乐趣,不断坚持,一直进步!

day2复杂度分析

2.1 算法效率评估

衡量一个算法的效率,通常包括时间效率和空间效率两个方面。为了了解算法运行得快不快(这也是我们最常追求的),我们通常会分析它的时间复杂度;而为了了解它占用的内存多不多,我们则会分析它的空间复杂度。很多时候为了效率的最高,我们往往追求二者最的优解,但很多时候,二者之前很难共存(或者条件太过于苛刻),我们必须考虑在满足当前需要下,做我们真正可以做到的,已到达我们的最优解!

2.2 时间复杂度

它不是具体的电脑运行了代码多久,而是指代码基本操作执行的次数与输入规模 𝑛之间的关系。
怎么计算?(看代码结构)
O(1) 常数阶:没有循环,或者循环次数固定。
Python例子:a = 1,a += 1,dict[key] 查找。

O(n) 线性阶:单层循环。
Python例子:for i in range(n): print(i)。

O(n²) 平方阶:双层嵌套循环。
Python例子:for i in range(n): for j in range(n): …

O(log n) 对数阶:每次循环规模减半(如二分查找、二叉搜索树)。
Python例子:while n > 1: n = n // 2。

计算原则:忽略常数项和低阶项。比如循环了 2n + 5 次,时间复杂度就是 O(n);双层循环 n * n + n 次,就是 O(n²)。

2.3 空间复杂度

它衡量的是算法在运行过程中临时占用多少额外的内存空间,随输入规模
𝑛的增长趋势。通常不计入输入数据本身占用的空间,只算“额外”申请的空间。
怎么计算?(看变量和递归)
• O(1) 常数阶:只用了几个固定的变量(如 i, sum, temp)。
◦ Python例子:两数交换 a, b = b, a。

• O(n) 线性阶:开辟了与输入规模成正比的额外数据结构。
◦ Python例子:new_list = [0] * n,或者把一个字典复制了一份 dict2 = dict1.copy()。

• O(n²) 平方阶:二维数组/矩阵。
◦ Python例子:matrix = [[0]*n for _ in range(n)]。

• 递归的空间复杂度:取决于递归调用栈的最大深度。比如计算阶乘的递归,空间复杂度是 O(n)。

2.4 测试代码

对于时间复杂度与空间复杂度,其实都不是实际准确的真实的消耗,如果你感兴趣可以自己测测:

  1. 测时间(实际耗时):
importtimeit# 测试一段代码执行1000次的时间time_taken=timeit.timeit('sum([i for i in range(1000)])',number=1000)print(f"耗时:{time_taken}秒")
  1. 测空间(内存占用)
importsys a=[1,2,3]print(sys.getsizeof(a))# 输出列表本身占用内存大小(字节)

2.5 练习题(仅仅算时间复杂度)

1.𝑂(𝑛)

def func1(n):
total = 0
for i in range(n):
total += i
return total
2.𝑂(n**2)

def func2(n):
total = 0
for i in range(n):
for j in range(i):
total += 1
return total
3.𝑂(2**𝑛)

def func1(n):
total = 0
for i in range(n):
total += i
return total

day3 数据结构

3.1 数据结构分类

常见的数据结构包括数组、链表、栈、队列、哈希表、树、堆、图,它们可以从“逻辑结构”和“物理结构”两个维度进行分类。

3.1.1 逻辑结构:线性与非线性

  1. 线性结构(Linear)
    • 概念:数据元素之间是“一对一”的关系。就像排队买票一样,除了队伍最前面的人(无前驱)和最后面的人(无后继),中间的每个人都有且只有一个前驱和一个后继。
    • 特点:数据连成一条直线,有唯一的“第一个”和“最后一个”。
    • 常见例子:
    ◦ 数组 (Array) / 列表 (List)
    ◦ 链表 (Linked List)
    ◦ 栈 (Stack):后进先出(就像一叠盘子)。
    ◦ 队列 (Queue):先进先出(就像排队过安检)。
  2. 非线性结构(Non-linear)
    • 概念:数据元素之间是“一对多”或“多对多”的关系。一个节点可能连着好几个其他节点。
    • 特点:数据不再是简单的一条直线,而是呈现出层次或网状关系。
    • 常见例子:
    ◦ 树 (Tree):一对多关系。比如公司的组织架构图、电脑里的文件夹目录(一个文件夹里有多个子文件夹)。
    ◦ 图 (Graph):多对多关系。比如微信的好友关系网、城市之间的航线图、地铁线路图。
    ◦ 集合 (Set):元素之间除了“同属于一个集合”外,没有其他关系。

3.1.2 物理结构:连续与分散

物理结构描述的是数据的逻辑结构在计算机内存中真实存储的形式。不管你在逻辑上怎么想,计算机内存就是一个个带地址的小格子。

  1. 连续结构(Sequential Storage)
    • 概念:数据在内存中占据一块连续的空间。就像电影院买票,你们10个朋友必须买连在一起的10个座位。
    • 代表:数组(Python中的 list 底层就是连续数组)。
    • 特点与优缺点:
    ◦ 优点:因为地址是连着的,计算机只要知道起始地址和偏移量,就能瞬间算出第 N 个元素在哪。所以“随机访问”极快(时间复杂度 O(1))。
    ◦ 缺点:大小固定(扩容麻烦);在中间插入或删除数据时,为了保持连续,必须把后面的元素整体往后挪或往前移,代价很大(时间复杂度 O(n))。而且在内存中找这么大一块连续的空地,有时很难。
  2. 分散结构(Linked Storage)
    • 概念:数据在内存中散落各处,不需要连续。为了把它们连起来,每个数据元素不仅存数据,还要存一个“指针”(或引用),指向下一个元素在哪里。就像寻宝游戏,每个线索纸条上写着下一个线索藏在哪里。
    • 代表:链表(如 Python 中自己实现的 Node)。
    • 特点与优缺点:
    ◦ 优点:不需要连续空间,内存哪里有空位就塞哪里;插入和删除数据非常方便,只要修改“指针”的指向即可(时间复杂度 O(1))。
    ◦ 缺点:“随机访问”极慢。如果你想找第 5 个元素,不能直接跳过去,必须从第 1 个开始,顺着指针挨个找(时间复杂度 O(n))。此外,存储指针会额外消耗一点内存空间。

3.2 基本数据类型

•整数类型 byte、short、int、long 。
• 浮点数类型 float、double ,用于表示小数。
• 字符类型 char ,用于表示各种语言的字母、标点符号甚至表情符号等。
• 布尔类型 bool ,用于表示“是”与“否”判断。
•等等

day4 数组与链表

数组(Array)和链表(Linked List)是数据结构中最基础、最核心的两种线性结构。一般在python里面表现为列表。
1.数组(连续存储)
在内存中占据一块连续的空间。就像电影院的连座票,只要知道第一排的起始位置,找第 5 个座位直接走过去就行。
2.链表(分散存储)
在内存中分散存储。每个节点(Node)不仅存数据,还存着下一个节点的“地址(指针)”。就像寻宝游戏,每张纸条不仅写着线索,还写着下一个线索藏在哪里。分为单链表(只能单向走)、双链表(能往前也能往后走)、循环链表(首尾相连)。

day5 栈与队列

stack 堆叠;collection收集;deque=double-ended queue 双重的 端头的 队列;append 追加,该方法无返回,原地改,appendleft从左边加入;pop 弹出 从后面开始删除 popleft pop+left 弹出 左边,从左端弹出,从左端删除;push压入、推进去;linkedlist =linked +list 被连接起来 + 列表=链表;is_empty 是+空,是不是空;peek偷看;to_list 转变+列表

栈:

(竞赛中我们一般用列表中最后一个作为栈顶,因为栈是后进先出,这样pop(从后面删除)与push(从后面推入)会更快)
遵循先入后出逻辑的线性数据结构。(先进来的在栈底,后进来的在栈顶,就像往瓶子里面丢石头,先丢进去都在里面,后丢进去的在外面,我们要将石头拿出来时,只能先从外面拿)
#Python 没有内置的栈类,可以把 list 当作栈来使用

#元素入栈stack.append(1)
#访问栈顶元素peek: int = stack[-1]
#元素出栈(默认删除最后一个)pop: int = stack.pop()
int:=stack.pop(0)#删除第一个
pop有返回
#获取栈的长度size: int = len(stack)
len有返回
#判断是否为空is_empty: bool = len(stack) == 0
if len(stsck) ==0成立,返回Ture

# 初始化栈# Python 没有内置的栈类,可以把 list 当作栈来使用stack:list[int]=[]# 元素入栈stack.append(1)stack.append(3)stack.append(2)stack.append(5)stack.append(4)# 访问栈顶元素peek:int=stack[-1]# 元素出栈pop:int=stack.pop()# 获取栈的长度size:int=len(stack)# 判断是否为空is_empty:bool=len(stack)==0

队列:

遵循先入先出规则的线性数据结构。(排队)

fromcollectionsimportdeque# 初始化队列# 在 Python 中,我们一般将双向队列类 deque 当作队列使用# 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不推荐que:deque[int]=deque()# 元素入队que.append(1)que.append(3)que.append(2)que.append(5)que.append(4)# 访问队首元素front:int=que[0]# 元素出队(默认删除第一个元素)pop:int=que.popleft()# 获取队列的长度size:int=len(que)# 判断队列是否为空is_empty:bool=len(que)==0

链表实现

classLinkedListQueue:"""基于链表实现的队列"""def__init__(self):"""构造方法"""self._front:ListNode|None=None# 头节点 frontself._rear:ListNode|None=None# 尾节点 rearself._size:int=0defsize(self)->int:"""获取队列的长度"""returnself._sizedefis_empty(self)->bool:"""判断队列是否为空"""returnself._size==0defpush(self,num:int):"""入队"""# 在尾节点后添加 numnode=ListNode(num)# 如果队列为空,则令头、尾节点都指向该节点ifself._frontisNone:self._front=node self._rear=node# 如果队列不为空,则将该节点添加到尾节点后else:self._rear.next=node self._rear=node self._size+=1defpop(self)->int:"""出队"""num=self.peek()# 删除头节点self._front=self._front.nextself._size-=1returnnumdefpeek(self)->int:"""访问队首元素"""ifself.is_empty():raiseIndexError("队列为空")returnself._front.valdefto_list(self)->list[int]:"""转化为列表用于打印"""queue=[]temp=self._frontwhiletemp:queue.append(temp.val)temp=temp.nextreturnqueue

双向队列(两边都可以进,也都可以出,双向队列甚至可以当作栈来用):

提供了更高的灵活性,允许在头部和尾部执行元素的添加或删除操作。

fromcollectionsimportdeque# 初始化双向队列deq:deque[int]=deque()# 元素入队deq.append(2)# 添加至队尾deq.append(5)deq.append(4)deq.appendleft(3)# 添加至队首deq.appendleft(1)# 访问元素front:int=deq[0]# 队首元素rear:int=deq[-1]# 队尾元素# 元素出队pop_front:int=deq.popleft()# 队首元素出队pop_rear:int=deq.pop()# 队尾元素出队# 获取双向队列的长度size:int=len(deq)# 判断双向队列是否为空is_empty:bool=len(deq)==0

例题:

例题来源洛谷,哪里有更好的环境,链接在下面:
https://www.luogu.com.cn/problem/list?type=luogu&keyword=%E6%A0%88&page=1&orderBy=difficulty&order=asc

input=sys.stdin.read().split()覆盖写法 :sys system系统; stdin standard input 标准输入;split拆分,split返回的是列表;算法中后者更快,因此常常这么写
map(转化函数,可迭代对象),map映射
*,可以用于解包列表
global,全局的,这里是用来声明dfs(depth first search 深度优先搜索)函数里面的全局变量;
range 范围

1 栈

小鱼的数字(入门):

importsysinput=sys.stdin.read().split()a=list(map(int,input))print(*a[-2::-1])

更快:

importsysprint(*sys.stdin.read().split()[-2::-1])

[NOIP 2003 普及组] 栈(普及)

解释为什么n=3时有5种:
1 2 3:进1,出1;进2,出2;进3,出3。
1 3 2:进1,出1;进2,进3,出3,出2。
2 1 3:进1,进2,出2,出1;进3,出3。
2 3 1:进1,进2,出2;进3,出3;最后出1
3 2 1:进1,进2,进3;然后连续出栈3次。

用递归一个个来(很慢)(这个是为了便于搞懂原理)
dfs(下一个准备入栈的数字,当前栈长)
注意3个if是同一级别,每一个相当于一个分支

n=int(input())ans=0defdfs(in_num,stack_size):globalansifin_num>nandstack_size==0:ans+=1returnifin_num<=n:dfs(in_num+1,stack_size+1)ifstack_size>0:dfs(in_num,stack_size-1)dfs(1,0)print(ans)

从数学角度来:
f ( n ) = f ( 0 ) f ( n − 1 ) + f ( 1 ) f ( n − 2 ) + ⋯ + f ( n − 1 ) f ( 0 ) . f(n) = f(0)f(n-1) + f(1)f(n-2) + \dots + f(n-1)f(0) .f(n)=f(0)f(n−1)+f(1)f(n−2)+⋯+f(n−1)f(0).

f ( n ) = ∑ k = 1 n f ( k − 1 ) × f ( n − k ) . f(n) = \sum_{k=1}^{n} f(k-1) \times f(n-k) .f(n)=k=1∑n​f(k−1)×f(n−k).

n=int(input())dp=[0]*(n+1)dp[0]=1foriinrange(1,n+1):forjinrange(i):dp[i]+=dp[j]*dp[i-1-j]print(dp[n])

这其实就是卡特兰数:
C 1 = 1 , C n = C n − 1 × 4 n − 2 n + 1 . C_1=1, C_n = C_{n-1} \times \frac{4n - 2}{n + 1} .C1​=1,Cn​=Cn−1​×n+14n−2​.

n=int(input())ans=1foriinrange(1,n+1):ans=ans*(4*i-2)//(i+1)print(ans)

2.队列

【深基4.例11】数列求和

n=int(input())ans=0foriinrange(1,n+1):ans+=iprint(ans)

或

n=int(input())print(n*(n+1)//2)

[NOIP 2006 普及组] 数列

暴力枚举

importsys k,n=map(int,input().split())powers=[]p=1for_inrange(11):powers.append(p)p*=k ans_list=[]defdfs(index,current_sum):ifindex==len(powers):ans_list.append(current_sum)returndfs(index+1,current_sum)dfs(index+1,current_sum+powers[index])dfs(0,0)ans_list=sorted(list(set(ans_list)))print(ans_list[n])

找规律:

第 N 项开关状态(选 / 不选)算式推导实际数值对应的二进制数
第 1 项只选 3⁰(001)3⁰11
第 2 项只选 3¹(010)3¹310
第 3 项选 3⁰、3¹(011)3⁰ + 3¹411
第 4 项只选 3²(100)3²9100
第 5 项选 3⁰、3²(101)3⁰ + 3²10101
第 6 项选 3¹、3²(110)3¹ + 3²12110
第 7 项选 3⁰、3¹、3²(111)3⁰ + 3¹ + 3²13111

读表要点:

  • 括号里的三位二进制数就是「开关状态」:从右往左依次对应 3⁰、3¹、3²,某一位为1表示选中该项,为0表示不选。
  • 二进制011的十进制值是 3,但它表示的是「第 3 种组合」,注意区分「编号」与「实际数值」。
  • 实际数值 = 所有被选中项之和,例如101对应 3⁰ + 3² = 1 + 9 = 10。
  • 这种「用二进制位表示选或不选」的思路,正是状态压缩 DP 与子集枚举的基础,后续遇到「n 个物品选或不选」的问题都可以套用。
k,n=map(int,input().split())ans=0p=1whilen>0:ifn%2==1:ans+=p p*=k n//=2print(ans)

ok!第一次学习就完了!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询