一、什么是算法
算法:一个计算过程,解决问题的方法。
程序 = 数据结构 + 算法
二、时间复杂度
时间复杂度:用来评估算法效率的一个东西
print('Hello World!') O(1) for i in range(n): O(n) print('Hello World!') for i in range(n): O(n²) for j in range(n): print('Hello World!') for i in range(n): O(n³) for j in range(n): for k in range(n): print('Hello World!')
while n>1: O(log₂n)或者O(logn)
print(n)
n= n//2
时间复杂度是用来估计算法运行时间的一个式子(单位),一般来说,时间复杂度高的算法比复杂度低的算法慢
常见的时间复杂度(按效率排序)
O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<O(n²logn)<O(n³)
不常见的的时间复杂度:O(n!),O(2ⁿ),O(nⁿ)
如何快速的判断时间复杂度:
1.循环减半的过程 O(logn)
2.几层循环就是n的几次方的复杂度
三、空间复杂度
空间复杂度:用来评估算法内存占用大小的一个式子
因为内存空间越来越大,相应时间要求越来越高,一般采用‘空间换时间’作为基本策略
四、列表查找
列表查找:从列表中查找指定元素的索引/下标 输入:列表和要查找的元素 输出:索引/下标/未找到
列表的查找分为:顺序查找和二分查找
1.顺序查找:从列表的第一个元素进行查找,顺序搜索,直到找到位置 (时间复杂度为O(n))
2.二分查找:从有序列表的候选区开始,通过比对查找值和候选区中间的的大小,渐渐缩小候选区大小,即查找范围逐步缩小 (时间复杂度为O(logn))
例子: 代码实现顺序列表中查找某个值所在的索引位置。
li = list(range(200,100000,10)) def bin_search(li,value): low = 0 high = len(li) -1 while low <= high: mid = (low + high) // 2 if li[mid] > value: high = mid - 1 elif li[mid] < value: low = mid + 1 else: return mid return None if __name__ == '__main__': mid = bin_search(li,10001) print(mid) if mid: print(li[mid])
# 使用递归实现 li = list(range(200,100000,10)) def bin_search(li,value,low=0,high=len(li)-1): mid = (low + high) // 2 if low <= high and li[mid] > value: return bin_search(li,value,low,mid-1) elif low <= high and li[mid] < value: return bin_search(li,value,mid+1,high) elif li[mid] == value: return mid else: return None if __name__ == '__main__': mid = bin_search(li,1000) print(mid) if mid: print(li[mid])
备注:假设排序的时间复杂度为O(nlogn),如果对列表进行一次查找建议使用顺序查找,需要对列表进行多次查找操作建议先排序,后反复进行二分查找。
五、列表排序
排序方法:冒泡排序、选择排序、插入排序、快速排序、堆排序、归并排序、希尔排序
1.冒泡排序(时间复杂度O(n²))
原理:一趟分别两两比较大小,一趟结束后无序区中最大的元素放到无序区的最后,也是有序区的最前。
import random def bubble_sort(li): for i in range(len(li)-1): # 趟数 for num in range(len(li)-i-1): if li[num]>li[num+1]: li[num],li[num+1] = li[num+1],li[num] if __name__ == '__main__': li = list(range(50, 10000, 3)) random.shuffle(li) bubble_sort(li) print(li)
冒泡排序优化:在执行排序的一趟中没有进行交换,则此时序列以及是有序,可以直接结算算法。(时间复杂度O(n²))
2.选择排序
原理:一趟遍历最小的元素放到第一个位置,反复进行
import random def select_sort(li): for i in range(len(li)-1): # 这里的-1表示最后一个可以不用进行排序 mid = i for j in range(i+1,len(li)): # 这里没有-1 if li[mid] > li[j]: mid = j li[i],li[mid] = li[mid],li[i] if __name__ == '__main__': li = list(range(50, 1000, 1)) random.shuffle(li) select_sort(li) print(li)
3.插入排序(时间复杂度O(n²))
要点: 每次取出的牌和手中有序的牌
# 插入排序 import random def inster_sort(li): for i in range(1,len(li)): j = i-1 mid = li[i] # 拿到的牌 while j >= 0 and mid < li[j]: li[j+1] = li[j] j -= 1 li[j+1] = mid if __name__ == '__main__': li = list(range(50, 100000, 13)) random.shuffle(li) inster_sort(li) print(li)
小结:冒泡排序,选择排序,插入排序
时间复杂度O(n²)
空间复杂度O(1)
4.快速排序 事件复杂度O(nlogn)
快速排序思路:一、取一个元素P(第一个元素),使元素p归位;
二、列表被p分成两部分,左边都比p小,右边都比p大;
三、递归完成排序;(即整理和递归)
# 快速排序 import random def partiton(li,low,high): mid = li[low] while low < high: while low < high and li[high] >= mid: high -= 1 li[low] = li[high] while low < high and li[low] <= mid: low += 1 li[high] = li[low] li[low] = mid return low def quick_sort(li, low, high): if low < high: mid = partiton(li,low,high) quick_sort(li,low,mid-1) quick_sort(li,mid+1,high) if __name__ == '__main__': li = list(range(0, 1000000)) random.shuffle(li) quick_sort(li, 0, len(li)-1) print(li)
快速排序存在两个问题:
1.最坏情况
2.递归最大深度 通过sys.setrecursionlimit(100)可以设置递归深度
解决办法:
1.随机生成比对值版本的快速排序,即:不取第一个值作为对比值,使用列表中随机的值来先和第一值进行交换,然后在继续进行操作。存在最坏情况,只是没法人为设计出来。
六、高级排序
堆排序
堆排序基础:
树: 树是一种数据结构 比如:目录结构
树的基本概念:根节点,叶子节点,树的深度(高度),树的度,孩子节点,父节点,子树。
二叉树:度不超过2的树(节点最多有两个叉)
满二叉树:除最后一层无子节点外,每一层上的所有结点都有两个子结点二叉树。
完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树
二叉树的存储方式:
链式存储和顺序存储
堆排序:
大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大。
小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小。
堆排序过程:
1.建立堆。
2.得到堆顶元素,为最大元素。
3.去掉堆顶,将对最后一个元素放到堆顶,此时可通过一次调整重新使堆有序。
4.堆顶元素为第二大元素。
5.重复步骤3,直到堆变为空。
堆排序注意事项:
1.在调整各个子树的时候,子节点的位置可以方便的设置为整个树最有一个节点。(重要)
堆排序代码:
# 向下调整 def sift(li, low, high): i = low j = 2 * i + 1 tmp = li[low] while j <= high: if j + 1 <= high and li[j] < li[j + 1]: j = j + 1 if tmp < li[j]: li[i] = li[j] i = j j = 2 * i + 1 else: break li[i] = tmp def heap_sort(li): for i in range(len(li) // 2 - 1, -1, -1): # 依次调整每棵子树 sift(li, i, len(li) - 1) for i in range(len(li) - 1, -1, -1): # 挨个出数 li[0],li[i] = li[i],li[0] sift(li, 0, i - 1) if __name__ == '__main__': li = [6, 8, 1, 9, 3, 0, 7, 2, 4, 5] heap_sort(li) print(li)
def sift(list_obj, low, high): # 调整树 tmp = list_obj[low] i = low j = 2 * i + 1 while j <= high: if j < high and list_obj[j] < list_obj[j + 1]: j = j + 1 if tmp < list_obj[j]: list_obj[i] = list_obj[j] i = j j = 2 * i + 1 else: break list_obj[i] = tmp def heap_sort(list_obj): for i in range(len(list_obj) // 2 - 1, -1, -1): # 调整各个子树 sift(list_obj, i, len(list_obj) - 1) for i in range(len(list_obj) - 1, -1, -1): # 大的数换到最后,继续调整树 list_obj[0], list_obj[i] = list_obj[i], list_obj[0] sift(list_obj, 0, i - 1) if __name__ == '__main__': list_obj = [6, 8, 1, 9, 3, 0, 7, 2, 4, 5] heap_sort(list_obj) print(list_obj)