文章目录
1、冒泡排序(bubble)
(1)思路
- 如果前一个数比后一个数小则交换……
- 时间复杂度为O(n2)
(2)代码
import random #随机数库
def bubble_sort(list): #传入打乱的数据
for i in range(len(list)-1): #最后一位不索引则减一
flag = False #数据未排列好
for j in range(len(list)-i-1): #每过i趟则无序区减i
if list[j] > list[j+1]: #前大于后则交换
list[j], list[j+1] = list[j+1], list[j]
flag = True #数据已排列好
print(list) #打印过程数据
if not flag: #排列好则退出
return
list = [random.randint(0,100) for i in range(10)] #随机生成数据
print(list)
bubble_sort(list)
(3)运行结果
[8, 1, 5, 8, 9, 1, 9, 7, 3, 4]
[1, 5, 8, 8, 1, 9, 7, 3, 4, 9]
[1, 5, 8, 1, 8, 7, 3, 4, 9, 9]
[1, 5, 1, 8, 7, 3, 4, 8, 9, 9]
[1, 1, 5, 7, 3, 4, 8, 8, 9, 9]
[1, 1, 5, 3, 4, 7, 8, 8, 9, 9]
[1, 1, 3, 4, 5, 7, 8, 8, 9, 9]
[1, 1, 3, 4, 5, 7, 8, 8, 9, 9]
2、选择排序(select)
(1)思路
- 一趟排序记录最小的数,放在第一个位置
- 再一趟排序记录列表无序区最小的数,放在第二个位置
- ……
- 最后无序区只剩下最大的数,则无需遍历。
- (简而言之,取列表最小的数放于首位,再取次小的数放于次位……)
- 时间复杂度为O(n2)
(2)代码
import random #随机数库
def select_sort(list):
for i in range(len(list) - 1): #i是第几趟,最后只剩一个则无需遍历,则减一
min_list = i #获取列表前面的数
for j in range(i+1, len(list)): #遍历无序区,从交换后的最小数开始
if list[j] < list[min_list]: #如果小于目前最小数则获取
min_list = j
if min_list != i: #找到最小值,则和无序区第一个数交换
list[i], list[min_list] = list[min_list], list[i]
print(list) #打印过程
list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
select_sort(list)
(3)运行结果
[2, 0, 7, 3, 1, 5, 7, 6, 5, 6]
[0, 2, 7, 3, 1, 5, 7, 6, 5, 6]
[0, 1, 7, 3, 2, 5, 7, 6, 5, 6]
[0, 1, 2, 3, 7, 5, 7, 6, 5, 6]
[0, 1, 2, 3, 7, 5, 7, 6, 5, 6]
[0, 1, 2, 3, 5, 7, 7, 6, 5, 6]
[0, 1, 2, 3, 5, 5, 7, 6, 7, 6]
[0, 1, 2, 3, 5, 5, 6, 7, 7, 6]
[0, 1, 2, 3, 5, 5, 6, 6, 7, 7]
[0, 1, 2, 3, 5, 5, 6, 6, 7, 7]
3、插入排序(insert)
(1)思路
- 初始时手里(有序区)只有一张牌
- 每次(从无序区)摸一张牌,插入到手里已有牌的正确位置
- (简而言之,如斗地主中的整牌。)
- 时间复杂度为O(n2) 具有稳定性
(2)代码
import random #随机数库
def insert_sort(list):
for i in range(1, len(list)): #i表示摸到的牌的下标
temp = list[i]
j = i - 1 #j指的是手里的牌的下标
while j >= 0 and list[j] > temp: #当有序区中的数大于无序区的数时
list[j+1] = list[j] #有序区向后移一位
j -= 1 #j向前索引
list[j+1] = temp #当有序区中的数小于无序区时,则置于有序区之后
print(list) #将每一趟打印
list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
insert_sort(list)
(3)运行结果
[2, 7, 1, 8, 10, 9, 8, 9, 9, 8]
[2, 7, 1, 8, 10, 9, 8, 9, 9, 8]
[1, 2, 7, 8, 10, 9, 8, 9, 9, 8]
[1, 2, 7, 8, 10, 9, 8, 9, 9, 8]
[1, 2, 7, 8, 10, 9, 8, 9, 9, 8]
[1, 2, 7, 8, 9, 10, 8, 9, 9, 8]
[1, 2, 7, 8, 8, 9, 10, 9, 9, 8]
[1, 2, 7, 8, 8, 9, 9, 10, 9, 8]
[1, 2, 7, 8, 8, 9, 9, 9, 10, 8]
[1, 2, 7, 8, 8, 8, 9, 9, 9, 10]
4、快速排序(quick)
(1)思路
- 取一个元素p(第一个元素),使元素p归位
- 列表被p分为两部分,左边都比p小,右边都比p大
- 递归完成排序
- 类似二分查找
- 一般 时间复杂度为O(nlogn)
- 最坏 时间复杂度为O(n2) 不具有稳定性
(2)代码
import random #随机数库
def partition(list, left, right):
temp = list[left]
while left < right:
while left < right and list[right] >= temp: #从右边找比temp小的数
right -= 1 #往左走一步
list[left] = list[right] #把右边的值写到左边空位上
while left < right and list[left] <= temp: #从左边找比temp大的数
left += 1 #往右走一步
list[right] = list[left] #把左边的值写到右边空位上
list[left] = temp #把temp归位
return left
def quick_sort(list, left, right):
if left < right: #至少两个元素
mid = partition(list, left, right) #获取第一个值
quick_sort(list, left, mid-1) #左边区间
quick_sort(list, mid+1, right) #右边区间
print(list)
list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
quick_sort(list, 0, len(list)-1)
(3)运行结果
[3, 8, 2, 9, 4, 8, 3, 4, 8, 2]
[2, 2, 3, 9, 4, 8, 3, 4, 8, 8]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]
[2, 2, 3, 3, 4, 4, 8, 8, 8, 9]
5、堆排序(heap)
(1)思路
- 堆:一种特殊的完全二叉树(即最后一行的叶子节点可以不全但需左对齐)结构。
- 大根堆:一颗完全二叉树,满足任一节点都比其孩子节点大
- 小根堆:一颗完全二叉树,满足任一节点都比其孩子节点小
- 堆排序过程:
- 1、建立堆;
- 2、得到堆顶元素,为最大元素;
- 3、去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序;
- 4、堆顶元素为第二大元素;
- 5、重复步骤3,直至堆为空。
- 大根堆得到的是升序;小根堆得到的是降序。
- 时间复杂度为O(nlogn) 不具有稳定性
(2)大根堆代码
import random #随机数库
def sift(list, low, high):
'''
:param list: 列表
:param low: 堆的根节点位置
:param high: 堆的最后一个元素的位置
:return:
'''
i = low #i最开始指向根节点
j = 2 * i + 1 #j开始是左孩子
temp = list[low] #把堆顶存起来
while j <= high: #只要j位置有数
if j+1 <= high and list[j+1] > list[j]: #如果右孩子有并且比较大
j = j + 1 #j指向右孩子
if list[j] > temp:
list[i] = list[j] #大值放于堆顶
i = j #往下一层
j = 2 * i +1
# else: #两种方式均可
# break
# else:
# list[i] = temp
else: #temp更大,把temp放到i的位置上
list[i] = temp #把temp放到某一级领导位置上
break
else: #到最后都没有比temp大的数
list[i] = temp #把temp放到叶子节点上
def heap_sort(list):
for i in range((len(list)-2) // 2, -1, -1):
# i表示建堆的时候调整的部分的根的下标
sift(list, i, len(list)-1)
print(list)
# 建堆完成了
for i in range(len(list)-1 , -1, -1):
# i指向当前堆的最后一个元素
list[0], list[i] = list[i], list[0]
sift(list, 0, i-1) #i-1是新的high
list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
heap_sort(list)
print(list)
运行结果
[5, 4, 6, 3, 7, 1, 10, 0, 2, 5]
[10, 7, 6, 3, 5, 1, 5, 0, 2, 4]
[0, 1, 2, 3, 4, 5, 5, 6, 7, 10]
(3)小根堆代码
import random #随机数库
def sift(list, low, high):
'''
:param list: 列表
:param low: 堆的根节点位置
:param high: 堆的最后一个元素的位置
:return:
'''
i = low #i最开始指向根节点
j = 2 * i + 1 #j开始是左孩子
temp = list[low] #把堆顶存起来
while j <= high: #只要j位置有数
if j+1 <= high and list[j+1] < list[j]: #如果右孩子有并且比较小
j = j + 1 #j指向右孩子
if list[j] < temp:
list[i] = list[j] #小值放于堆顶
i = j #往下一层
j = 2 * i +1
# else: #两种方式均可
# break
# else:
# list[i] = temp
else: #temp更小,把temp放到i的位置上
list[i] = temp #把temp放到某一级领导位置上
break
else: #到最后都没有比temp小的数
list[i] = temp #把temp放到叶子节点上
def heap_sort(list):
for i in range((len(list)-2) // 2, -1, -1):
# i表示建堆的时候调整的部分的根的下标
sift(list, i, len(list)-1)
print(list)
# 建堆完成了
for i in range(len(list)-1 , -1, -1):
# i指向当前堆的最后一个元素
list[0], list[i] = list[i], list[0]
sift(list, 0, i-1) #i-1是新的high
list = [random.randint(0,10) for i in range(10)] #随机生成数据
print(list)
heap_sort(list)
print(list)
#print(list[::-1]) #倒序
运行结果
[9, 7, 9, 5, 8, 10, 3, 5, 9, 5]
[3, 5, 9, 5, 5, 10, 9, 7, 9, 8]
[10, 9, 9, 9, 8, 7, 5, 5, 5, 3]
(4)内置函数(小根堆)
import heapq #q->queue 优先队列
import random
list = list(range(10)) #随机生成数据
random.shuffle(list)
print(list)
heapq.heapify(list) #建堆
for i in range(len(list)):
print(heapq.heappop(list), end=',') #排序输出
运行结果
[5, 2, 1, 0, 3, 7, 4, 8, 6, 9]
0,1,2,3,4,5,6,7,8,9,
6、归并排序(merge)
(1)思路
- 分解:将列表越分越小,直至分成一个元素;
- 终止条件:一个元素是有序的;
- 合并:将两个有序列表归并,列表越来越大。
- 不具有稳定性
- 图示
- (简而言之,就是将乱序list分解成有序的子list,然后再合并成一个list。)
- 时间复杂度为O(nlogn)
- (归并排序需要格外一个列表)
- 空间复杂度为O(n)
(2)代码
def merge(list, low, mid, high): # 一次归并
i = low # 第一个有序列表的首值
j = mid + 1 # 第二个有序列表的首值
temp = [] # 临时储存的列表
while i <= mid and j <= high: # 当两个列表都有数据才有效
if list[i] < list[j]: # 小的先出
temp.append(list[i])
i += 1 # 位置后移
else:
temp.append(list[j])
j += 1
# while执行完后,或许其中一个列表有部分数据还没处理
while i <= mid:
temp.append(list[i]) # 因为为有序列表,所以直接追加即可
i += 1
while j <= high:
temp.append(list[j])
j += 1
list[low: high+1] = temp # 重新传回list
def merge_sort(list, low, high):
if low < high: # 至少有两个元素,递归
mid = (low + high) // 2
merge_sort(list, low, mid) # mid左边先分解
merge_sort(list, mid+1, high) # mid右边再分解
print(list[low: high+1]) # 打印分解过程
merge(list, low, mid, high) # 归并
print(list[low: high+1]) # 打印归并过程
list = list(range(10)) # 长度为10的列表
import random # 随机函数库
random.shuffle(list) # 随机排列list
print(list) # 打印乱序list
merge_sort(list, 0, len(list) - 1) # 归并排序
#print(list) # 打印有序list
(3)运行结果
[0, 1, 2, 6, 9, 8, 4, 7, 5, 3] # 乱序数据
[0, 1] # 一次左分解
[0, 1] # 一次合并
[0, 1, 2] # 一次左分解
[0, 1, 2] # 一次合并
[6, 9] # 一次左分解
[6, 9] # 一次合并
[0, 1, 2, 6, 9] # 一次左分解
[0, 1, 2, 6, 9] # 一次合并
[8, 4] # 一次右分解
[4, 8] # 一次合并
[4, 8, 7] # 一次右分解
[4, 7, 8] # 一次合并
[5, 3] # 一次右分解
[3, 5] # 一次合并
[4, 7, 8, 3, 5] # 一次右分解
[3, 4, 5, 7, 8] # 一次合并
[0, 1, 2, 6, 9, 3, 4, 5, 7, 8] # 两个有序的列表
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 有序数据
(1~6总结)
- 前三种排序算法的时间复杂度均为O(n2);
- 后三种排序算法的时间复杂度均为O(nlogn);
- 一般情况就运行时间而言,快速排序 < 归并排序 < 堆排序
- 后三种排序的缺点:
- 快速排序:极端情况下排序效率低;
- 归并排序:需要额外的内存开销;
- 堆排序:在后三种排序中运行速度慢。
- 总结
7、希尔排序(sher)
(1)思路
- 其是一种分组插入排序算法。
- 1、首先取一个整数d1=n/2,将元素分为d1个组,每组相邻元素之间的距离为d1,在各组内进行直接插入排序;
- 2、取第二个整数d2=d1/2,重复上述分组排序过程,直到d1=1,即所有元素在同一组内进行直接插入排序。
- 希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。
- (简而言之,就是按步长(递减)进行分组,各组进行插入排序,直到整体有序。)
- 时间复杂度: 比较复杂,与选取的gap(组距)有关。
(2)代码
def insert_sort_gap(list, gap): # 插入排序加入组距gap
for i in range(gap, len(list)): # i表示摸到牌的下标
tmp = list[i]
j = i - gap # j值手里牌的下标
while j >= 0 and list[j] > tmp:
list[j + gap] = list[j]
j -= gap
list[j + gap] = tmp
def sher_sort(list): # 希尔排序
d = len(list) // 2 # 组距
while d >= 1: # 不到一时,一直处于接近有许的操作
insert_sort_gap(list, d)
d //= 2
list = list(range(10)) # 长度为10的列表
import random # 随机函数库
random.shuffle(list) # 随机排列list
print(list) # 打印乱序list
sher_sort(list)
print(list) # 打印有序list
(3)运行结果
[0, 5, 3, 2, 4, 8, 1, 9, 7, 6]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
8、计数排序(count)
(1)思路
- 已知列表的数范围在0到n之间。
- 优点:
- 运行速度快,比快速排序还快。
- 缺点:
- 需提前知道n大小;
- 需新建n+1个列表,空间复杂度高。
- (简而言之,就是乱序中每个数值统计入对应的列表中。)
- 时间复杂度O(n)
- 空间复杂度O(n+2)
(2)代码
def count_sort(list, max_count = 100): # max_count为需要的新创的列表数
count = [0 for _ in range(max_count + 1)] # 每个新创列表值为0
for val in list: # 将list中存在的值计入对应下标的列表中
count[val] += 1
list.clear() # 原列表清空
for ind, val in enumerate(count): # enumerate多用于在for循环中得到计数,同时获得索引和值
for i in range(val): # 值为多少就追加多少个i
list.append(ind)
import random
list = [random.randint(0, 100) for _ in range(10)] # 随机生成10个0到100的数
print(list) # 打印乱序list
count_sort(list)
print(list) # 打印有序list
(3)运行结果
[54, 84, 13, 27, 80, 38, 21, 68, 48, 68]
[13, 21, 27, 38, 48, 54, 68, 68, 80, 84]
9、桶排序(bucket)
(1)思路
- 基于计数排序,如果元素范围过大,可使用桶排序。
- 桶排序:首先将元素分在不同的桶中,再对每个桶中元素进行排序。
- 优点:
- 可减少计数排序的空间。
- 缺点:
- 需提前知道n大小。
- 桶排序的表现:取决于数据的分步。也就是需要对不同数据排序时采取不同的分桶策略。
- 平均时间复杂度O(n+k)
- 最坏时间复杂度O(n2k)
- 空间复杂度O(nk)
(2)代码
def bucket_sort(list, n=5, max_num=10): # n为桶的数量,max_num为需要新创的列表数
bucket = [[] for _ in range(n)] # 创建桶
for var in list:
i = min(var // (max_num // n), n-1) # i表示var放在几号桶
bucket[i].append(var)
for j in range(len(bucket[i])-1, 0, -1): # 进行桶内排序,插入排序
if bucket[i][j] < bucket[i][j-1]:
bucket[i][j], bucket[i][j-1] = bucket[i][j-1], bucket[i][j]
else:
break
sorted_list = [] # 新建存储排好序的列表
for buc in bucket:
sorted_list.extend(buc) # 用新列表扩展原来的列表
return sorted_list
import random
list = [random.randint(1,100) for i in range(10)] # 随机生成10个0到100的数
print(list) # 打印乱序list
list = bucket_sort(list)
print(list) # 打印有序list
(3)运行结果
[50, 21, 51, 56, 81, 75, 44, 77, 31, 96]
[21, 31, 44, 50, 51, 56, 75, 77, 81, 96]
10、基数排序(radix)
(1)思路
- 又称多关键字排序:假如现在有一个员工表,要求按照薪资排序,年龄相同的按照年龄排序。
- 先按照年龄进行排序,再按照薪资进行稳定的排序。
- (简而言之,就是相当于对一个数进行每一位的位数排序。)
- 时间复杂度O(kn) (仅次取计数排序)
- 空间复杂度O(k + n) (k表示数字位数)
(2)代码
def radix_sort(list):
max_num = max(list) # 乱序中最大的数
k = 0 # 最大的数的位数
while 10 ** k <= max_num:
buckets = [[] for _ in range(10)] # 每一位都进行一次桶排序
for var in list:
digit = (var // 10 ** k) % 10 # 取对应位数的值
buckets[digit].append(var)
list.clear() # 分桶完成
for buc in buckets:
list.extend(buc) # 把数写回list
k += 1 # 位数加一
import random
list = list(range(10))
random.shuffle(list)
print(list) # 打印乱序list
radix_sort(list)
print(list) # 打印有序list
(3)运行结果
[4, 1, 0, 6, 5, 8, 9, 7, 3, 2]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]