1 Python算法概述
1.1 算法概念
数据结构:数据的存储结构,对这些数据进行操作。
算法:运算方法,解决问题的方法和思想。
1.2 算法的意义与重要性
(1)让代码可行、高效、低占用资源
(2)明白代码底层逻辑,方便使用和阅读代码
(3)面试必考,高薪企业看中
算法的5个基本要素:
(1)输入:0个或多个输入
(2)输出:至少有1个或多个输出
(3)有穷尽:可以终止
(4)确定性:不会有歧义
(5)可行性:有效性,可执行的步骤,明确的落地执行的
1.3 算法举例
算法是针对问题出现的,有问题的地方就有算法。
算法思想(加粗为5大常用算法):
(1)分治法:划分任务,越来越简单
(2)贪心法:1,3,5,找9元钱
(3)穷举法:所有组合方案列出来,比如试密码。答案全,但耗费精力
(4)递归法:调用函数本身,递归到本身
(5)递推法:数列根据前几项规律,得到第n项的规律
(6)回溯法:走迷宫,如果不通回到上个路口找其他路径,解决不下去,回到 回溯点,找其他方案
(7)动态规划法:用于数学中,寻找最优解
(8)迭代法:旧值 推导出 新值
(9)分支界限法:大问题找出最优解,找出各分支的上界和下届,再综合得到整个的优解
1.4 算法书籍推荐
《啊哈!算法》:c语言
《算法图解》:Python,3-4小时可以看完
《数据结构与算法 Python语言实现》
刷题:
LeetCode、牛客网nowcoder
2 时间复杂度
我们将算法执行运算的操作数丢弃低阶项,再去掉所有的系数。
在它前门加一个O,就是大O表示法
O(n):其中n是操作数
3 算法分析法则
3.1 一般法则
(1)for循环:循环体为O(n),循环次数为m,则总的时间复杂度=O(nm)
(2)嵌套的for循环:各个循环次数分别是a,b,c,则总的时间复杂度=O(nab…)
(3)顺序语句:各个语句的运行时间求和即可(或者说取较大值)
(4)if/else语句:总的时间复杂度=其中复杂度对打路径
3.2 常见复杂度
li.append()
li.insert()
4 二分法查找O(logn)
二分法:在有序数组中查找某一特定元素的搜索算法
搜索过程从数组的中间元素开始:
如果中间元素是正好要找的元素,则搜索过程结束;
如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较;
如果在某一步骤数组为空,则代表找不到。
# 先决条件:有序数组
# 数据类型有范围:其他语言如java采用表达式start+(end-start)/2可以防止溢出
# start =mid +1和end=mid-1,防止死循环
# 数据量不可过大,防止内存溢出
# 时间复杂度:O(log n)
def binary_search(arr,key):
start = 0
end = len(arr)-1
while start <= end:
mid = (start+end) // 2
if arr[mid] < key:
start = mid +1
elif arr[mid] > key:
end = mid + 1
else:
return mid
return -1
5 数组vs链表
5.1 定义区别
数组:必须连续,存取麻烦;直接访问。为了解决计算机定址问题出现
链表:不要求连续,但需要内存地址标记,查找麻烦;顺序访问。作为原始数据类型存在。
5.2 时间复杂度区别
操作 | 链表 | 数组 |
---|---|---|
查找 | O(n) | O(1) |
在头部插入/删除 | O(1) | O(n) |
在尾部插入/删除 | O(n) | O(1) |
在中间插入/删除 | O(n) | O(n) |
区别总结:
(1)链表插入/删除时间复杂度:当插入/删除指针当前指向的节点时,时间复杂度是O(1);当插入/删除某个给定值的节点时,我们需要遍历链表,时间复杂度是O(n)。
(2)链表插入/删除时间复杂度O(n):需要进行遍历引起的;
数组插入/删除时间复杂度O(n):数据的拷贝覆盖导致的;
5.3 原理区别
插入删除很少,查询非常多,不会out of memory,采用数组(局部性原理,后续访问在一级缓存中);
频繁插入、遍历,查询检索很少,采用链表(每次都是内存访问节点)。
5.4 注
数据结构:数据的组织和存储方式,决定数据结构;
实现进行封装:封装数据类型。
6 线性表
6.1 线性表(线性逻辑和顺序):
(1)顺序表:数组(连续内存空间、类型相同)、引用数组(地址存为数组,通过地址访问,解决不同类型数据的问题)、动态数组(list)
(2)链表:单链表、双链表
(3)栈:先进后出(LIFO)
(4)队列:先进先出(FIFO)
(5)等等
6.2 栈(stack)&队列
运算过程中保存数据的,遇到不能确定的数据,缓冲存储结构。
栈:或叫堆栈、堆叠。先进后出(LIFO)
队列:先进先出(FIFO)
7 Python常见算法
7.1 选择排序
# 选择排序:不断选择最小的元素进行排序
# 运行时间和输入无关;数据移动是最少的。
# 时间复杂度:O(n^2),空间复杂度O(1)
def selection_sort(li): # 不断选择无序序列中最小的元素,放在第1个,再将该元素置于有序序列的最后
for i in range(len(li)):
min_index = i
for j in range(i+1, len(li)): # 找到值最小的元素index
if li[min_index] > li[j]:
min_index = j
li[i], li[min_index] = li[min_index], li[i] #交换第i个元素和最小值的元素
7.2 冒泡排序
# 冒泡排序
# 运行时间和输入无关;
# 时间复杂度:O(n^2)
def bubble_sort(li): #两两比较,大的向右移动
for i in range(len(li)-1): # i表示第几趟,共n-1趟
for j in range(len(li)-i-1): # j 表示每趟比较的次数,为n-1-i次
if li[j] > li[j+1]: # 如果前者大于后者
li[j], li[j+1] = li[j+1], li[j] # 交换两者数值(也就是小于等于时,数值不变)
# 优化:时间复杂度:O(n)
def bubble_sort_1(li):
for i in range(len(li)-1): # i表示第几趟,共n-1趟
exchange = False
for j in range(len(li)-i-1): # j 表示每趟比较的次数,为n-1-i次
if li[j] > li[j+1]: # 如果前者大于后者
li[j], li[j+1] = li[j+1], li[j] # 交换两者数值(也就是小于等于时,数值不变)
exchange = True
if not exchange:
return li
7.3 插入排序
# 插入排序
# 运行时间取决于输入元素的顺序,有序比乱序、逆序快的多
# 应用:1. 数组中每个元素距离它的最终位置都不远;2. 一个有序的大数组接一个小数组;
# 3. 数组中只有几个元素的位置不正确。
def insertion_sort(li): # 无序序列中拿一个元素,扫描有序序列并插入
for i in range(1, len(li)):
tmp = li[i] # 刚摸到的牌
j = i -1
while j >= 0 and li[j] > tmp: # 当j存在且大于tmp时,互换位置,比较前一个j;直到j不存在 或者 小于tmp时退出循环
li[j+1] = li[j]
j -= 1
li[j+1] =tmp # 退出while循环的时候,将tmp插入当前j的后面一个位置
# 希尔排序:插入排序的优化
# 将待排序列划分为若干组,每组进行插入排序。完成后进行最后一步普通的插入排序。
# 各个子数组都很短;排序之后子数组都是部分有序的
# 时间复杂度:pass
7.4 归并排序
# 归并排序
# 分解问题;解决问题;合并问题
# 自顶向下,自底向上
def merge_sort(li): # 拆序列至1个元素,合并排序为2个元素;再合并排序
if len(li) <= 1:
return li
middle = len(li)//2
left = merge_sort(li[:middle])
right = merge_sort(li[middle:])
return merge(left, right)
def merge(left, right):
i,j = 0, 0
result = []
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(left[j:])
return result
7.5 快速排序
# 快速排序
# 时间复杂度:O(nlog n),空间复杂度高
# 快速排序优化:
# 1.切换到插入排序(5-15个长度为小数组);2.三取样切分(三个元素挑中间的元素作为中轴元素);
# 3. 三向切分的快速排序
def quick_sort(li): # 找中轴元素(基准),小左大等右;再两侧分别找中轴元素,继续排序
if len(li) < 2:
return li
else:
base = li[0] #选取中轴元素
left =[elem for elem in li[1:] if elem < base] #小的放左边
right = [elem for elem in li[1:] if elem > base] #大的放右边
return quick_sort(left) + [base] + quick_sort(right)