最近经常用到各种排序算法,但是网上的python排序源码质量参差不齐,因此就结合网上的资料和自己的理解,整理了一份可直接使用的排序算法python源码
在网络上(此处)查到了一份排序算法的选取规则,复制到此。
(1)元素个数n大,排序码分布随机,稳定性不做要求 --------- 快速排序
(2)元素个数n大,内存空间允许, 要求稳定性 ------------- 归并排序
(3)元素个数n大,排序码可能正序或逆序,稳定性不做要求 --------- 堆排序、归并排序
(4)元素个数n小,排序码基本有序或随机,要求稳定性 ------------- 插入排序
(5)元素个数n小,稳定性不做要求 ------ 选择排序
(6)元素个数n小,排序码不接近逆序 ---- 插入排序
(7)冒泡排序一般很少用(时间空间成本都高)
因此,常使用的排序算法主要包括 快速排序,归并排序,堆排序
方法: 在无序区通过反复交换找到最大元素放在队首(比较次数多,交换次数多)
主要思想: 前后两两比较,大小顺序错误就交换位置
代码思路:
1.比较相邻元素,如果前者大于后者,就交换位置
2.从队首到队尾,每一对相邻元素都重复上述步骤,最后一个元素为最大元素
3.针对前n-1个元素重复
# 代码
def BubbleSort(arr):
for i in range(len(arr) - 1): # i 控制比较的轮数,减去1是除开自身(两两比较,下句相同)
for j in range(len(arr) - i - 1): # j表示每轮比较的元素范围,每经过一个大轮,就减少了待比较一个元素,减去i
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
if __name__ == "__main__":
arr_in = [6, 5, 18, 2, 16, 15, 19, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
print(arr_in)
arr_out = BubbleSort(arr_in)
print(arr_out)
时间复杂度 O(n^2)
空间复杂度 O(1)
稳定排序
方法:在无序区找到最下的元素放到有序区的队尾(比较次数多,交换次数少)
主要思想:水果摊挑苹果,先选出最大的,再选出次大的,直到最后
选择是对冒泡的优化,比较一轮只交换一次数据
代码思路:
1.找到无序待排序列中最小的元素,和第一个元素交换位置
2.剩下的待排无序序列(2-n)选出最小的元素,和第二个元素交换位置
3.直到最后选择完成
# 代码
def SelectSort(arr):
for i in range(len(arr)): # 找到全局最小
minIndex = i
for j in range(i+1, len(arr)):
if arr[j] < arr[minIndex]:
minIndex = j
arr[i], arr[minIndex] = arr[minIndex], arr[i] # 把全局最小和待排序列首位交换
return arr
if __name__ == "__main__":
arr_in = [6, 5, 18, 2, 16, 15, 19, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
print(arr_in)
arr_out = SelectSort(arr_in)
print(arr_out)
时间复杂度 O(n^2)
空间复杂度 O(1)
非稳定排序
方法: 把无序区的第一个元素插入到有序区的合适位置(比较次数少,交换次数多)
主要思想: 扑克牌打牌时候的插入思想,逐个插入到前面的有序数中
代码思路:
1.选择待排无序序列的第一个元素作为有序数列的第一个元素
2.把第2个元素到最后一个元素看做无序待排序列
3.依次从待排无序序列取出每一个元素,与有序序列的每个元素比较(从右向左扫描),符合条件交换元素位置
# 代码
# 方法一:
# def InsertSort(arr):
# for i in range(1, len(arr)):
# preindex = i - 1 # 有序序列最后一个元素位置
# current = i # 待排无序序列第一个元素位置
# while preindex >= 0 and arr[preindex] > arr[current]: # 对有序序列倒序遍历完成 & 待排元素 > 比较的有序元素
# arr[preindex], arr[current] = arr[current], arr[preindex] # 交换二者位置
# preindex -= 1
# current -= 1
# return arr
# 方法二
def InsertSort(arr):
for i in range(1, len(arr)): # 控制循环的次数,从1开始,默认0位最小
for j in range(i, 0, -1): # 从右向左遍历已排有序序列
if arr[j] < arr[j - 1]: # 相邻两个数比较
arr[j], arr[j - 1] = arr[j - 1], arr[j]
return arr
if __name__ == "__main__":
arr_in = [6, 5, 18, 2, 16, 15, 19, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
print(arr_in)
arr_out = InsertSort(arr_in)
print(arr_out)
时间复杂度 O(n^2)
空间复杂度 O(1)
稳定排序
方法:每一轮按照事先决定的间隔进行插入排序,间隔依次缩小,最后到 1
主要思想: 改进插入排序,从远距离到小距离进行比较-交换,类似冒泡和插入的联合
代码思路:
1.先对数据进行间隔分组,分组数依此指数型减小 (/2)
2.组内元素进行比较交换位置
3.直到最后分组为1
# 方法一: 调用插入排序
# # 1.先写插入排序
# def InsertSort(arr):
# for i in range(0, len(arr)): # 待排无序序列
# for j in range(i, -1, -1): # 已排有序序列,从右向左
# if arr[j] > arr[i]:
# arr[i], arr[j] = arr[j], arr[i]
# i -= 1
# j -= 1 # j会取到-1,如果要保证代码没bug,if中限制j>=0
# return arr
# # 2.再写希尔排序
# def ShellSort(arr):
# gap = int(len(arr) / 2) # 增量最大长度
# while gap > 0:
# for m in range(0, len(arr), gap):
# InsertSort(arr)
# gap = int(gap / 2)
# return arr
# 方法二:直接写希尔排序
# 希尔排序
def ShellSort(arr):
gap = len(arr) # 计算排序序列长度
while gap > 1:
gap = gap // 2 # 分组在减少,组内比较次数在增多
for i in range(gap, len(arr)): # 后半部分遍历
for j in range(i % gap, i, gap): # 前半部分遍历
if arr[i] < arr[j]:
arr[i], arr[j] = arr[j], arr[i]
return arr
if __name__ == "__main__":
arr_in = [6, 5, 18, 2, 16, 15, 19, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17]
print(arr_in)
arr_out = ShellSort(arr_in)
print(arr_out)
时间复杂度 O(n^2)
空间复杂度 O(1)
非稳定排序
方法:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序
主要思想: 分治法:自上而下的递归和自下而上的迭代
代码思路:
1.将待排序序列从中间无限二分划分,直至最小为1
2.再从小到大合并,合并组内重复比较大小
3.直到最后合并完成
def Merge(left, right):
result = []
while len(left) and len(right):
if left[0] <= right[0]: # 如果左边小于右边
result.append(left.pop(0)) # 就把左边取出来放到有序序列
else:
result.append(right.pop(0))
if len(left) != 0:
result += left
if len(right) != 0:
result += right
return result
def MergeSort(arr):
if len(arr) <= 1:
return arr
num = len(arr) // 2 # 二分序列
left = MergeSort(arr[:num])
right = MergeSort(arr[num:])
return Merge(left, right) # 不断递归
if __name__ == "__main__":
arr_in = [6, 5, 18, 2, 16, 15]
print(arr_in)
arr_out = MergeSort(arr_in)
print(arr_out)
时间复杂度 O(n log n)
空间复杂度 O(n)
稳定排序
方法:选取一个基准值,小的在前,大的在后
代码思路:
1.从数列挑选一个基准值
2.根据基准值大小,小于基准值得放在后面,小于基准值的放在前面
3.递归,直到所有子集只剩下一个元素
4.分解完成再一层一层返回,返回规则是:左边分区+基准值+右边分区
def QuickSort(arr):
if len(arr) <= 1:
return arr # 递归跳出的条件
else:
mid = arr[0] # 取第一个数为基准值
# 列表解析式,先执行中间的for,再执行后面if或者for的条件语句,最后执行前面的目的语句
left = QuickSort([l for l in arr[1:] if l < mid]) # 从第二个数据开始,遍历arr,如果小于mid,就递归
right = QuickSort([m for m in arr[1:] if m >= mid]) #
return left + [mid] + right
arr_in = [6, 5, 18, 2, 16, 15]
print(arr_in)
arr_out = QuickSort(arr_in)
print(arr_out)
时间复杂度 O(n log n)
空间复杂度 O(n log n)
不稳定排序
方法: 二叉堆本来就是有序的大顶堆或者小顶堆(存放在列表中的)
主要思想:将待排序列构成一个二叉堆
二叉堆的基本代码:参考文献网址
二叉堆的节点计算(列表从0开始索引) 给出任意结点下标 r 父节点 : (r -1) // 2, (0号元素的父节点还是0,-1/2 =
0) 左子节点: 2 * r + 1 右子节点: 2 * r + 2
代码思路:
1.根据输入的无序序列,构建局部大顶堆:依次 倒序自下向上 遍历所有父节点,把每个父节点和其子节点比较,更大的交换到父节点,构成局部大顶堆
2.构建大顶堆:依次遍历树,倒序把末节点和顶节点交换位置,然后再次构建大顶堆
3.循环完成就结束
# 调整大顶堆
def heap_adjust(heap, heap_size, root):
# 计算节点下标
larger = root # 父节点
left = 2 * root + 1 # 左节点
right = 2 * root + 2 # 右节点
if left < heap_size and heap[larger] < heap[left]: # 找出左右结点更大的位置
larger = left
if right < heap_size and heap[larger] < heap[right]: # 找出左右结点更大的位置
larger = right
if larger != root: # 最大值结点 不是 父节点
heap[larger], heap[root] = heap[root], heap[larger] # 交换父节点和找到的最大值叶子节点
heap_adjust(heap, heap_size, larger)
# 建立大顶堆
def build_max_heap(heap):
heap_size = len(heap)
for i in range((heap_size-2)//2, -1, -1): # 自低向顶构建局部大顶堆
heap_adjust(heap, heap_size, i)
for j in range(heap_size-1, -1, -1):
heap[j], heap[0] = heap[0], heap[j] # 从最后一个节点开始,依此把最后一个节点放到第一个节点位置,再把大节点上浮(为了下一次交换到堆底)
heap_adjust(heap, j, 0) # 大数上浮
return heap
if __name__ == '__main__':
arr_in = [6, 5, 18, 2, 16, 15, -1, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
print(arr_in)
arr_out = build_max_heap(arr_in)
print(arr_out)
时间复杂度 O(n log n)
空间复杂度 O(1)
不稳定排序
方法:统计每个元素出现的次数
明显问题: 只能最大最小值相差不是太大的整数数组排序
代码思路:
1.找出待排序的数组中最大和最小的元素
2.统计数组中每个值为i的元素出现的次数,存入计数数组的第 i-min_value 项
3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4.反向填充目标数组:根据计数数组位置索引,将对应位置的 索引值+ min_value 放入输出数组,每放一个元素就将conter该位置计数值减去1
def CountSort(arr):
max_value = max(arr) # 待排序列的最大值
min_value = min(arr) # 待排序列的最小值
arr_out = [] # 待输出序列
# 创建一个全0列表,计数待排序列中的每个数出现的次数
conter = [0] * (max_value - min_value + 1)
# 计数
for one in arr:
conter[one - min_value] += 1
# 回填
# 遍历计数数组
for index in range(0, len(conter)):
# 当计数数组某位不为零时,证明该位置有数
while conter[index] > 0:
# 添加到输出数组,其值大小为(index + min_value)
arr_out.append(index + min_value)
# 每添加一次,该值的计数减1
conter[index] -= 1
return arr_out
if __name__ == '__main__':
arr_in = [6, 5, 18, 2, 16, 15, -1, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
print(arr_in)
arr_out = CountSort(arr_in)
print(arr_out)
时间复杂度 O(n + m)
空间复杂度 O(n + m)
稳定排序
方法:计数排序的升级版,依次将一定范围内的数放进一个桶,再分别进行排序
代码思路:
1.找出待排序的数组中最大和最小的元素
2.计算每个桶的存储数据范围
3.建立空桶
4.根据桶的存数范围,分配进相应的桶
5.对桶内数据进行排序
6.依次将桶内数据取出来组成新的有序序列
def BucketSort(arr):
max_value = max(arr) # 待排序列的最大值
min_value = min(arr) # 待排序列的最小值
arr_out = [] # 待输出数组
bucket_size = (max_value - min_value) / len(arr) # 根据桶的数量找到每个桶的取值范围
count_list = [[] for i in range(len(arr) + 1)] # 建立桶
for one in arr: # 向桶内分配数字
count_list[int((one - min_value) // bucket_size)].append(one) # 取整,然后存到对应位置
for i in count_list:
for j in sorted(i): # 对桶内的数字进行排序
arr_out.append(j) # 回填
return arr_out
if __name__ == '__main__':
arr_in = [6, 5, 18, 2, 16, 15, -1, 13, 10, 12, 7, 9, 4, 4, 8, 1, 11, 14, 3, 20, 17, 10]
print(arr_in)
arr_out = BucketSort(arr_in)
print(arr_out)
时间复杂度 O(n + m)
空间复杂度 O(n + m)
稳定排序
方法:先依据个位排序,再依据十位排序,再依据百位排序,最后的数据就是有序的
主要思想:反复进行同位相比较
负数不能参与排序
代码思路:
1.建立10个桶(后续存放0-9)
2.计算待排序列中最大数的位数
3.个位相同的数放进同一个桶,并桶内排序
4.取出来组成新序列,再依据十位相同的放进一个桶,桶内排序
5.依此循环直到最高位
6.组成新序列
def RadixSort(arr):
max_value = max(arr) # 待排序列的最大值
arr_out = [] # 待输出数组
max_digits = len(str(max_value)) # 计算序列中最大数的位数
for count_digits in range(max_digits):
bucket_list = [[] for i in range(10)] # 每一轮建立10个桶
for one in arr:
bucket_list[(one // (10 ** count_digits) % 10)].append(one) # 按照位来放入不同的桶
arr = [j for one in bucket_list for j in one] # 按当前桶的顺序重排列表
return arr
if __name__ == '__main__':
arr_in = [16, 5, 1835, 20, 6, 150]
print(arr_in)
arr_out = RadixSort(arr_in)
print(arr_out)
时间复杂度 O(n * m)
空间复杂度 O(n ^ 2)
稳定排序