文章目录
图片来源:十大经典排序算法(动图演示)
图片来源:十大经典排序算法(动图演示)
1 Sort
sort():对原始列表进行排序
sorted():返回一个新的排序列表
groceries = ['milk', 'bread', 'tea']
new_groceries = sorted(groceries)
# new_groceries = ['bread', 'milk', 'tea']
print(new_groceries)
# groceries = ['milk', 'bread', 'tea']
print(groceries)
groceries.sort()
# groceries = ['bread', 'milk', 'tea']
print(groceries)
配合 operator,来自 python中的operator.itemgetter函数
import operator
a = [1,2,3]
fun_b = operator.itemgetter(1) # 定义函数 fun_b,获取对象的第1个域的值
fun_c = operator.itemgetter(2,1,0)
print(fun_b(a))
print(fun_c(a))
a = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]
b = sorted(a, key=operator.itemgetter(1,2))
print(b)
1.1 冒泡排序
图片来自于:https://www.bilibili.com/video/av53583801/?p=35
动图来源:十大经典排序算法(动图演示)
思路(升序):
n-1 趟,每一趟确定一个数的最终位置,
每一趟两两元素(未确定最终位置)比较,大的往后放
nums = [3,5,6,7,8,9,2,1,4]
def bubble(nums):
n = len(nums)
for i in range(n-1): # 排序 n-1 趟,每一趟最少排好一个数
count = 0 # 记录交换的次数
for j in range(n-1-i): # 两个元素比较的次数
if nums[j] > nums[j+1]: # 这里的符号改变一下就是逆序了
nums[j],nums[j+1] = nums[j+1],nums[j]
count+=1
if count==0: # 某一趟中,没有元素交换,说明排序结束了,退出
break
return nums
print(bubble(nums))
output
[1, 2, 3, 4, 5, 6, 7, 8, 9]
注意:没有 count 的统计和判断,那么最好最坏的复杂度都是 O ( n 2 ) O(n^2) O(n2),有了以后最好的是 O ( n ) O(n) O(n)(原来的数组就有序),最坏的 O ( n 2 ) O(n^2) O(n2)(逆序)!算法是稳定的
1.2 选择排序
动图来源:十大经典排序算法(动图演示)
思路(升序):将数组分为两部分,取后面部分的最小值放在前面!每一趟确认前面部分的一个值!
nums = [3,5,6,7,8,9,2,1,4]
def select_sort(nums):
n = len(nums)
for i in range(n-1):
min_index = i # 初始最小值下标为 i
for j in range(i+1,n): # 遍历i+1到n,找最小值的下标
if nums[j] < nums[min_index]:
min_index = j
nums[i],nums[min_index] = nums[min_index],nums[i] # 交换初始化最小值下标和比较后的最小值下标
return nums
print(select_sort(nums))
分析:最坏最好都是 O ( n 2 ) O(n^2) O(n2),不稳定的,如果算法改为,把从前面部分选最大值放在后面部分,发现相同元素的位置调换了
eg 3(1) 3(2) 1 2
第一趟:3(2) 1 2 | 3(1)
第二趟:1 2 | 3(2) 3(1)
第三趟:1 | 2 3(2) 3(1)
可以看出算法是不稳定的
1.3 插入排序
动图来源:十大经典排序算法(动图演示)
同选择排序,也是将数组分为两个部分,从后面部分选择元素插入到第一部分中,插入的过程是逐个比较,比前部分小就交换位置!
nums = [3,5,6,7,8,9,2,1,4]
def insert_sort(nums):
n = len(nums)
for i in range(1,n):
for j in reversed(range(1,i+1)): # 反向遍历 i 到 1
if nums[j] < nums[j-1]: # 和前一个元素比较大小
nums[j],nums[j-1] = nums[j-1], nums[j] #小的话交换位置
else: # 大的话直接退出,保证了最优为 O(n)
break
return nums
print(insert_sort(nums))
最好 O ( n ) O(n) O(n),就是有序的时候都 break 了,最坏的是 O ( n 2 ) O(n^2) O(n2)(两层循环),平均的是 O ( n 2 ) O(n^2) O(n2),是稳定的!
1.4 希尔排序
动图来源:十大经典排序算法(动图演示)
插入排序的改进版,将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序(直接插入排序的步长变为了gap,而不是1了)!不同的分割策略会影响算法的时间复杂度,gap 为1的时候就是插入排序,eg 4,2,1。
nums = [3,5,6,7,8,9,2,1,4]
def shell_sort(nums):
n = len(nums)
gap = n//2
while(gap>0):
for i in range(gap,n):
for j in reversed(range(1,i+1,gap)): # 反向遍历 i 到 1,步长变为了 gap
if nums[j] < nums[j-1]: # 和前一个元素比较大小
nums[j],nums[j-1] = nums[j-1], nums[j] #小的话交换位置
else: # 大的话直接退出,保证了最优为 O(n)
break
gap //=2 # gap 的策略,这里是 1/2
return nums
print(shell_sort(nums))
1.5 快排
介绍可以参考 python】Leetcode(Data Structure / Algorithm) 中的 215. 数组中的第K个最大元素(快排)
partion 函数,把小于 base 的放在左边,把大于base 的放在右边,然后递归下去!
nums = [3,5,6,7,8,9,2,1,4]
def quick_sort(nums,start,end):
if start<end:
base = partion(nums,start,end)
quick_sort(nums,start,base-1)
quick_sort(nums,base+1,end)
return nums
def partion(nums,start,end):
l = start
r = end
base = nums[l]
while(l<r):
while(l<r and nums[r]>=base):
r-=1
nums[l] = nums[r]
while(l<r and nums[l]<=base):
l+=1
nums[r] = nums[l]
nums[l] = base
return l
print(quick_sort(nums,0,len(nums)-1))
1.6 归并排序
稳定的,最好最坏的平均的时间复杂度都是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),但是会生成同样大小的数组,思想是,一直分下去(eg 2,4,8……),然后排序后合起来!排序的过程用到了双指针,一个指向二分左部分的,一个指向二分右部分的,比较大小移动指针来合并成一个排序的新数组!
图片来自于:https://www.bilibili.com/video/av53583801/?p=46
图片来自于:https://www.bilibili.com/video/av53583801/?p=35
nums = [3,5,6,7,8,9,2,1,4]
def merge_sort(nums):
if len(nums)<=1: # 递归终止条件
return nums
mid = len(nums)//2
l_list = merge_sort(nums[:mid]) # 分成左右两部分
r_list = merge_sort(nums[mid:]) # 分成左右两部分
result = [] # 存放排序的结果
l,r = 0,0 # 用两个指针对左右两部分进行排序
while(l<len(l_list) and r<len(r_list)): # 左右有一个到了尽头的时候退出
if l_list[l]<=r_list[r]:
result.append(l_list[l])
l+=1
else:
result.append(r_list[r])
r+=1
result+=l_list[l:] # 把剩下的挂在result的后面
result+=r_list[r:] # 把剩下的挂在result的后面
return result # 返回排序后的结果
print(merge_sort(nums))
2 Search
二分查找
递归版本
nums = [1,2,3,4,5,6,7,8,9]
def binary_search(nums,item):
if len(nums)>0: # 这个条件很关键
mid = len(nums)//2
if item == nums[mid]:
return True
elif item < nums[mid]:
return binary_search(nums[:mid],item)
elif item > nums[mid]:
return binary_search(nums[mid+1:], item)
return False
print(binary_search(nums,5))
print(binary_search(nums,10))
output
True
False
非递归版本
nums = [1,2,3,4,5,6,7,8,9]
def binary_search(nums,item):
l = 0
r = len(nums)-1
while(l<=r):
mid = (l+r) // 2
if item == nums[mid]:
return True
elif item < nums[mid]:
r = mid - 1
elif item > nums[mid]:
l = mid + 1
return False
print(binary_search(nums,5))
print(binary_search(nums,10))
output
True
False
最优
O
(
1
)
O(1)
O(1)
最坏
O
(
l
o
g
n
)
O(logn)
O(logn)
3 Insert
3.1 bisect
用 bisect.insort 插入新元素
排序很耗时,因此在得到一个有序序列之后,我们最好能够保持它的有序。bisect.insort就是为这个而存在的
insort(seq, item) 把变量 item 插入到序列 seq 中,并能保持 seq 的升序顺序
import random
from random import randint
import bisect
lst = []
SIZE = 10
random.seed(5)
for _ in range(SIZE):
item = randint(1, SIZE)
bisect.insort(lst, item)
print('%2d ->' % item, lst)
A 附录
A.1 key 参数的应用
(1)根据字符串中出现的数字大小排序
直接排序的话,会按照字符串排序的规则,从左到右比 ASCII 大小,不符合我们的期望
s = ["f1", "e2", "d3", "c4", "b5", "a6"]
print(sorted(s))
output
['a6', 'b5', 'c4', 'd3', 'e2', 'f1']
此时,我们可以借助正则表达式(参考 【python】Regular Expression(13)),指定排序算法根据字符串中的数字来排序
import re
s = ["f1", "e2", "d3", "c4", "b5", "a6"]
def sort_key(s):
if s:
try:
c = re.findall(r"\d+", s)[0]
except:
c = -1
return int(c)
print(sorted(s, key=sort_key))
output
['f1', 'e2', 'd3', 'c4', 'b5', 'a6']
(2)根据列表最后一个元素的大小进行排序
boxes = [[1,3],[2,2],[3,1]]
boxes = sorted(boxes, key=lambda x:x[-1])
print(boxes)
output
A.2 中文排序
Python中实现读取与windows资源管理器中相同顺序文件,顺序排列
安装 natsort
库
from natsort import natsorted
用法同 sorted