一、复习:
递归的两个特点:
1、调用自身。
2、结束条件。
1 1.def func1(x) 2 print(x) 3 func1(x-1) 4 5 6 2.def func2(x) 7 if x>0: 8 print(x) 9 func2(x+1) 10 11 12 3.def func3(x) 13 if x>0: 14 print(x) 15 func3(x-1) 16 17 18 4.def func4(x) 19 if x>0: 20 func4(x-1) 21 print(x)
看上列代码如果是func1跟func2就是无限循环,没有合理的结束条件。func3跟func4就是有合理条件,可以循环结束。
ps:但是func3跟func4虽然都没问题。但是print 的方式并不同,func3输出5.3.2.1。但是func4的输出结果是1.2.3.4.5 因为他每次循环是没有走print就进入了下一个循环 之中,往外出的时候才会进行打印。
二、时间复杂度
类比生活中的一些事件,估计时间:
1.眨一下眼 一瞬间/几毫秒
2.口算“29+68” 几秒
3.烧一壶水 几分钟
4.睡一觉 几小时
5.完成一个项目 几天/几星期/几个月
6.飞船从地铁飞出太阳系 几年
时间复杂度的表示方式
1 print('Hello World') 2 print('Hello Python') 3 print('Hello Algorithm') 4 #用时间复杂度来讲是O(1) 5 6 for i in range(n): 7 print('Hello World') 8 for i in range(n): 9 print('Hello World') 10 #用时间复杂度来讲是O(n²) 11 12 for i in range(n): 13 for i in range(n): 14 print('Hello World') 15 #用时间复杂度来讲是O(n²) 16 17 while n > 1: 18 print(n) 19 n = n // 2 20 #用时间复杂度来讲是O(logn)
时间复杂度-小结:
1.时间复杂度是用来估计算法运行事假你的一个式子(单位)。
2.一般来说,时间复杂度高的算法比复杂度低的算法慢。
3.常见的时间复杂度(按效率排序)
O(1)<O(login)<O(n)<O(nlogn)<O(n²)<O(n²logn)<O(n³)
4.不常见的时间复杂度(看看就好)
O(n!) O(2^n) O(n^n)
5.如何一眼判断时间复杂度?
循环减半的过程---O(logn)
几次循环就是n的几次方的复杂度
三、空间复杂度
1.空间复杂度:用来评估算法内存占用大小的一个式子
2.“空间换时间”
四、列表查找:从列表中查找指定元素
输入:列表、待查找元素
输出:元素下标或未查到元素
顺序查找:
从列表第一个元素开始,顺序进行搜索,直到找到为止。
1 def ordinary_lookup(number): 2 dict = range(1000) 3 for i in dict: 4 if i == number: 5 print(i) 6 break 7 ordinary_lookup(200)
二分查找
从有序列表的候选区data[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半。
1 import time,random 2 3 def cal_time(func): 4 def wrapper(*args,**kwargs): 5 ti = time.time() 6 x = func(*args,**kwargs) 7 ti2 = time.time() 8 print('time cost:',func.__name__,ti2-ti) 9 return x 10 return wrapper 11 12 @cal_time 13 def bin_search(data_set,val): 14 low = 0 15 high = len(data_set) - 1 16 while low <= high: 17 mid = (low + high)//2 18 if data_set[mid] == val: 19 return mid 20 elif data_set[mid] <val: 21 low = mid + 1 22 else: 23 high = mid - 1 24 return 25 26 27 def _binary_search(dataset, find_num): 28 if len(dataset) > 1: 29 mid = int(len(dataset) / 2) 30 if dataset[mid] == find_num: # find it 31 #print("找到数字", dataset[mid]) 32 pass 33 elif dataset[mid] > find_num: # 找的数在mid左面 34 #print("\033[31;1m找的数在mid[%s]左面\033[0m" % dataset[mid]) 35 return binary_search(dataset[0:mid], find_num) 36 else: # 找的数在mid右面 37 # print("\033[32;1m找的数在mid[%s]右面\033[0m" % dataset[mid]) 38 return binary_search(dataset[mid + 1:], find_num) 39 else: 40 if dataset[0] == find_num: # find it 41 # print("找到数字啦", dataset[0]) 42 pass 43 else: 44 pass 45 # print("没的分了,要找的数字[%s]不在列表里" % find_num) 46 47 @cal_time 48 def binary_search(data_set, val): 49 return _binary_search(data_set, val) 50 #不要在递归里面直接加装饰器会无限调用。 51 #循环要比递归有效率 52 #切片是一个特别耗费时间的操作 53 54 data = list(range(100000)) 55 bin_search(data,152) 56 binary_search(data, 152)
练习:使用二分查找法,进行查找相应的id号码。
1 def random_list(n): 2 ids = list(range(1001,1001+n)) 3 result = [] 4 a1 = ['周','x','x,'x','x','x','x','x','x'] 5 a2 = ['杰','x','x','x','x','x','x','x'] 6 a3 = ['伦','x','','','x','x','x','x','x','x'] 7 for i in range(n): 8 age = random.randint(18,60) 9 id = ids[i] 10 name = random.choice(a1)+random.choice(a2)+random.choice(a3) 11 name_dict = {'id':id,'name':name,'age':age} 12 result.append(name_dict) 13 14 return result 15 16 #用于生成一个关于姓名的列表包含字典 17 18 def bin_search(val): 19 data_set = random_list(10000) 20 low = 0 21 high = len(data_set) - 1 22 while low <= high: 23 mid = (low + high)//2 24 if data_set[mid]['id'] == val: 25 return mid 26 elif data_set[mid]['id'] <val: 27 low = mid + 1 28 else: 29 high = mid - 1 30 return 31 print(bin_search(1160)) 32 33 #用二分查找进行查找生成列表内的id号在第几个
各种排序演示网站http://www.atool.org/sort.php
五、列表排序:将无序列表变为有序列表
1.应用场景:
各种榜单
各种表格
给二分排序用
给其他算法用
2.使用方式
输入:无序列表
输出:有序列表
分为生序和降序
3.排序low b 三人组:
冒泡排序
选择排序
插入排序
4.快速排序
5.排序NB两人组:
堆排序
归并排序
6.什么人用的排序
基数排序
希尔排序
桶排序
冒泡排序
首先,列表每两个相邻的数,如果前边的比后面的打,那么交换这两个数...
需要记住两个概念,趟,无序区。
时间复杂度:O(n²)
1 def bubble_sort(li): 2 for i in range(len(li)-1): #记录多少趟 3 flag = False 4 for j in range(0,len(li)-i-1): #交换了多少次 5 if li[j] > li[j+1]: 6 li[j],li[j+1] = li[j+1],li[j] #交换 7 flag = True 8 if not flag: 9 break
选择排序
一趟便利记录最小的数,放到第一个位置;
再一趟遍历记录剩余列表中最小的数,继续放置;
需要记住两个概念,无序区,最小数的位置。
时间复杂度:O(n²)
1 def select_sort(li): 2 for i in range(len(li)-1): 3 min_loc = i 4 for j in range(i+1,len(li)): 5 if li[j] < li[min_loc]: 6 min_loc = j 7 li[min_loc],li[i] = li[i],li[min_loc]
插入排序
列表被分为有序区和无序区两个部分。最初有序区只有一个元素。
每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空。
需要记住两个概念,摸到的牌,手里的牌。
时间复杂度:O(n²)
1 def insert_sort(li): 2 for i in range(1,len(li)): 3 tmp = li[i] 4 j = i - 1 5 while j>=0 and li[j] > tmp: 6 li[j+1] = li[j] 7 j = j - 1 8 li[j+1] = tmp
快速排序:快
写好的排序算法里最快的
快的排序算法里最好写的
时间复杂度:O(nlogn)
需要记住两个概念,整理,递归。
快排的思路:
1.取一个元素p(第一个元素),使元素p归位;
2.列表被p分成两个部分,左边都比p小,右边都比p大;
3.递归完成排序。
1 def partition(data,left,right): 2 tmp = data[left] 3 while left < right and left < right: 4 while data[right] >= tmp: 5 right = right - 1 6 data[left] = data[right] 7 while left < right and data[left] <= tmp: 8 left = left +1 9 data[right] = data[left] 10 data[left]= tmp 11 return left 12 13 def quick_sort(data,left,right ): 14 if left < right: 15 mid = partition(data,left,right) 16 quick_sort(data,left,mid - 1) 17 quick_sort(data,mid + 1 ,right)
堆排序--树与二叉树简介
树是一种数据结构 比如:目录结构
树是一种可以递归定义的数据结构
树是由n个节点组成的集合:
如果n=0,那这是一颗空树;
如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。
一些概念
根节点、叶子节点
树的深度(高度)
树的度
孩子节点/父节点
子树
特殊且常用的树--二叉树
二叉树:度不超过2的树(节点最多有两个叉)
两种特殊二叉树
满二叉树
完全二叉树
二叉树的存储方式
链式存储方式
顺序存储方式(列表)
父节点和左孩子节点的编号下标有什么关系?
0-1 1-3 2-5 3-7 4-9 i - 2i+1
父节点和右孩子节点的编号下标有什么关系
0-2 1-4 2-6 3-8 4-10 i - 2i+2
堆:
大根堆:一颗完全二叉树,满足任一节点都比其孩子节点大
小根堆:一个完全二叉树,满足任一节点都比其孩子节点小
堆排序过程
1.建立堆
2.得到堆顶元素,为最大元素。
3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序。
4.堆顶元素为第二大元素。
5.重复步骤3,直到堆变空。
时间复杂度:O(nlogn)
1 def sift(data,low,high): #调整 2 i = low 3 j = 2*i+1 4 tmp=data[i] 5 while j<=high: #省长已经是村民了 6 if j+1 <=high and data[j]< data[j+1]: 7 j+=1 8 if tmp <data[j]: 9 data[i] = data[j] 10 i=j 11 j=2*i+1 12 else: #省长能罩住下一级孩子 13 break 14 data[i] = tmp 15 16 def heap_sort(data): 17 n = len(data) 18 for i in range(n//2-1,-1,-1): #建堆时low变high不变 i代表low 19 sift(data,i,n-1) 20 #建堆完成 21 for j in range(n-1,-1,-1): #j代表high ,挨个出数时 high变low不变 22 data[0],data[j] = data[j],data[0] 23 sift(data,0,j-1)
归并排序
假设现在的列表分成两段有序,如何将其合成为一个有序列表
两各列表都是有序列表,从第一个数据拿出来进行对比,如果小的数据就拿出来。
时间复杂度:O(nlogn)
这种操作我们叫做一次归并。
分解:将列表越分越小直至分成一个元素。
一个元素是有序的。
合并:将两个有序列表归并,列表越来越大。
1 def merge(data,low,mid,high): 2 i = low 3 j = mid+1 4 ltmp = [] 5 while i <=mid and j<= high: 6 if data[i]<=data[j]: 7 ltmp.append(data[i]) 8 i += 1 9 else: 10 ltmp.append(data[j]) 11 j += 1 12 while i<=mid: 13 ltmp.append(data[i]) 14 i += 1 15 while j <= high: 16 ltmp.append(data[j]) 17 j += 1 18 data[low:high+1] = ltmp 19 # j = 0 20 # for i in range(low,high+1): 21 # data[i]=ltmp[j] 22 # j+=1 23 24 def merge_sort(data,low,high): 25 if low < high: 26 mid = (low+high)//2 27 merge_sort(data,low,mid) 28 merge_sort(data,mid+1,high) 29 merge(data,low,mid,high)
希尔排序
希尔排序是一种分组插入排序算法。
首先取一个整数d1 = n/2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进行直接插入排序;
取第二个整数d2 = d1/2,重复上述分组排序过程,直到di=1,既所有元素在同一组内进行直接插入排序。
希尔排序每趟并不适这些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。
排序-小结
快速排序、堆排序、并归排序 三种排序算法的时间复杂度都是O(nlogn)
一般情况下,就运行时间而言:
快速排序<并归排序<堆排序
三种排序算法的缺点:
快速排序:计算情况下排序效率低;不稳定
并归排序:需要额外的内存开销
堆排序:在快的排序算法中相对较慢;不稳定
排序方法 | 最坏情况 | 平均情况 | 最好情况 | 稳定性 | 代码复杂度 |
冒泡排序 | O(n²) | O(n²) | O(n²) | 稳定 | 简单 |
直接选择排序 | O(n²) | O(n²) | O(n²) | 不稳定 | 简单 |
直接插入排序 | O(n²) | O(n²) | O(n²) | 稳定 | 简单 |
快速排序 | O(n²) | O(nlogn) | O(nlogn) | 不稳定 | 较复杂 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | 不稳定 | 复杂 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | 稳定 | 较复杂 |
希尔排序 | O(1.3n) | 不稳定 | 较复杂 |
ps:python 内置函数有堆的使用 可以百度自己查询使用方法 heapq模块