失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 02_Python算法+数据结构笔记-冒泡排序-选择排序-插入排序-快排-二叉树

02_Python算法+数据结构笔记-冒泡排序-选择排序-插入排序-快排-二叉树

时间:2020-07-26 21:05:10

相关推荐

02_Python算法+数据结构笔记-冒泡排序-选择排序-插入排序-快排-二叉树

b站视频:路飞IT学城

清华计算机博士带你学习Python算法+数据结构_哔哩哔哩_bilibili

文章目录

#11 排序介绍#12 冒泡排序介绍#13 冒泡排序#14 选择排序#15 插入排序#16 快速排序原理介绍#17 快速排序代码实现#18 快速排序代码实现2#19 堆排序前传树的基础知识#20 堆排序前传二叉树的基础知识

#11 排序介绍

# 什么是列表排序# 常用排序算法介绍# 排序算法分析# 排序:将一组“无序”的记录序列调整为“有序”的记录序列。# 列表排序:将无序列表变为有序列表# 输入:列表# 输出:有序列表 升序与降序# 升序与降序 # 注:2个基本排序方式# 内置排序函数:sort()#-----------------------------------------------------------### 常见排序算法# ·排序Low B三人组 ·排序NB三人组 ·其他排序# 冒泡排序快速排序希尔排序# 选择排序堆排序计数排序# 插入排序归并排序基数排序

理解什么是排序

#12 冒泡排序介绍

### 冒泡排序 (Bubble Sort)# 列表每两个相邻的数,如果前面比后面大,则交换这两个数。# 一趟排序完成后,则无序区减少一个数,有序区增加一个数。# 代码关键点:趟、无序区范围 (重点)# 注:目的是为了排除来一个升序的列表#列表注:右边是下标# 1 8<-- 9 和 1比 ;9 >1 交换 9 1 9 8# 9 7<-- 8 和 9比 ;8 < 9 不交换1 7 <-- 比较到倒数第二个# 2 6<-- 8 和 2比 ;8 > 9 交换 8 2 8 6# 8 5<-- 7 和 8比 ;7 < 8 不交换2 5# 3 4<-- 7 和 3比 ;7 > 3 交换 7 3 7 4# 6 3<-- 7 和 6比 ;7 > 6 交换 7 6 3 3# 4 2<-- 7 和 4比 ;7 > 4 交换 7 4 6 2# 5 1<-- 7 和 5比 ;7 > 5 交换 4 5 4 1# 7 0 5 0 <--#这是冒泡排序的1趟 从小到上走一遍 最后箭头只会到倒数第2个元素 (因为不会往后比了)#冒泡排序 就像烧开水 气开了一样#这一趟下来 这1个最大的数上去了 这个位置肯定就是最后1个#上面的叫有序区(已经排好了的),下面的叫无序区#第二趟只要对整个无序区进行排序,因为9已经排好了的#第0趟有序区没有数#第1趟有序区 9#第2趟有序区 9 8#第3趟有序区 9 8 7#第4趟有序区 9 8 7 6#第5趟有序区 9 8 7 6 5#第6趟有序区 9 8 7 6 5 4# 注:每一趟出来1个数 但是叫第6趟 因为从第0趟开始数的#第7趟有序区 9 8 7 6 5 4 3#第8趟有序区 9 8 7 6 5 4 3 2#这1趟不用做了 9 8 7 6 5 4 3 2 1 #注:只剩下1个数的时候 不用做了#注:整个排序算法排了 n - 1 趟#注:循环1趟会出来1个最大的数### 代码关键点:趟、无序区范围 (重点)# 第0趟有序区没有数# 第1趟有序区有1个数#注:列表切片顾头不顾尾 n-2# 第2趟 ;n长度 i第i趟 ;n - i;无序区还剩n - i个数# 第2趟 n-i =9-2=7 还剩7个数 最上面那个数下标为6# 但箭头不会指到 下标为6 那 因为后面没有数了 所有指到下标为5的数 ;n-i-1

#13 冒泡排序

import random # 注:导入随机模块def bubble_sort(li): # 注:传入列表for i in range(len(li)-1): # 注:整个算法排了n - 1 趟;i表示 第 i 趟 且从0开始#上面定义走n-1趟# 注:每一趟里都有1个指针,箭头永远从0开始,到 n - i -1for j in range(len(li)-i-1): # 注:实际是确定的箭头 指针顾头不顾尾# 注:每一趟里都有1个指针,箭头永远从0开始,到 n - i -1# 注:想想第0趟 排n-1个数 下标n-i-2 (0,n-i-1)这代表n-i-1个if li[j] > li[j+1]: # 注:箭头要和下一个元素 比较大小 如果这个数大于后边的数# 注:li[j]箭头那个数 li[j+1] 箭头后面那个数li[j], li[j+1] = li[j+1], li[j] # 注:大于就交换# 注:Python内部实现 实际是 元组的交换print(li)# li = [random.randint(0,10000) for i in range(1000)] # 注:列表生成式 生成长度是1000的列表# print(li) # 打印列表# bubble_sort(li) # 注:调用算法# print(li) # 打印排序后的列表#结果为#[6507, 6107, 1706, 4345, 3183,…………#[8, 10, 13, 51, 53, 53, 57, ………… # 注:升序的li = [3,2,4,6,5,9,8,7,1]print(li)bubble_sort(li)#结果为# [3, 2, 4, 6, 5, 9, 8, 7, 1] # 注:原来的列表# [2, 3, 4, 5, 6, 8, 7, 1, 9] # 9# [2, 3, 4, 5, 6, 7, 1, 8, 9] # 8# [2, 3, 4, 5, 6, 1, 7, 8, 9] # 7# [2, 3, 4, 5, 1, 6, 7, 8, 9] # 6# [2, 3, 4, 1, 5, 6, 7, 8, 9] # 5# [2, 3, 1, 4, 5, 6, 7, 8, 9] # 4# [2, 1, 3, 4, 5, 6, 7, 8, 9] # 3# [1, 2, 3, 4, 5, 6, 7, 8, 9] # 21自动最小了### 注:如果想降序排序 小于的 上去就行了 改 if li[j] < li[j+1]:# 时间复杂度:O(n**2)# 注:n 是列表长度 算法里没有发生循环折半的过程 2层关于n的循环 所以复杂度O(n**2)#####################################################################################def bubble_sort2(li):for i in range(len(li)-1):for j in range(len(li)-i-1):if li[j] > li[j+1]:li[j], li[j+1] = li[j+1], li[j]# 时间复杂度:O(n2)#####################################################################################li = [9,8,7,1,2,3,4,5,6]print(li)bubble_sort(li)#结果为# [9, 8, 7, 1, 2, 3, 4, 5, 6]# [8, 7, 1, 2, 3, 4, 5, 6, 9]# [7, 1, 2, 3, 4, 5, 6, 8, 9]# [1, 2, 3, 4, 5, 6, 7, 8, 9]# [1, 2, 3, 4, 5, 6, 7, 8, 9] # 注:这1趟没有任何交换# [1, 2, 3, 4, 5, 6, 7, 8, 9]# [1, 2, 3, 4, 5, 6, 7, 8, 9]# [1, 2, 3, 4, 5, 6, 7, 8, 9]# [1, 2, 3, 4, 5, 6, 7, 8, 9]#####################################################################改善:如果在1趟过程中 没有发生交换 ,我们就认为 已经排好序了#怎样改进? 加1个标志位 在i循环 那里 放1个标志位# 如果冒泡排序中的一趟排序没有发生交换,则说明列表已经 有序,可以直接结束算法。def bubble_sort_1(li):for i in range(len(li)-1):exchange = False # 在第i趟那加标志位for j in range(len(li)-i-1):if li[j] > li[j+1]:li[j], li[j+1] = li[j+1], li[j]exchange = True # 注:如果有交换 把它识成True 交换这里也是1个标志位if not exchange: # 注:如果每1趟结束后 exchange没有发生交换 (这个在for里面)return# 注:就直接结束掉这个函数#极端例子:如果传入的无序列表 是排好了的,它就只要走一趟 而原本的冒泡排序是 n - 1 趟

代码思路,两个for循环,如果前面的一个数比后面这个数大,两个数互换位置

注意点:

第一个for range(len(li)-1)

第二个for 循环 range(len(li) - i -1)

改进思路:如果第二趟一次都没变就证明排好了,就可以加一个exchange判断是否排好

#14 选择排序

#先从列表中遍历1遍 可以找到最小的数 拿出来#再把剩下的元素 再遍历1遍 可以找到第2小的数 再拿出来#再剩下的……第3小……第4小……### 拿出来放在哪?#可以放在1个新列表里 (这是最简单版的)def select_sort_simple(li):li_new = [] # 这里还需要1个新列表for i in range(len(li)): # 注:遍历1遍出来1个数 需要遍历 n 遍# for j in li: # j 就是元素/值 如果它是最小的就拿出来min_val = min(li) # 注:调用内置函数li_new.append(min_val) # 把最小值放到新列表里li.remove(min_val) # 该值删掉#值重复的也没关系 ,因为它会从左边找return li_new # 返回新列表li = [3,2,4,1,5,6,8,7,9]print(select_sort_simple(li))#结果为 [1, 2, 3, 4, 5, 6, 7, 8, 9]#这个算法 是 正确的 但是不推荐写 有几点致命缺点#1、生成2个列表,数据量大,多占内存# 冒泡排序 叫他原地排序 不需要开辟1个新的列表 在原来的列表上#2、复杂度 不是O(n)# min()函数 O(n)# remove()删除函数 也是O(n) 因为列表删除1,不能只删了,删了的话会造成空位,后面都要往前挪1步,过程是O(n)#相当于里面有2个O(n) 但是2个O(n)记为1个O(n)# 所以时间复杂度是O(n**2) 而不是O(n**3)################################################################################ 不想开辟新列表 但想把选的数放到1个地方# 把它放到列表的第1个位置 (它只要和第一个数交换 就可以了) 接下来找无序区最小的数 再跟无序区的第1个位置交换# 为什么不放到最后1个位置:放到最后1个位置 代码不一样def select_sort(li):#还是需要n趟,每1趟出来1个最小的数# 但n-1趟放完后 还是1个数,那个数值最大的,所有和冒泡排序1样,需要n - 1趟for i in range(len(li) - 1): # i 是第几趟# 遍历无序区的范围# 第0趟从0到最后,第1趟从1到最后,第i趟从i到最后min_loc = i # 记最小值的位置(下标),因为后面要做交换,记无序区的第1个数为最小值# for j in range(i,len(li)): # i->列表长度 因为前包后不包所有写列表长度for j in range(i+1,len(li)): # 所以可以从 i + 1开始遍历# 没必要自己和自己比# j 是我们要从哪看到的if li[j] < li[min_loc]: # 注:如果遍历的数 比那个数还要小min_loc = j # 注:就min_loc继承j ;j就是最小值的下标#for循环执行完了 min_loc就是无序区最小的那个数的下标了li[i], li[min_loc] = li[min_loc], li[i] #最小的数和无序区的第1个值交换# 1趟就完成了 运行 n-1趟,选出n-1个数,最后1个数 一定是最大的print(li)li = [3,4,2,1,5,6,8,7,9]print(li)select_sort(li)print(li)#结果为 [1, 2, 3, 4, 5, 6, 7, 8, 9]#结果为 先排除小的# [3, 4, 2, 1, 5, 6, 8, 7, 9]# [1, 4, 2, 3, 5, 6, 8, 7, 9]# [1, 2, 4, 3, 5, 6, 8, 7, 9]# [1, 2, 3, 4, 5, 6, 8, 7, 9]# [1, 2, 3, 4, 5, 6, 8, 7, 9]# [1, 2, 3, 4, 5, 6, 8, 7, 9]# [1, 2, 3, 4, 5, 6, 8, 7, 9]# [1, 2, 3, 4, 5, 6, 7, 8, 9]# [1, 2, 3, 4, 5, 6, 7, 8, 9]# [1, 2, 3, 4, 5, 6, 7, 8, 9]###########################################################def select_sort(li):for i in range(len(li) - 1):min_loc = ifor j in range(i+1, len(li)):if li[j] < li[min_loc]:min_loc = jif min_loc != i:li[i], li[min_loc] = li[min_loc], li[i]# 时间复杂度:O(n**2)#注:代码规模是n ,没有出现折半,而且代码里没有出现任何 函数 (min\remove复杂度大于O(1)的函数)#注:所以2层循环是O(n**2)#它暂时没有像冒泡一样的优化;也没有二分查找优化(因为二分查找前提是有序的),暂时没有优化############################################################## 选择排序 (Select Sort)# 一趟排序记录最小的数,放到第一个位置# 再一趟排序记录记录列表无序区最小的数,放到第二个位置# ……# 算法关键点:有序区和无序区、无序区最小数的位置# 注:更注意的是无序区的位置 记录无序区里最小数的位置(默认指定无序区里第1个数,通过比较 更新)

遍历列表,每次遍历选一个最小值到新列表(也可把一个列表分为有序区和无序区)

#15 插入排序

#原理类似于 玩牌的时候 摸1张牌 把7 插到 10 的左边### 插入排序# 初始时手里(有序区)只有一张牌# 每次(从无序区)摸一张牌,插入到手里已 有牌的正确位置### 过程# 5 7 4 6 3 1 2 9 8#手里的牌是5# 摸到1张7 7插到5的右边# 4把4查到5的左边,把 5 7 的位置往右边挪 前面就空出1个位置 只需要拿1个变量把它存起来# 457 6 3 1 2 9 8 ,6的话 插到5 7 中间 所有 7 往右 挪一挪# 4567 3 1 2 9 8 , 3的话 4567 全都挪 右边 3 插过来# 34567 1 2 9 8 , 1的话 34567 全都往右挪# 134567 2 9 8 , 2的话 34567后面5个数 挪一下 ,3插过来# 1234567 9 8,9 不变放在这# 12345679 8, 8 把9挪一下,8 插过来# 123456789#弄清楚 哪些牌是手里的牌 ,哪些牌是摸到的牌#摸多少次牌,就是我们要排序多少趟#我们要摸 n - 1 次牌 (一共长度为n ,手里有1张牌 ,摸 n-1次)#摸到4后 从右往左看 57 先7 后5 ,左边有比4小的数停掉循环,或者走到最左边的元素了 停掉循环#从右边循环看# 时间复杂度:O(n2)def insert_sort(li):for i in range(1, len(li)): # 最开始手里已经有1张牌# i 表示摸到的牌的下标tmp = li[i] # 注:把摸到的牌存起来 因为它要往后挪#这个临时变量最后才用到,就是保存li[i]的 更形象j = i - 1 # 注:j 指的是手里的牌的下标 (起初是最右边的牌)# 如果j 这个牌 比 摸到的牌 小 就把摸到的牌插到j的右边# 如果j已经到最左边了 j=0的时候 比手里摸到的牌i# 如果j大 i小(摸到的牌) 则j往右挪一个while j >= 0 and tmp < li[j]: # 循环里是 数往右移,j往前看下一个 ;tmp是li[i]# j >= 0 表示 j到了-1退出# 找到1个比只要j比摸到的牌i小的情况 退出li[j + 1] = li[j] # 把数往右移了 (以前都理解错了,其实右边仅仅只是个值,左边被赋值)#而且右边 一开始等于 j=i-1 j+1=i 的下标遍历不到的 不影响j = j - 1 # j 这个箭头往左移 1 个位置 ,右边仅仅是值 左边才是li[j + 1] = tmp # while循环结束了 li[i]摸的牌写到 j+1的位置;代表插入i的 值 在j+1print(li)# while循环干的事情:找插入的那个位置li = [3,2,4,1,5,7,9,6,8]print(li)insert_sort(li)# print(li)#结果为 [1, 2, 3, 4, 5, 6, 7, 8, 9]#结果为# [3, 2, 4, 1, 5, 7, 9, 6, 8] # 注:原来的列表# [2, 3, 4, 1, 5, 7, 9, 6, 8] # 注:一开始有序区只有3,摸到2,把2插到3的左边 ,有序区23# [2, 3, 4, 1, 5, 7, 9, 6, 8] # 注:有序区23 摸到4 把4插到3右边有序区234# [1, 2, 3, 4, 5, 7, 9, 6, 8] # 注:2341 插到2的左边 1234# [1, 2, 3, 4, 5, 7, 9, 6, 8] # 注:1234 5 右边 12345# [1, 2, 3, 4, 5, 7, 9, 6, 8] # 注:12345 7 右边 123457# [1, 2, 3, 4, 5, 7, 9, 6, 8] # 注:123457 9 右边 1234579# [1, 2, 3, 4, 5, 6, 7, 9, 8] # 注:1234579 6 5后边 12345679# [1, 2, 3, 4, 5, 6, 7, 8, 9] # 注:12345679 8 把8插到7的右边 123456789#注:排了8次#while循环里 把数往右移 j往前看下一个#每次都是写到j+1的位置### while j >= 0 and tmp < li[j]的理解:退出循环的条件 1、2 (# 也可以单纯字面理解)#情况1 注:j<i时 不用挪了 把i放到 j+1的位置#情况2 注:4567 i=3 7、6、5比3大 往右移# 4 还比3 大 往右移 j=-1了 ,即j<0时 ,while退出# 这时 把这个元素 还是写在 j+1的位置(即0的位置)######################################################################import randomfrom cal_time import * # 检测1个代码的运行时间,要把打印的都注释起来或去掉@cal_time # 注:装饰器def insert_sort(li):for i in range(1, len(li)):tmp = li[i]j = i - 1while j >= 0 and tmp < li[j]:li[j + 1] = li[j]j = j - 1li[j + 1] = tmpli = list(range(10000)) # 注:用range方法生成1个1万的列表random.shuffle(li)# 注:random模块的shuffle方法 打乱insert_sort(li)#结果为 insert_sort running time: 4.095908880233765 secs.# 时间复杂度:O(n**2)#不是O(n) 而是O(n**2) ,n是代表列表长度,没有折半;2层循环,这个循环跟j相关的,j是跟n相关的#所以j是跟n 相关的#因为插入1个的过程的,我是要移动一些元素,并不知道移动多少元素。摸来第1张牌最多移动1个元素,摸来第2张牌最多移动2个元素,摸来第n张牌最多移动n-1个元素#每一趟最多移动n/2个元素,n趟 (n**2)/2#######################################################################注:冒泡排序,选择排序,插入排序 时间复杂度都是O(n**2),而且它们3个都是原地排序#注:O(n**2)的排序算法具体效率怎么样?# 1台电脑1s中的运行效率在10的7次方,1000万次基本操作# n=10000,n方式10000*10000是1亿,所以需要十秒左右#冒泡排序的效率比较慢,平时比较效率是不太能接受的'''插入排序插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。1. 算法步骤将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)'''def insertionSort(arr):#定义一个插入函数for i in range(len(arr)):#i = 0~7#遍历这个插入函数preIndex = i-1#头一个数的下标current = arr[i]#当前比的这个数可理解为手中的牌while preIndex >= 0 and arr[preIndex] > current:#当下标大于0并且这个数大于手中牌arr[preIndex+1] = arr[preIndex]#那么手中牌后面一个位置等于这个下标为i-1的数preIndex-=1#下标减1'''这里的循环体可理解为每次循环前面比current大的数都往后移一位'''arr[preIndex+1] = current#第一次的时候arr这个列表插入current这个数,#由于前面减1来这个其实重复了#那么手中牌后面一个位置等于这个下标为i-1的数的操作return arrarr = [115,181,95,15,154,1748,13,54]print(insertionSort(arr))

插入排序:模拟打扑克放牌,只不过每次current不动,让牌动,

eg:取牌四,先和牌三比比牌三大直接放牌三后,比三小就和牌二比,再小,再往前...........,没比小的了直接放最前面

#16 快速排序原理介绍

### 快速排序# 快速排序:快# 快速排序思路:# 取一个元素p(第一个元素),使元素p归位;# 列表被p分成两部分,左边都比p小,右边都比p大;# 递归完成排序。# 排序前:5 7 4 6 3 1 2 9 8P为5# p归位: 2 1 4 3 5 6 7 9 8左边比5小右边比5大 #这个过程怎么实现的 后面会说# 第3步递归完成 (5归位后 列表分为左右2部分 分别再调用)# 2 1 4 3 5 6 7 9 8#第1个元素2 2归位 1 (2) 4 3 小列表又被分成2部分#左边的列表1只有1个元素的时候 或者0个元素的时候 就达到了递归的终止条件#因为1个元素的时候是有序的#右边的列表43 4归位 3 4 两个部分3(4) 左边部分1个元素3,右边0个元素(以4为分割)#进而整个大列表的左边 都递归完成了# 整个大列表的右边 6 7 9 8#6归位后(6)798 左边零个元素(不用递归了)右边7 9 8 三个元素#7 9 87归位后(7)98 左边零个元素右边9 8 两个元素 左边不用递归 递归右边#98两个元素 9归位后 列表分为2部分8(9)左1个元素8(递归完成) 右边0个元素(递归完成)# 89递归完成 所以 789 递归完成 所以6789递归完成,所以右边递归完成,所以整个大列表递归完成# 相当于把它分成类似二分整 每一个进行递归# 只需要实现归位算法,排序只需要递归就行了#-------------------------------------------------------------------------------------------def quick_sort(data, left, right): # data表示列表 left、right 列表左右 表示范围if left < right: # 说明列表取余至少有2个元素,有2个或者2个以上的元素 递归(递归的终止条件)# left < right 列表至少2个元素# left == right 列表区域有1个元素mid = partition(data, left, right) # 先用函数 算出递归后的下标位置quick_sort(data, left, mid - 1)# 列表分成两部分,递归第1部分 (left,mid-1)quick_sort(data, mid + 1, right) # 递归第2 部分 (mid+1,right)# 归位函数时partition函数 data 参数1 是我们的列表,left和right是列表的一部分# 比如 整个大列表 left是0的位置 right是n-1的位置# 这个归位函数返回1个mid的值 (归位后 归位的数的下标)# 返回了mid值后 列表可以被拆分成2部分 (left --> mid-1) (mid+1 --> right) 2部分# 再对这2步进行递归# 只要把partition函数写出来#-------------------------------------------------------------------------------------------# 5 7 4 6 3 1 2 9 8# 注:5归位,先拿1个变量把5存起来 left=5下标 right=8下标# 注:列表左边有1个空位 是给5小的数准备的,这个数从右边开始找# 8 比5大 不对, 9 不对,2 放过来# 2原来的位置 空位,从左边找比5大的数放过去# 这个空置不能是5自己过去,因为左边要比5小 不满足# 2原来的位置空位,从左边找比5大的数扔过去。7比5大,扔过去# 2 4 6 3 1 7 9 8 7--》扔到右边# 扔的过程是从left位置 到 right位置# 左边有空位,从右边找比5小的数,1 放过来;从right位置放到left位置# 右边有空位,1比5小 没问题,4比5小 没问题;6比5大,6去右边空位# 左边有空位,6比5大 没问题,3比5小 去左边# 接下来还应该从左边找比5大的数 放到右边去,但是 3 比5 大。到这里发现 left和right重合了# left和right重合了说明 这个位置在中间,我们把5放过来 就可以

取一个数比大小,小去左,大去右

#17 快速排序代码实现

# 快速排序-partition函数def partition(li, left, right): #参数1列表 2、3操作的列表范围# 存出来第一个位置tmp = li[left] # 注:目的 把p存起来。注意不要写0,切的过程应该是left的位置,而不是0的位置while left < right: # 注:left=right的时候结束,终止条件# 这是右边的while left < right and li[right] >= tmp: # 从右边找比tmp小的数。# 如果右边都比5大 就碰上了 ,希望退出这个循环# left < right 检测,保证 右边一直大的情况,最后结果不是right-1这种right -= 1# 只要这个数比tmp大,left向左移动1个# 注:往左走1步li[left] = li[right] #注:如果left=right极端情况,那就自己等于自己 不影响#如果找到了 把right写到空位上 ,把right这个数写到left位上#把右边的值写到左边空位上# print(li) # 注:从右边找到比5小的# 这是左边的while left < right and li[left] <= tmp: # 如果找到了,从左边找比5大的数# 只要左边小于等于 tmp,一直循环;一旦找到比它大的数 终止循环# left=right 没有找到时,检测一下 left < right 不执行left+=1# 只要left和right碰上就退出left += 1li[right] = li[left] # 把左边的值写到右边空位上#如果找到了,把left的值 写到 右边的空位上 right#它们两个碰上了 也没关系 ,自己等于自己# print(li) # 注:从左边找到比5大的# 两个小循环一直执行,不管在哪只要left和ritht等了,某个循环不执行了# 那么等了之后,触发外面的大条件,跳出循环。把原来的值写到空位上li[left] = tmp# 把tmp归位return left # 返回mid的值 left和right都行,因为碰上了# 返回值 为的是 嵌套小循环li = [5,7,4,6,3,1,2,9,8]print(li)partition(li,0,len(li)-1)print(li)#结果为# [5, 7, 4, 6, 3, 1, 2, 9, 8]# [2, 7, 4, 6, 3, 1, 2, 9, 8]# [2, 7, 4, 6, 3, 1, 7, 9, 8]# [2, 1, 4, 6, 3, 1, 7, 9, 8]# [2, 1, 4, 6, 3, 6, 7, 9, 8]# [2, 1, 4, 3, 3, 6, 7, 9, 8]# [2, 1, 4, 3, 3, 6, 7, 9, 8]# [2, 1, 4, 3, 5, 6, 7, 9, 8] # 注:5归位#####################################################################逻辑复杂 代码对称# 开始 tmp = li[left]# 最后 li[left] = tmp 写出去写回来# 中间1个大while# 2个小while,每个while都是 1个赋值# 开始是右边,然后是左边的。先右后左,碰头或者找到一个数,就切换循环##################################################################### 快速排序-partition函数def partition(li, left, right):tmp = li[left]while left < right:while left < right and li[right] >= tmp: # 从右边找比tmp小的数right -= 1# 往右走一步li[left] = li[right] # 把右边的值写到左边空位上while left < right and li[left] <= tmp:left += 1li[right] = li[left] # 把左边的值写到右边空位上li[left] = tmp # 把tmp归位return left# mid 是 这个函数返回left值的目的# 快速排序-框架def quick_sort(li, left, right):if left < right: # 至少2个元素mid = partition(li, left, right) # 这个函数返回left值的目的quick_sort(li, left, mid - 1) # 左边部分quick_sort(li, mid + 1, right) # 右边部分li = [5,7,4,6,3,1,2,9,8]quick_sort(li, 0, len(li)-1)print(li)# [1,2,3,4,5,6,7,8,9]#新理解li[right] = li[left]# 注:右边仅仅是值,而左边的值的变化和下标、值 2者都有关

#18 快速排序代码实现2

### 快速排序# 快速排序的效率:# 快速排序的时间复杂度 O(nlogn)# 快速排序的问题:# 最坏情况# 递归###################################################################注:假设一开始有16个数,n=16,一次partition 把它分成2部分16 partition的过程 (每1层都做了一次partition) partition 是16 O(n)8 8 partition的过程 (每1层都做了一次partition) partition 是8+8 164 4 4 4 partition的过程 (每1层都做了一次partition) 是16 O(n)22 22 …………………… 是16 O(n)# 1次partition的 复杂度:# left 一边 right一般;left从右往左边撒,right从右往左边撒,最后碰上了# 相当于 从两边 往中间 把整个列表扫描了一边,所以1个长度为n的列表,它的实际复杂度是O(n)#每一层都是O(n)#一共logn层 log16层# 所以这个时间复杂度是 O(nlogn)#装饰器不能放在递归函数上,装饰器也会递归,会打印很多次# 解决方法:套一层马甲import randomfrom cal_time import *import copy # copy模块做li深拷贝,为了一块执行import sys # 注:修改递归最大深度sys.setrecursionlimit(10000) # 注:修改递归最大深度@cal_timedef bubble_sort(li):for i in range(len(li)-1):exchange = Falsefor j in range(len(li)-i-1):if li[j] > li[j+1]:li[j], li[j+1] = li[j+1], li[j]exchange = Trueif not exchange:returndef partition(li, left, right):tmp = li[left]while left < right:while left < right and li[right] >= tmp:right -= 1li[left] = li[right]while left < right and li[left] <= tmp:left += 1li[right] = li[left]li[left] = tmpreturn leftdef _quick_sort(li, left, right): # 注:改个名if left < right:mid = partition(li, left, right)_quick_sort(li, left, mid - 1)_quick_sort(li, mid + 1, right)@cal_timedef quick_sort(li): # 指接收1个li参数_quick_sort(li, 0, len(li)-1) # 调用这个函数li = list(range(10000))random.shuffle(li) # 乱序li1 = copy.deepcopy(li)# copy模块做li深拷贝,为了一块执行 复制成2个一样的列表li2 = copy.deepcopy(li)# copy模块做li深拷贝,为了一块执行 复制成2个一样的列表quick_sort(li1) # 快速排序 传入li1bubble_sort(li2) # 冒泡排序 传入li2print(li1)print(li2)#结果为# quick_sort running time: 0.02394270896911621 secs.# bubble_sort running time: 8.118826389312744 secs.#举例 n = 1024 时,冒泡排序 n**2 大概在100万 1024*1024# 快速排序 nlogn 1024*log1024 = 1024*10 = 1万#数越大差的越多 比如 n = 2的32次方 大概42亿,冒泡排序42亿*42亿 亿亿级别# 快排42亿*log42亿 = 42亿*32 也就几百亿 差很多倍# 所以 快排的效率 比冒泡高很多###################################################################### ### 快速排序# # 快速排序的效率:# # 快速排序的时间复杂度 O(nlogn)# # 快速排序的问题:# # 最坏情况# # 递归## ################################################################### 快排的问题存在:# 1、递归的写法:Python有一个递归最大深度999 可以改# 递归消耗相当一部分的系统资源 (不好的地方)# 2、快速排序有一个最大不好的地方存在,当列表是 9 8 7 6 5 4 3 2 1 时# 这种最坏的情况 冒泡每次至少一个数 ,而不是每次大概劈成一半,就不会出现nlogn了,复杂度是n**2# 9,8,7,6,5,4,3,2,1# 1,8,7,6,5,4,3,2,9右边没有# 1,8,7,6,5,4,3,2 左边没有# 2,7,6,5,4,3,8 右边没有# 2,7,6,5,4,3# ………………# 示例li = list(range(10000,0,-1)) # 倒序的列表quick_sort(li)#结果为 RecursionError: maximum recursion depth exceeded in comparison#达到了递归最大深度,因为每次少1个数,会递归n层,递归了1000层,超过了最大线程#修改递归最大深度后 sys.setrecursionlimit(100000) #结果为 5s左右#如何解决?随机化版本的快速排序#随机找1个值,而不是找第1个值。把第1个数和随机选的数交换一下,再按照之前的partition函数交换一下#只需要加一步#这样倒序的例子,时间复杂度不会特别高,只不过还是会出现最坏情况,只不过最坏情况你不能设计出来了。最坏的情况就不需要考虑,快排没有问题#算法有正常复杂度和最坏复杂度。最好情况时间复杂度、一般情况时间复杂度、最坏情况时间复杂度# 一般看的是一般情况 和 最坏情况#快速排序的最坏情况是 n**2 只不过最坏情况的可能性非常小#快速排序一般情况是 n*logn# 最好排序 相当于只走一次#改进之后,冒泡的最好情况是O(n),但是最好情况只有1个,它的最好情况太少了

#19 堆排序前传树的基础知识

# 树是一种数据结构 比如:目录结构# 树是一种可以递归定义的数据结构# 树是由n个节点组成的集合:#注:这是递归定义的方式,节点就是元素# 如果n=0,那这是一棵空树;# 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。# 一些概念# 根节点、叶子节点# 树的深度(高度)# 树的度# 孩子节点/父节点 子树#-------------------------------------------------------#注:根节点:A#注:叶子节点:不能分叉的节点 [最后一个,下面没有孩子了](叶子已经到了树的最末端,不能分叉)#-------------------------------------------------------#注:树的深度(高度):看它最深有几层 。1层2层3层4层#-------------------------------------------------------#注:节点的度:F节点往下分了3个叉,度是3。分了几个叉,就有几个度 看往下的叉#注:树的度就是整个树里 最大的那个节点的度#注:树的度就是 这个树最多分了几叉。A分了6个叉,这个树的度就是6#-------------------------------------------------------#注:孩子节点/父节点:E叫做I的父节点,I叫做E的孩子节点# A叫做B的父节点,B叫做A的孩子节点# 树在上面的 辈分比较高,生下来孩子#-------------------------------------------------------#注:子树:大树里 把一个树枝掰下来,这个树枝代表很多小树杈,那么它是1棵子树

#20 堆排序前传二叉树的基础知识

# 二叉树:度不超过2的树 # 注:每个节点往下 最多分2个叉## 每个节点最多有两个孩子节点# 两个孩子节点被区分为左孩子节 点和右孩子节点#--------------------------------------------#注:二叉树 度不超过2的树#注:B是A的左孩子节点,C是A的右孩子节点#--------------------------------------------# 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。# 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。#--------------------------------------------#注:满二叉树:对称,每个叉 该有的都有,最后一层是满的#注:完全二叉树:简单理解 从满二叉树 最后 拿走几个节点#注:完全二叉树:从上到下,从左到右,顺序不少 ,最后一层从左往右 少了也没关系#--------------------------------------------#注:堆是特殊的完全二叉树### 二叉树的存储方式(表示方式)# 链式存储方式# 顺序存储方式 #注:堆排序中讲顺序存储方式#注:顺序存储方式 简答来说是 用列表存#注:按树的顺序 存进去,是一一对应的#注:写二叉树 最常见的操作:父亲找孩子,孩子找父亲。一层一层往下找,不能跳#注:列表里父节点和孩子节点,下标的关系### 父节点和左孩子节点的编号下标有什么关系?# 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的0次方个节点#第2层2的1次方个节点#第3层2的2次方个节点#注:假设孩子的节点是i ,那么父亲的节点是 (i-1)//2 注:这是整除#注:比如 4-9 4-10。父节点是4 ,(9-1)//2 = 4 ,(10-1)//2 = 4#注:可以用顺序存储方式存储二叉树,就是列表。一个一个挨个存#注:父亲找孩子 i 2i+1 2i+2#注:孩子找父亲 i (i-1)//2

如果觉得《02_Python算法+数据结构笔记-冒泡排序-选择排序-插入排序-快排-二叉树》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。