排序算法是计算机科学中用于将数据元素按照特定顺序进行排列的算法,常见的排序算法有以下几类:
比较排序
非比较排序
基本原理
冒泡排序的基本思想是通过相邻元素之间的比较和交换,将最大(或最小)的元素逐步 “浮” 到数组的末尾(或开头),就像气泡在水中上升一样,每一轮比较都会将当前未排序部分的最大(或最小)元素移动到正确的位置。具体过程如下:
def bubble_sort(arr):
n = len(arr)
for i in range(n):
# 最后i个元素已经排好序,无需再比较
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(bubble_sort(arr))
算法复杂度
算法稳定性
冒泡排序是一种稳定的排序算法。在比较和交换元素时,如果两个相等的元素相邻,且前一个元素在后面元素之前,只有当前一个元素大于后面元素时才会交换位置,相等时不会交换,所以相等元素的相对顺序在排序前后不会改变。
基本原理
选择排序(Selection Sort)的核心思想是每一轮从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置(对于升序排序是起始位置,降序排序则是末尾位置),直到全部待排序的数据元素排完。具体步骤如下:
算法示例
以下是使用 Python 实现选择排序的代码:
def selection_sort(arr):
n = len(arr)
for i in range(n):
# 假设当前未排序部分的第一个元素是最小值
min_index = i
for j in range(i + 1, n):
if arr[j] < arr[min_index]:
min_index = j
# 将找到的最小值与当前未排序部分的第一个元素交换位置
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
# 测试
arr = [663636]
print(selection_sort(arr))
算法复杂度
算法稳定性
选择排序是一种不稳定的排序算法。例如,对于数组 [5, 8, 5, 2]
,在第一轮选择中,会将最小元素 2
与第一个 5
交换位置,这样两个 5
的相对顺序就发生了改变,所以选择排序不能保证相等元素的相对顺序不变。
基本原理
插入排序(Insertion Sort)的工作原理就像我们整理扑克牌一样。它将数组分为已排序和未排序两部分,初始时已排序部分只有一个元素(通常是数组的第一个元素),然后依次从未排序部分取出元素,将其插入到已排序部分的合适位置,使得已排序部分始终保持有序,直到未排序部分为空。具体步骤如下:
算法示例
以下是使用 Python 实现插入排序的代码:
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
# 取出当前待插入的元素
key = arr[i]
j = i - 1
# 将比 key 大的元素后移
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j = j - 1
# 插入 key
arr[j + 1] = key
return arr
# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(insertion_sort(arr))
插入排序是一种稳定的排序算法。在插入元素时,如果遇到相等的元素,不会将待插入元素插入到相等元素的前面,而是保持它们原来的相对顺序,所以相等元素在排序前后的相对位置不会改变。
基本原理
希尔排序(Shell Sort)是对插入排序的一种改进,也称为缩小增量排序。插入排序在处理基本有序的数据时效率较高,但在处理大规模无序数据时,每次只能将元素移动一位,效率较低。希尔排序通过引入一个“增量”的概念,将原始数据分成多个子序列,对每个子序列分别进行插入排序,随着增量逐渐减小,子序列的长度逐渐增加,整个序列会变得越来越接近有序,最后当增量减至 1 时,整个序列就相当于进行一次普通的插入排序,但此时由于数据已经基本有序,插入排序的效率会大大提高。
具体步骤如下:
算法示例
以下是使用 Python 实现希尔排序的代码:
def shell_sort(arr):
n = len(arr)
# 初始增量为数组长度的一半
gap = n // 2
while gap > 0:
for i in range(gap, n):
temp = arr[i]
j = i
# 对相隔 gap 的元素进行插入排序
while j >= gap and arr[j - gap] > temp:
arr[j] = arr[j - gap]
j -= gap
arr[j] = temp
# 缩小增量
gap //= 2
return arr
# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(shell_sort(arr))
算法复杂度
算法稳定性
希尔排序是一种不稳定的排序算法。由于在不同的子序列中进行插入排序时,相等元素可能会被交换位置,所以不能保证相等元素的相对顺序在排序前后不变。例如,对于数组 [3, 5, 3, 1]
,在某个增量下进行排序时,可能会改变两个 3
的相对顺序。
基本原理
归并排序(Merge Sort)是采用分治法(Divide and Conquer)的一个非常典型的应用。分治法的核心思想是将一个大问题分解为多个小问题,分别解决这些小问题,然后将小问题的解合并起来得到大问题的解。归并排序具体分为两个步骤:
算法示例
以下是使用 Python 实现归并排序的代码:
def merge_sort(arr):
if len(arr) <= 1:
return arr
# 分解数组
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
# 递归排序左右子数组
left_half = merge_sort(left_half)
right_half = merge_sort(right_half)
# 合并已排序的子数组
return merge(left_half, right_half)
def merge(left, right):
result = []
i = j = 0
# 比较左右子数组元素,依次添加到结果数组
while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
# 添加左子数组剩余元素
result.extend(left[i:])
# 添加右子数组剩余元素
result.extend(right[j:])
return result
# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(merge_sort(arr))
算法复杂度
算法稳定性
归并排序是一种稳定的排序算法。在合并两个子数组时,如果遇到相等的元素,会先将左边子数组的元素放入结果数组,这样就保证了相等元素的相对顺序不变。
基本原理
快速排序(Quick Sort)同样采用分治法的思想,它的核心在于选择一个基准元素(pivot),通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比基准元素小,另一部分记录的关键字均比基准元素大,然后分别对这两部分继续进行排序,以达到整个序列有序。具体步骤如下:
算法示例
以下是使用 Python 实现快速排序的代码:
def quick_sort(arr):
if len(arr) <= 1:
return arr
else:
# 选择第一个元素作为基准元素
pivot = arr[0]
# 小于基准元素的元素组成的子数组
left = [x for x in arr[1:] if x <= pivot]
# 大于基准元素的元素组成的子数组
right = [x for x in arr[1:] if x > pivot]
# 递归排序左右子数组并合并结果
return quick_sort(left) + [pivot] + quick_sort(right)
# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(quick_sort(arr))
算法复杂度
算法稳定性
快速排序是一种不稳定的排序算法。在分区过程中,相等元素的相对顺序可能会发生改变。例如,在分区时,相等元素可能会被交换到不同的子数组中,从而导致它们的相对顺序发生变化。
基本原理
堆排序(Heap Sort)是利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并且满足堆积的性质:每个节点的值都大于或等于其子节点的值(大顶堆),或者每个节点的值都小于或等于其子节点的值(小顶堆)。堆排序的基本思想是先将待排序的数组构建成一个堆,然后不断地从堆中取出最大(大顶堆)或最小(小顶堆)的元素,将其放到已排序序列的末尾,同时调整剩余元素,使其仍然满足堆的性质,重复这个过程直到整个数组有序。具体步骤如下:
算法示例
以下是使用 Python 实现堆排序(升序,使用大顶堆)的代码:
def heapify(arr, n, i):
# 初始化根节点为最大值
largest = i
left = 2 * i + 1
right = 2 * i + 2
# 如果左子节点大于根节点
if left < n and arr[left] > arr[largest]:
largest = left
# 如果右子节点大于当前最大值
if right < n and arr[right] > arr[largest]:
largest = right
# 如果最大值不是根节点
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
# 递归调整受影响的子树
heapify(arr, n, largest)
def heap_sort(arr):
n = len(arr)
# 构建大顶堆
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
# 一个个交换元素
for i in range(n - 1, 0, -1):
arr[0], arr[i] = arr[i], arr[0] # 交换堆顶和当前末尾元素
heapify(arr, i, 0) # 调整剩余元素为大顶堆
return arr
# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(heap_sort(arr))
算法复杂度
算法稳定性
堆排序是一种不稳定的排序算法。在交换堆顶元素和末尾元素的过程中,可能会改变相等元素的相对顺序。例如,在调整堆的过程中,相等元素可能会被交换到不同的位置,导致它们的相对顺序发生变化。
基本原理
计数排序(Counting Sort)是一种非比较排序算法,其核心思想是通过统计数组中每个元素出现的次数,然后根据统计结果将元素按照顺序输出,从而实现排序。该算法适用于待排序元素的取值范围较小且为整数的情况。具体步骤如下:
算法示例
以下是使用 Python 实现计数排序的代码:
def counting_sort(arr):
if not arr:
return arr
# 找出最大值和最小值
min_val = min(arr)
max_val = max(arr)
# 计算计数数组的长度
range_size = max_val - min_val + 1
# 初始化计数数组
count = [0] * range_size
# 统计每个元素出现的次数
for num in arr:
count[num - min_val] += 1
# 计算累积次数
for i in range(1, len(count)):
count[i] += count[i - 1]
# 初始化结果数组
result = [0] * len(arr)
# 输出排序结果
for num in reversed(arr):
index = count[num - min_val] - 1
result[index] = num
count[num - min_val] -= 1
return result
# 测试
arr = [4, 2, 2, 8, 3, 3, 1]
print(counting_sort(arr))
算法复杂度
算法稳定性
计数排序是一种稳定的排序算法。在输出排序结果时,通过从后往前遍历待排序数组,保证了相等元素的相对顺序不变。例如,对于数组 [2, 2]
,在排序过程中,后面的 2
会在前面的 2
之后被放入结果数组,从而保持了它们原来的相对顺序。
基本原理
桶排序(Bucket Sort)是一种分布式排序算法,它的核心思想是将待排序的数据分到有限数量的桶里,每个桶再分别进行排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后将各个桶中的数据依次取出,合并成一个有序序列。具体步骤如下:
算法示例
以下是使用 Python 实现桶排序的代码:
def bucket_sort(arr):
if not arr:
return arr
# 确定桶的数量
num_buckets = 10
# 找出最大值和最小值
min_val = min(arr)
max_val = max(arr)
# 计算每个桶的范围
bucket_range = (max_val - min_val) / num_buckets
# 初始化桶
buckets = [[] for _ in range(num_buckets)]
# 将数据分配到桶中
for num in arr:
index = int((num - min_val) // bucket_range)
if index == num_buckets:
index = num_buckets - 1
buckets[index].append(num)
# 对每个桶内的数据进行排序
for bucket in buckets:
bucket.sort()
# 合并各个桶中的数据
result = []
for bucket in buckets:
result.extend(bucket)
return result
# 测试
arr = [0.897, 0.565, 0.656, 0.1234, 0.665, 0.3434]
print(bucket_sort(arr))
算法复杂度
算法稳定性
桶排序的稳定性取决于桶内排序算法的稳定性。如果使用的桶内排序算法是稳定的(如插入排序),那么桶排序就是稳定的;如果使用的桶内排序算法是不稳定的(如快速排序),那么桶排序就是不稳定的。
基本原理
基数排序(Radix Sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。它是通过多轮的排序来完成整个排序过程,每一轮只关注数据的某一位数字。具体步骤如下:
算法示例
以下是使用 Python 实现基数排序的代码,这里使用计数排序作为每一轮的稳定排序算法:
def counting_sort_for_radix(arr, exp):
n = len(arr)
output = [0] * n
count = [0] * 10
# 统计当前位上每个数字出现的次数
for i in range(n):
index = arr[i] // exp
count[index % 10] += 1
# 计算累积次数
for i in range(1, 10):
count[i] += count[i - 1]
# 构建输出数组
i = n - 1
while i >= 0:
index = arr[i] // exp
output[count[index % 10] - 1] = arr[i]
count[index % 10] -= 1
i -= 1
# 将输出数组复制回原数组
for i in range(n):
arr[i] = output[i]
def radix_sort(arr):
if not arr:
return arr
# 找到最大数,确定最大位数
max_num = max(arr)
exp = 1
while max_num // exp > 0:
# 按当前位进行计数排序
counting_sort_for_radix(arr, exp)
exp *= 10
return arr
# 测试
arr = [170, 45, 75, 90, 802, 24, 2, 66]
print(radix_sort(arr))
算法复杂度
算法稳定性
基数排序是一种稳定的排序算法。这是因为每一轮使用的计数排序是稳定的,在排序过程中,相等元素的相对顺序不会改变,所以经过多轮排序后,整个排序过程也是稳定的。