学习排序算法的一些记录。也希望能为大家提供帮助。算法都用python实现。
1.讲排序算法前先提一下大O表示法.(O(n))
以下是《算法图解》中的一点介绍。大O表示法是一种特殊的表示法,指出了算法的速度有多快。但表示的并非是时间,n指的是操作的次数。O(n)表示的是操作时间的增速。
举个栗子。假设要在一张纸上画一个16格的网格。下面我们用两种算法来实现。
1.
2.
第一种方式我们要操作16次才能画出16个格子,而第二种方式我们只要折叠四次就有16个格子。用大O法表示就是:1.O(n) 16次操作16个格子呈线性关系
2.O(logn) 4次操作得到16个格子 3次8个 2的四次方16 2的三次方8 指数关系
最后补充下常见的一些大O表示方法:
假设每秒我们能操作10次画10个格子
以上是五种较为常见的大O表示法。接下来进入排序算法。
1.从左到右依次取两个数比较,较大的数往后挪接着和后面的数比较
2.这样子所有的数都比完每一轮都会冒出一个最大数,并且挪到最后那个位置
比如有个列表[7,5,8,3,4]
1.7和5先进行比较 7>5 7的索引和5的索引对换 列表变成[5,7,8,3,4]
2.接下去7和8进行比较7<8 两个索引都不变
3.8和3进行比较 8>3往后挪 [5,7,3,8,4]
直到8到达最后个位置又从第一个数5和7开始比较,一直循环len(list) 次
lists = [7,5,9,1,8,3,4,2,6]
n = len(lists) #个数
#冒泡排序 从左到右两个值做比较
def bubble_sort(lists,n):
for j in range(n,0,-1): #j剩余没得出最大值的个数
for i in range(j-1): #从左到右开始比较i为索引
if lists[i]>lists[i+1]:
lists[i],lists[i+1]=lists[i+1],lists[i] #如果前一个数大于后一个数 两个数位置对调
return lists
print(bubble_sort(lists,n))
如果一个列表刚好是排序完的状态[1,2,3,4,5,6,7,8,9]
只需要执行最外层的一个循环 走完9次就好 表示为O(n)
假设最糟糕的状况就是[9,8,7,6,5,4,3,2,1]
里外每一个循环都要走满 9+8+7+6+5+4+3+2+1=81 表示为O(n^2)
所以冒泡排序用大O表示时间介于O(n)-O(n2)之间,不是一个稳定的值
1.选择排序选择排序 即每一轮在所有数中找出最大的那个数,然后放到最后
接着找出第二大的,放在末二依次类推。
2.我们可以从第一个数开始,假设第一个数就是最大的,依次与后面的数一个个做比较。比较过程中遇到更大的数就保留,接着用更大的数去比较。
还是刚那个栗子列表[7,5,8,3,4]
假设7是最大的 先与5做比较 7>5 ,接着与下一个数8进行比较7<8,我们取较大的数接下去比较,8接下去和3,4进行比较都大于。最后就是把8的索引和最后一个数4的索引做对换 [7,5,4,3,8]。
注意了 选择排序可能看上去和冒泡排序很像。但这不同的地方又很明显。虽然都是两两进行比较,但在比较的过程中,选择排序的索引都不会发生变化。只是选出最大的值,并记录索引,和最后的一个值做调换。
lists = [7,5,9,1,8,3,4,2,6]
n = len(lists) #个数
#选择排序 找出最大值放最后
def selection_sort(lists,n):
for j in range(n-1,0,-1): #j表示还需进行的次数也是最后值的索引,第一次进行时最大值应该放在最后j的位置,第二次第二大值应该放j-1位置
maxs = lists[0] #假设最大值是第一个数值
post = 0 #post记录最大值所在的索引
for i in range(j+1):
if lists[i]>maxs: #当发现更大的值时
maxs = lists[i] #保留更大的值
post = i #并记录位置
lists[post],lists[j] = lists[j],lists[post] #最大值放到最后
return lists
print(selection_sort(lists,n))
选择排序 假设的数都要与每个数做比较发现最大值有n个数时n+n-1+n-2+…. 表示为O(n)
1.假设列表中存在一个有序的列表了,然后我们把其他数字代入这个列表和列表中的数值一个个做比较。从而将这个数插入到这个有序的列表中一个合适的位置。
2.可以假设第一个数就是一个有序的列表。(因为就一个数字不存在排序问题就是一个有序的列表) 从第2个数开始分别与第一个数做比较。
3.每次要插入的这个数我们叫做key,key与有序列表的从右到左开始比较,当遇到小于key的数时,key直接放这个数后面。
在列表中[7,5,8,3,4] 假设7是一个已经排序完的有序列表了。 现在把5插入这个有序列表中,5 先与7做比较5<7
所以[5,7,8,3,4],接着将8插入[5,7]这个有序列表中 8与第一个数7比较就大于7 直接放7后面 [5,7,8,3,4]
接着将3插入[5,7,8]这个有序列表中 3<8 接着与7 和5 做比较都小于 就放最前面[3,5,7,8,4] 以此类推
lists = [7,5,9,1,8,3,4,2,6]
n = len(lists) #个数
#插入排序 跟左边的值一个个做对比
def insertion_sort(lists,n):
for i in range(1,n): #i为有序列表的后的第一个值 ,假设索引0,第一个数为一个有序列表
key = lists[i] #所以key,要插入的值从第二个数开始
j = i #j为要插入值所在的位置 即索引
while lists[j-1]>key: #当有序列表从大到小的数依次与key做比较
lists[j] = lists[j-1] #数比key大时因为有序列表中插入了key所以比较过的数索引往后挪
j = j-1 #有序列表中从右到左一个个数与key做比较所以索引递减
if j==0: #当j==0时打破循环key已经是最小的左边没有数了没必要再比了
break
lists[j] = key #当key大于[j-1]位置的数时key找到合适位置索引为j
return lists
print(insertion_sort(lists,n))
插入排序法 最好的情况就是[1,2,3,4,5,6,7,8,9] 这样只要外面的大循环 里面的小循环while一进去就被打破 表示O(n)
最糟糕的情况就是while每次数都比较了一次 表示O(n^2)
1.快速排序就是选中一个基准点称作pivot,然后所有的数与他做比较,比他小的全归到左边,比他大的全部归到右边。
2.就形成了[小列表]+[pivot]+[大列表] 一个有序的排列 ,然后再对两个列表重复第一步的操作
3.[[小列表]+[pivot]+[大列表] ]+[pivot]+[[小列表]+[pivot]+[大列表] ] 直到基准点前后只有一个数时 排序就完成了
重复的操作可以用递归来完成 所以代码并不复杂 贴出两种写法 思路不同 第一种就不详细注释了 可以看第二种
lists = [7,5,9,1,8,3,4,2,6]
n = len(lists) #个数
#快速排序1
def quick_sort(lists,l,r): #l为left表示左边的第一个数的索引 r为right右边最后一个数的索引
i = l
j = r
pivot = lists[round((l+r)/2)] #设最中间这个数为基准
while i<=j:
while lists[i]1
while lists[j]>pivot:
j-=1
if i<=j:
lists[i],lists[j] = lists[j],lists[i]
i+=1
j-=1
if lif ireturn lists
print(quick_sort(lists,0,n-1))
#快速排序2
def quick_sort2(lists):
if len(lists)<2: #当列表中只有0或一个数时返回结束递归,即pivot左或右只有1或0个数
return lists
else:
pivot = lists[0] #就取列表的第一个数作为基准
small = [] #创建两个列表存放大于基准和小于基准的数
big = []
for i in lists[1:]: #因为第一个数时基准从第二个数开始遍历
if i<=pivot:
small.append(i)
else:
big.append(i)
lists = quick_sort2(small)+[pivot]+quick_sort2(big) #最后将列表拼接起来。得到的大和小的列表再放入函数中运行排序
return lists
print(quick_sort2(lists))
大O表示快速排序法为 O(nlogn)
比之前几种排序算法而言比较快 优化版的冒泡排序
先贴个我写的递归函数。要理解归并,感觉递归熟了之后还是很好理解的,也可以借着学习递归巩固下对递归的认识。
https://blog.csdn.net/qq_41239584/article/details/82253771
归并排序 就像这个名字一样,把一个个列表合并在一起。怎么来合并呢,简单来说就是先拆后拼。
1.先将一个列表分成两个,两个分四个 分到不能再分,每一个列表都只有一个元素
2.再将列表一级一级的有序的合并起来,4个一个元素的列表变成 2个有序的2元素列表 再变成一个有序的4元素列表
举个例子 列表[4,8,9,3,6,5,2,1]
1.将列表从中分开分成
[4,8,9,3] [6,5,2,1]
2.接着分
[4,8] [9,3] [6,5] [2,1] (如果奇数个数字就2,2,2,3分没事)
3.分到最简
[4] [8] [9] [3] [6] [5] [2] [1]
3.最简后开始合并,按照分解的步骤合并,但有序的合并。
[4,8] [3,9] [5,6] [1,2] (第2点怎么分现在怎么合,但列表数要排序)
4.按第一部分解的合并
[3,4,8,9] [1,2,5,6]
5.最后两个列表合并 小的放前面
假设创建一个新列表A[ ]空集合
2个列表都取第一个数开始比,1和3先比1小 先丢进去——A[1]
右边列表的指针往后移 指向2,2和3比较 2小 丢进去——A[1,2]
右边列表的指针接着后移,指向5,5和3比较 3小 ——A[1,2,3]
左边列表的指针向后移动,指向4, 4和5比较 4小 ——A[1,2,3,4]
左边列表的指针向后移动,指向8, 8和5比较 5小 ——A[1,2,3,4,5]
右边列表的指针接着后移,指向6,6和8比较 6小 ——A[1,2,3,4,5]
最后右边列表空了,左边列表还剩[8,9] 拼在A列表后————A[1,2,3,4,5,6,8,9]
整个过程结束
其实整个过程 就是两个东西 一直拆,选一个中点拆成两边,拆到不能拆 。有没有很递归
接着一直比较,然后合并1合2 2合4 4合8 比较到数都比完 合并到没得合
上代码
#归并排序
def merge_sort(lists):
n = len(lists) #列表个数
if n<=1: #递归基线条件 当分解到单个数值时结束分解
return lists
mid = n//2 #中间索引取整
left = merge_sort(lists[:mid]) #递归分解,从中间开始分解列表 左列表和右列表
right = merge_sort(lists[mid:]) #直到分解成单个数字的列表
left_i,right_i = 0,0 #左右的指针索引设为0
ret = [] #设置一个空列表用来重组
while left_iand right_i#指针不能超出范围left_i的最大索引为len(left)-1,所以left_i
if left[left_i]#按一个小列表到大列表重组的过程,小的先添加进列表就在前面
ret.append(left[left_i]) #这样每个列表排序成有序列表了
left_i+=1 #当数字添加进后,指针向后走,后面的数接着喝前面较大数比较
else:
ret.append(right[right_i])
right_i+=1
ret += left[left_i:] #捡漏! 剩下较大的未参与比较的直接补到列表后面
ret += right[right_i:] #A列表+空列表还是=A列表没变化
return ret #返回最后的列表
ret = merge_sort(lists)
print('归并排序:',ret)
上个手工的图理解下:
调用栈的分解往下分解 最后由下到上传回返回排序完的有序列表。
最后用大O表示下 O(nlogn) 纵向 2 4 8 。2的次方关系 横向 n的关系 n*logn.
各个排序算法的时间复杂度和空间复杂度汇总。最常用的还是快速排序。
写的第一篇。希望大家能提出宝贵意见。
keep on coding