02-常见排序算法
关于排序算法,博客实在太多,不乏好文,图文讲述比较麻烦,一旦学会了以后,再回头来看中心思想又觉得特别简单,本博客只讲述中心思想。
大多数排序算法使用递归来写更加简洁明了,因此下面的但凡能用递归的就用递归
冒泡
最简单的一种排序算法。假设长度为n的数组arr,要按照从小到大排序。则冒泡排序的具体过程可以描述为:首先从数组的第一个元素开始到数组最后一个元素为止,对数组中相邻的两个元素进行比较,如果位于数组左端的元素大于数组右端的元素,则交换这两个元素在数组中的位置。这样操作后数组最右端的元素即为该数组中所有元素的最大值。接着对该数组除最右端的n-1个元素进行同样的操作,再接着对剩下的n-2个元素做同样的操作,直到整个数组有序排列。算法的时间复杂度为O(n^2)。
def BubbleSort(arr, end):
if end <= 0:
return
i = 0
while i < end:
if arr[i] > arr[i + 1]:
arr[i], arr[i + 1] = arr[i + 1], arr[i]
i += 1
BubbleSort(arr, end - 1)
arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 99]
BubbleSort(arr, len(arr) - 1)
print(arr)
选择
选择排序则是从左往右访问过程中,始终标记最小的,一旦确认到了最小值,则将第0个值与最小值换位,依次类推,冒泡保证最后测始终最大,选择保证最左侧始终最小,不过冒泡的过程中存在交换,而选择只在确认到了最小值才进行交换,这个就效率来说,选择比较快一点。
def selectSort(arr, start):
# print(start)
if start >= len(arr) - 1:
return
k = start
# 假设start为最小值,那么起始下标应该是start+1
for i in range(start + 1, len(arr)):
if arr[i] < arr[k]:
k = i
# 交换start和最小值
arr[start], arr[k] = arr[k], arr[start]
selectSort(arr, start + 1)
arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 99]
selectSort(arr, 0)
print(arr)
插入排序
插入排序的核心思想是:假设左侧n个数是有序的,则将第n+1个数从右往左与前n个数进行比较,当前值比n+1大,下标继续左移,如果当前值小于等于n+1,则本轮定位结束,继续n+2的定位
def insertSort(arr, index):
# 如果当前待确认位置的下标已经是最后一个的下一个位置,则没必要继续排序了
if index >= len(arr):
return
# 如果是寻找第index的数的位置,那么index就应该与第0到第index-1个数进行比较
i = index - 1
while i >= 0:
# 如果当前值小于index,则没必要继续向左插入了
if arr[i] <= arr[i + 1]:
break
arr[i], arr[i + 1] = arr[i + 1], arr[i]
i -= 1
insertSort(arr, index + 1)
arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 99]
insertSort(arr, 1)
print(arr)
快排
快速排序(Quick Sort)使用分治法策略。
它的基本思想是:通过一轮排序手段使得基准数放在了他在有序序列中的正确位置,这轮排序以后,基准数左侧的都小于基准数,基准数右侧的大于基准数。
快速排序流程:
(1) 从数列中挑出一个基准值。
(2) 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置。
(3) 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序。
重点就是如何把比基准值小的数放在左边,比基准值大的数放在右边。
以下是一层遍历,基准值归位的操作图
接下来就是对特殊情况进行判断,从而形成完整的代码
def quickSortInfo(arr, start, end):
# 如果没有元素或者只有一个元素,可以直接返回
if start >= end:
return
# 如果是两个元素,则需要判断下这两个元素的大小关系
if end - start == 1:
if arr[start] > arr[end]:
arr[start], arr[end] = arr[end], arr[start]
return
k = start # 基准值坐标
i = start + 1 # 从左往右遍历,找出所有的小于基准值
j = end # 从右往左遍历,找出所有的大于基准值的数
# 只要i,j没有相遇,则循环不会结束,直至找出本轮递归中的基准值的准确位置
while i < j:
# 只要元素i小于基准值,则一直右移
while i != j and arr[k] > arr[i]:
i += 1
# 将k与i-1互换位置
arr[k], arr[i - 1] = arr[i - 1], arr[k]
# 修改k,k已经改变了
k = i - 1
# 只要元素j小于基准值,则一直左移
while i < j and arr[k] < arr[j]:
j -= 1
# 假如说ij相遇了,那本轮递归就结束了。ij相遇需要判断,
if i == j:
if arr[k] > arr[i]:
arr[k], arr[i] = arr[i], arr[k]
k += 1
break
# 如果ij没有相遇,则kij必定是231的形式,需要换一下位置
else:
arr[k], arr[i], arr[j] = arr[j], arr[k], arr[i]
k += 1
i += 1
j -= 1
# 基准值左侧继续递归
quickSortInfo(arr, start, k - 1)
# 基准值右侧继续递归
quickSortInfo(arr, k + 1, end)
arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 99]
quickSortInfo(arr, 0, len(arr) - 1)
print(arr)
堆排序
大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小
此处我们使用大根堆来进行正序排序。
可以看到:
0号元素,左右子节点分别是 2*0+1 2*0+2
1号元素,左右子节点分别是 2*1+1 2*1+2
2号元素,左节点是 2*2+1
因此可以依照此关系,把一个连续数组当作一个完全二叉树。于是就有了如下的堆排序代码。核心思路虽然简单,但是细枝末节却是最麻烦的,下面的代码初看未必看得懂,需要多看几次,我觉得这份代码应该是我能写出来的最高校的堆排序代码
# 该函数用来寻找当前堆的最大值下标
def heapSortInfo(arr, i, length):
# 当前下标小于长度,但是左节点下标又超过了最大下标
# 说明该元素是根节点,直接返回当前下标
if i < length and 2 * i + 1 >= length:
return i
# 走到了这里,已经说明该元素的父节点存在左节点,
# 但是右节点不存在的话,就返回左节点的下标
# 完全二叉树最多只存在一个没有右节点的节点,因此下面的访问条件只会触发一次
if i >= length:
return i - 1
# 取出左子树的最大值下标
left = heapSortInfo(arr, 2 * i + 1, length)
# 取出右子树的最大值下标
right = heapSortInfo(arr, 2 * i + 2, length)
# 三者比较得最最大值下标
max = i if arr[i] > arr[left] else left
max = max if arr[max] > arr[right] else right
return max
def heapSort(arr):
length = len(arr)
# 只要待排序的元素数量超过1个,就要进行排序
while length >= 2:
# 获取最大的元素下标
max = heapSortInfo(arr, 0, length)
# 将最大元素放到数组末尾
arr[length - 1], arr[max] = arr[max], arr[length - 1]
length -= 1
arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 19]
heapSort(arr)
print(arr)
归并排序
归并排序的基本思路如下图所示:
import random
def merge(li, low, mid, high):
# 一次归并
'''
:param li: 列表
:param low: 起始位置
:param mid: 按照那个位置分
:param high: 最后位置
:return:
'''
i = low
j = mid + 1
ltmp = []
while i <= mid and j <= high:
if li[i] <= li[j]:
ltmp.append(li[i])
i += 1
else:
ltmp.append(li[j])
j += 1
while i <= mid:
ltmp.append(li[i])
i += 1
while j <= high:
ltmp.append(li[j])
j += 1
li[low:high + 1] = ltmp
def _merge_sort(li, low, high):
if low < high: # 至少两个元素
mid = (low + high) // 2
_merge_sort(li, low, mid)
_merge_sort(li, mid + 1, high)
merge(li, low, mid, high)
print(li[low:high + 1])
def merge_sort(li):
return _merge_sort(li, 0, len(li) - 1)
li = list(range(16))
random.shuffle(li)
print(li)
merge_sort(li)
print(li)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了