原理:从第一个元素开始,将相邻的两个元素进行比较,如果前一个元素大于后一个元素就将它们交换,一轮下来最大的元素就会被交换到最后,再对前面的元素进行相同的操作,直到全部排序完成。
模板:
def bubble_sort(nums):
n = len(nums)
for i in range(n):
for j in range(0, n - i - 1): #表示每一轮循环需要比较的元素数量,每一轮结束后要将已排序的元素排除在外。
if nums[j] > nums[j + 1]:
nums[j], nums[j + 1] = nums[j + 1], nums[j]
return nums
原理:选择一个枢轴元素,将序列分为左右两部分,左边的元素都小于枢轴元素,右边的元素都大于它,然后对左右两部分分别进行相同的操作,直到全部排序完成。
模板:
def quick_sort(nums):
if len(nums) <= 1:
return nums
pivot = nums[0] #pivot表示枢轴元素
left_nums = [x for x in nums[1:] if x < pivot]
right_nums = [x for x in nums[1:] if x >= pivot]
return quick_sort(left_nums) + [pivot] + quick_sort(right_nums)
原理:将未排序序列中的第一个元素插入到已排序序列中合适的位置,对于未排序序列中的其他元素进行相同的操作,直到全部排序完成。
模板:
def insertion_sort(nums):
n = len(nums)
for i in range(1, n):
j = i
while j > 0 and nums[j - 1] > nums[j]:
nums[j], nums[j - 1] = nums[j - 1], nums[j]
j -= 1
return nums
原理:希尔排序,也称缩小增量排序,是插入排序的一种高效改进式版本。它的思想是将数组分成若干个子序列,对每个子序列进行插入排序,经过若干轮排序后,整个序列变成有序。与插入排序不同的是,希尔排序是对元素间隔固定的多个子序列分别进行插入排序,随着排序轮数的增加,子序列的长度逐渐缩小,最后变成一个整体有序的序列。
希尔排序通过减小项间距,使得一些逆序对提前得到了交换,从而加快排序的速度。这也是希尔排序高效的主要原因。
模板:
def shell_sort(arr):
n=len(arr)
gap=n//2
while gap>0:
for i in range(gap,n):
j=i
while j>=gap and arr[j]<arr[j-gap]:
arr[j],arr[j-gap]=arr[j-gap],arr[j]
j-=gap
gap//=2
return arr
原理:从第一个元素开始,找到剩余元素中最小的元素,将它与第一个元素交换位置,然后从第二个元素开始重复这个过程,直到全部排序完成。
模板:
def selection_sort(nums):
n = len(nums)
for i in range(n - 1):
min_index = i
for j in range(i + 1, n):
if nums[j] < nums[min_index]:
min_index = j
if min_index != i:
nums[i], nums[min_index] = nums[min_index], nums[i]
return nums
注释:
min_index表示当前未排序元素中最小值的下标。
if nums[j] < nums[min_index]:表示如果找到了比当前最小值还要小的值,就更新min_index。
if min_index != i:表示如果min_index没有变化,就意味着当前元素已经是未排序元素中最小的,不需要交换。
原理:堆排序是一种选择排序。它是借助堆这种数据结构来完成的。堆分为小根堆和大根堆,大根堆就是堆顶是最大值的堆,小根堆就是堆顶是最小值的堆。堆排序主要分为两个步骤:
模板:
def heapify(a, n, i): # 将小的节点向下交换,使之成为一棵大根堆
largest = i
l = 2 * i + 1 # 左子节点
r = 2 * i + 2 # 右子节点
# 找到三个节点中的最大值
if l < n and a[l] > a[largest]:
largest = l
if r < n and a[r] > a[largest]:
largest = r
# 如果最大节点不是当前节点,就交换它们的位置,并递归地调用 heapify
if largest != i:
a[i], a[largest] = a[largest], a[i]
heapify(a, n, largest)
def heap_sort(a):
n = len(a)
# 构建二叉堆
for i in range(n // 2 - 1, -1, -1):
heapify(a, n, i)
# 针对二叉树的每个节点,将根节点与末尾节点交换,末尾节点被"排序",然后 heapify 重新平衡行,从而保证根节点的最大值
for i in range(n - 1, 0, -1):
a[i], a[0] = a[0], a[i]
heapify(a, i, 0)
return a
使用 heapq 模块内置函数实现堆排序的 Python 模板:
import heapq
def heap_sort(arr):
# 将列表转换为堆
heapq.heapify(arr)
# 依次弹出堆中元素,即可获得有序序列
result = []
while arr:
result.append(heapq.heappop(arr))
return result
原理:将序列拆分成每个只有一个元素的子序列,然后对这些子序列进行两两归并,直到所有子序列都归并成一个有序序列。
模板:
def merge_sort(nums):
if len(nums) <= 1:
return nums
mid = len(nums) // 2
left_nums = nums[:mid]
right_nums = nums[mid:]
return merge(merge_sort(left_nums), merge_sort(right_nums)) # 先对左右两个子序列进行排序,然后进行归并
def merge(left_nums, right_nums):
result = []
i, j = 0, 0
while i < len(left_nums) and j < len(right_nums):
if left_nums[i] < right_nums[j]:
result.append(left_nums[i])
i += 1
else:
result.append(right_nums[j])
j += 1
result += left_nums[i:]
result += right_nums[j:]
return result
原理:计数排序是一种非比较排序算法,它的主要思想是对于给定的一组元素,确定每个元素在序列中出现的个数,进而推算出每个元素在最终有序序列中所处的位置。要求输入的数据必须是有确定范围的整数(如内部所有元素均在 0~K 的范围内),并且是离散的元素。
在计数排序中,我们利用待排序数列中的最大值和最小值来确定统计数组的长度,然后逐个遍历计算出待排序序列中每个元素出现的次数。接下来,我们依据统计数组中记录的元素出现次数,从小到大或从大到小地做一个累加,目的是确定待排序元素在有序序列中的索引位置。最后,我们对待排序序列进行遍历,按照有序序列的索引位置将待排序元素放入其应该在的位置,从而得到有序的序列。
适应场景:要求输入的数据必须是有确定范围的整数,适合于最大值和最小值的差值不是很大的情况,或对时间复杂度要求较高,快排O(nlogn) 解决不了,需要O(n) 或题目明确约束要线性时间内运行的场景。基本思想就是把数组元素作为数组的下标,然后用一个临时数组统计该元素出现的次数,例如 temp[i] = m, 表示元素 i 一共出现了 m 次。最后再把临时数组统计的数据从小到大汇总起来。
:LeetCode计数排序,274,561, 912,1122
模板:
def counting_sort(arr):
size = len(arr)
if size<2: # 处理长度为0或1的情况
return arr
# 找到序列中的最大值和最小值,以确定计数数组(c)的长度
max_val = max(arr)
min_val = min(arr)
# 统计数组c用于记录元素i出现的次数
c = [0] * (max_val - min_val + 1)
for i in arr:
c[i - min_val] += 1
# 累加计数数组c并更新这些元素的顺序
for i in range(1, len(c)):
c[i] = c[i - 1] + c[i]
# 创建一个临时数组,用于存储排序后的结果
ans = [0] * size
for i in reversed(arr):
ans[c[i - min_val] - 1] = i
c[i - min_val] -= 1
return ans
原理:基数排序是按照数字位数从低到高依次对待排序数字进行排序,最终可以获得有序序列。具体实现中,每次操作时将待排序序列按照指定位数进行分桶,然后按照顺序将桶中的数字输出,重复该过程直到全部数字排序完成。
以下是基数排序的实现模板:
def radix_sort(nums):
# 找到待排序序列中最大的数字
max_digit = 0
for num in nums:
max_digit = max(max_digit, len(str(num)))
# 从个位到最高位进行排序
for i in range(max_digit):
# 初始化10个桶
bucket = [[] for _ in range(10)]
# 将每个数字分配到对应的桶中
for num in nums:
digit = num // (10 ** i) % 10
bucket[digit].append(num)
# 重构待排序序列为已分配的桶中的数字
nums.clear()
for j in range(10):
nums += bucket[j]
return nums
原理:桶排序是一种按照区间依次对数字进行排序的算法。具体实现中,首先需要将待排序序列分配到不同的桶中,然后对每一个非空的桶进行排序,并将结果合并到一起。在桶排序中,分配每个数字到桶中的过程可以使用不同的划分函数实现,比如可以按照数字的区间、数字的排名等进行划分。
适应场景:数列取值范围过大,或者不是整数时,如double,就可以用桶排序。桶排序的性能并非绝对稳定,理想情况是桶中的元素分布均匀,元素个数等于桶的个数时,时间复杂度可以达到O(n);但如果桶内元素的分布极不均衡,极端情况下第一个桶中有n-1个元素,最后一个桶中有1个元素,时间复杂度将退化为O(nlogn) ,还白白浪费空间,创建了许多空桶。
典型题目:LeetCode桶排序,164,220,347,451,692, 908
以下是桶排序的实现模板:
def linear_sort(nums):
max_value = max(nums) # 找到待排序序列中的最大值
bucket = [0] * (max_value + 1) # 初始化一个长度为max_value + 1的桶,用于存放每个数字的出现次数
for num in nums: # 遍历待排序序列,统计每个数字出现的次数
bucket[num] += 1
count = 0 # 记录桶中出现的数字的个数
for i in range(max_value + 1): # 遍历桶,输出排序结果
while bucket[i] > 0:
nums[count] = i
count += 1
bucket[i] -= 1
return nums