目录
[1. 选择排序(Selection Sort)](#1. 选择排序(Selection Sort))
[2. 冒泡排序(Bubble Sort)](#2. 冒泡排序(Bubble Sort))
[3. 插入排序(Insertion Sort)](#3. 插入排序(Insertion Sort))
[4. 计数排序(Counting Sort)](#4. 计数排序(Counting Sort))
[5. 归并排序(Merge Sort)](#5. 归并排序(Merge Sort))
[6. 快速排序(Quick Sort)](#6. 快速排序(Quick Sort))
[7. 桶排序(Bucket Sort)](#7. 桶排序(Bucket Sort))
[8. 基数排序(Radix Sort)](#8. 基数排序(Radix Sort))
[9. 堆排序(Heap Sort)](#9. 堆排序(Heap Sort))
[10. 希尔排序(Shell Sort)](#10. 希尔排序(Shell Sort))
料青山略输我峥嵘,判江河亦低我磅礴
------ 25.6.6
一、选择排序回顾
① 遍历数组 :从索引 0
到 n-1
(n
为数组长度)。
② 每轮确定最小值 :假设当前索引 i
为最小值索引 min_index
。从 i+1
到 n-1
遍历,若找到更小元素,则更新 min_index
。
③ 交换元素 :若 min_index ≠ i
,则交换 arr[i]
与 arr[min_index]
。
python
'''
① 遍历数组:从索引 0 到 n-1(n 为数组长度)。
② 每轮确定最小值:假设当前索引 i 为最小值索引 min_index。从 i+1 到 n-1 遍历,若找到更小元素,则更新 min_index。
③ 交换元素:若 min_index ≠ i,则交换 arr[i] 与 arr[min_index]。
'''
def selectionSort(arr: List[int]):
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
if min_index != i:
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
二、冒泡排序回顾
① 初始化 :设数组长度为 n
。
② 外层循环 :遍历 i
从 0
到 n-1
(共 n
轮)。
③ 内层循环 :对于每轮 i
,遍历 j
从 0
到 n-i-2
。
④ 比较与交换 :若 arr[j] > arr[j+1]
,则交换两者。
⑤ 结束条件:重复步骤 2-4,直到所有轮次完成。
python
'''
① 初始化:设数组长度为 n。
② 外层循环:遍历 i 从 0 到 n-1(共 n 轮)。
③ 内层循环:对于每轮 i,遍历 j 从 0 到 n-i-1。
④ 比较与交换:若 arr[j] > arr[j+1],则交换两者。
⑤ 结束条件:重复步骤 2-4,直到所有轮次完成。
'''
def bubbleSort(arr: List[int]):
n = len(arr)
for i in range(n):
for j in range(n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
三、插入排序回顾
① 遍历未排序元素 :从索引 1
到 n-1
。
② 保存当前元素 :将 arr[i]
存入 current
。
③ 元素后移 :从已排序部分的末尾(索引 j = i-1
)向前扫描,将比 current
大的元素后移。直到找到第一个不大于 current
的位置或扫描完所有元素。
④ 插入元素 :将 current
放入 j+1
位置。
python
'''
① 遍历未排序元素:从索引 1 到 n-1。
② 保存当前元素:将 arr[i] 存入 current。
③ 元素后移:从已排序部分的末尾(索引 j = i-1)向前扫描,将比 current 大的元素后移。直到找到第一个不大于 current 的位置或扫描完所有元素。
④ 插入元素:将 current 放入 j+1 位置。
'''
def insertSort(arr: List[int]):
n = len(arr)
for i in range(n):
current = arr[i]
j = i - 1
while current < arr[j] and j >0:
arr[j+1] = arr[j]
j -= 1
arr[j + 1] = current
return arr
四、计数排序回顾
① 初始化 :设数组长度为 n
,元素最大值为 r
。创建长度为 r+1
的计数数组 count
,初始值全为 0。
② 统计元素频率 :遍历原数组 arr
,对每个元素 x,
将 count[x]
加 1。
③ 重构有序数组 :初始化索引 index = 0
。遍历计数数组 count
,索引 v
从 0 到 r,
若 count[v] > 0
,则将 v
填入原数组 arr[index]
,并将 index
加 1。count[v] - 1,重复此步骤直到 count[v]
为 0。
④ 结束条件:当计数数组遍历完成时,排序结束。
python
'''
输入全为非负整数,且所有元素 ≤ r
① 初始化:设数组长度为 n,元素最大值为 r。创建长度为 r+1 的计数数组 count,初始值全为 0。
② 统计元素频率:遍历原数组 arr,对每个元素 x,将 count[x] 加 1。
③ 重构有序数组:初始化索引 index = 0。遍历计数数组 count,索引 v 从 0 到 r,
若 count[v] > 0,则将 v 填入原数组 arr[index],并将 index 加 1。
count[v] - 1,重复此步骤直到 count[v] 为 0。
④ 结束条件:当计数数组遍历完成时,排序结束。
'''
def countingSort(arr: List[int], r: int):
# count = [0] * len(r + 1)
count = [0 for i in range(r + 1)]
for x in arr:
count[x] += 1
index = 0
for v in range(r + 1):
while count[v] > 0:
arr[index] = v
index += 1
count[v] -= 1
return arr
五、归并排序回顾
Ⅰ、递归分解列表
① 终止条件: 若链表为空或只有一个节点(head is None
或 head.next is None
),直接返回头节点。
② 快慢指针找中点: 初始化 slow
和 fast
指针,slow
指向头节点,fast
指向头节点的下一个节点。fast
每次移动两步,slow
每次移动一步。当 fast
到达末尾时,slow
恰好指向链表的中间节点。
③ 分割链表: 将链表从中点断开,head2
指向 slow.next
(后半部分的头节点)。将 slow.next
置为 None
,切断前半部分与后半部分的连接。
④ 递归排序子链表: 对前半部分(head
)和后半部分(head2
)分别递归调用 mergesort
函数。
Ⅱ、合并两个有序列表
① 创建虚拟头节点 :创建一个值为 -1
的虚拟节点 zero
,用于简化边界处理。使用 current
指针指向 zero
,用于构建合并后的链表。
② 比较并合并节点: 遍历两个子链表 head1
和 head2
,比较当前节点的值:若 head1.val <= head2.val
,将 head1
接入合并链表,并移动 head1
指针。否则,将 head2
接入合并链表,并移动 head2
指针。每次接入节点后,移动 current
指针到新接入的节点。
**③ 处理剩余节点:**当其中一个子链表遍历完后,将另一个子链表的剩余部分直接接入合并链表的末尾。
④ 返回合并后的链表: 虚拟节点 zero
的下一个节点即为合并后的有序链表的头节点。
python
'''
Ⅰ、递归分解列表
① 终止条件:若链表为空或只有一个节点(head is None 或 head.next is None),直接返回头节点。
② 快慢指针找中点:初始化 slow 和 fast 指针,slow 指向头节点,fast 指向头节点的下一个节点。fast 每次移动两步,slow 每次移动一步。当 fast 到达末尾时,slow 恰好指向链表的中间节点。
③ 分割链表:将链表从中点断开,head2 指向 slow.next(后半部分的头节点)。将 slow.next 置为 None,切断前半部分与后半部分的连接。
④ 递归排序子链表:对前半部分(head)和后半部分(head2)分别递归调用 mergesort 函数。
Ⅱ、合并两个有序列表
① 创建虚拟头节点:创建一个值为 -1 的虚拟节点 zero,用于简化边界处理。使用 current 指针指向 zero,用于构建合并后的链表。
② 比较并合并节点:遍历两个子链表 head1 和 head2,比较当前节点的值:若 head1.val <= head2.val,将 head1 接入合并链表,并移动 head1 指针。否则,将 head2 接入合并链表,并移动 head2 指针。每次接入节点后,移动 current 指针到新接入的节点。
③ 处理剩余节点:当其中一个子链表遍历完后,将另一个子链表的剩余部分直接接入合并链表的末尾。
④ 返回合并后的链表:虚拟节点 zero 的下一个节点即为合并后的有序链表的头节点。
'''
def mergesort(self, head: ListNode):
if head is None or head.next is None:
return head
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
head2 = slow.next
slow.next = None
return self.merge(self.mergesort(head), self.mergesort(head2))
def merge(self, head1: ListNode, head2: ListNode):
zero = ListNode(-1)
current = zero
while head1 and head2:
if head1.val <= head2.val:
current.next = head1
head1 = head1.next
else:
current.next = head2
head2 = head2.next
current = current.next
current.next = head1 if head1 else head2
return zero.next
六、快速排序回顾
Ⅰ、分区函数 Partition
**① 随机选择基准元素:**根据左右边界下标随机选择基准元素(选择的是元素并非下标),将基准元素赋值变量进行后续比较
**② 交换基准元素:**将基准元素移动到最左边,将基准元素存储在变量中,
**③ 分区操作:**对于基准元素右边的元素,找到第一个小于基准元素的值,移动到最左边;对于基准元素左边的元素,找到第一个大于基准元素的值,移动到最右边
**④ 返回基准元素的最终位置:**循环执行完毕后,基准元素左边的值都小于它,基准元素右边的值都大于它
Ⅱ、递归快速排序函数
**① 定义递归终止条件:**当左索引小于右索引时,结束递归
② 分区操作: 执行第一次分区操作,找到基准元素
**③ 递归调用分区函数:**将基准元素的左边、右边部分分别传入递归函数进行排序
python
'''
Ⅰ、分区函数 Partition
① 随机选择基准元素:根据左右边界下标随机选择基准元素(选择的是元素并非下标),将基准元素赋值变量进行后续比较
② 交换基准元素:将基准元素移动到最左边,将基准元素存储在变量中,
③ 分区操作:对于基准元素右边的元素,找到第一个小于基准元素的值,移动到最左边;对于基准元素左边的元素,找到第一个大于基准元素的值,移动到最右边
④ 返回基准元素的最终位置:循环执行完毕后,基准元素左边的值都小于它,基准元素右边的值都大于它
Ⅱ、递归排序函数
① 定义递归终止条件:当左索引小于右索引时,结束递归
② 分区操作: 执行第一次分区操作,找到基准元素
③ 递归调用分区函数:将基准元素的左边、右边部分分别传入递归函数进行排序
'''
def Partition(arr, left, right):
idx = random.randint(left, right)
arr[left], arr[idx] = arr[idx], arr[left]
l = left
r = right
x = arr[l]
while l < r:
while l < r and x < arr[r]:
r -= 1
if l < r:
arr[l], arr[r] = arr[r], arr[l]
l += 1
while l < r and x > arr[l]:
l += 1
if l < r:
arr[l], arr[r] = arr[r], arr[l]
r -= 1
return l
def quickSort(arr, l, r):
if l >= r:
return
node = self.quickSort(l, r)
self.quickSort(arr, l, node-1)
self.quickSort(arr, node+1, r)
return arr
七、桶排序回顾
**① 初始化桶和频率数组:**创建字符长度+1的桶bucket,索引 i 表示频率为 i 的字符列表;长度为max的频率数组count,用于记录每个字符的出现次数
**② 统计字符频率:**通过 ord(char) 获取字符的ASCII码,作为频率数组的索引
**③ 将字符按照频率放入桶中:**遍历频率数组,将每个字符以频率作为索引放入数组中
**④ 返回桶数组:**返回桶数组,其中每个桶包含对应频率的字符列表
python
def bucketSort(arr, max_val): # 移除 max_val 表示字符编码最大值(如 256)
n = len(arr)
# 初始化桶:索引范围 [0, max_val-1]
bucket = [[] for _ in range(max_val)]
# 分布:按字符编码放入桶
for char in arr:
bucket[ord(char)].append(char) # 索引 = 字符编码值
# 合并桶(索引升序即字符升序)
sorted_arr = []
for b in bucket:
sorted_arr.extend(b) # 每个桶内元素已按插入顺序排列
return sorted_arr # 返回排序后的一维数组
八、基数排序
① 初始化参数和辅助数组 :设置最大元素数MAX_N
为 50000,最大位数MAX_T
为 8,进制BASE
为 10;计算并存储基数的各次幂(如 10⁰, 10¹, ..., 10⁷)到数组PowOfBase
中
② 预处理数组元素 :为每个元素加上BASE^(MAX_T-1)
(即 10⁷),确保所有元素变为正数;这一步是为了处理可能的负数输入,将其转换为正数进行排序
③ 按位进行多轮排序 :从最低有效位(个位)开始,逐位进行处理(共进行MAX_T
轮)
分配阶段:将元素分配到对应的桶(0-9)中对每个元素,计算当前位上的数字(通过整除和取模运算)
收集阶段:按桶的顺序(0 到 9)依次收集元素;将收集的元素依次放回原数组,覆盖原有的顺序
④ 恢复原始数值 :排序完成后,从每个元素中减去BASE^(MAX_T-1)
(即 10⁷);恢复元素的原始值,完成排序过程
python
'''
基数排序
① 初始化参数和辅助数组:设置最大元素数MAX_N为 50000,最大位数MAX_T为 8,进制BASE为 10;计算并存储基数的各次幂(如 10⁰, 10¹, ..., 10⁷)到数组PowOfBase中
② 预处理数组元素:为每个元素加上BASE^(MAX_T-1)(即 10⁷),确保所有元素变为正数;这一步是为了处理可能的负数输入,将其转换为正数进行排序
③ 按位进行多轮排序:从最低有效位(个位)开始,逐位进行处理(共进行MAX_T轮)
分配阶段:将元素分配到对应的桶(0-9)中对每个元素,计算当前位上的数字(通过整除和取模运算)
收集阶段:按桶的顺序(0 到 9)依次收集元素;将收集的元素依次放回原数组,覆盖原有的顺序
④ 恢复原始数值:排序完成后,从每个元素中减去BASE^(MAX_T-1)(即 10⁷);恢复元素的原始值,完成排序过程
'''
MAX_N = 50000 # 最多的元素数
MAX_T = 8 # 元素的最大位数
BASE = 10 # 定义数字的进制
def RedixSort(self, arr):
n = len(arr)
PowOfBase = [1 for i in range(self.MAX_T)] # 代表BASE的多少次方
for i in range(1, self.MAX_T):
PowOfBase[i] = PowOfBase[i - 1] * self.BASE
for i in range(n):
arr[i] += PowOfBase[self.MAX_T - 1]
pos = 0
while pos < self.MAX_T:
RedixBucket = [ [] for i in range(self.BASE)]
for i in range(n):
rdx = arr[i] // PowOfBase[pos] % self.BASE
RedixBucket[rdx].append( arr[i] )
top = 0
for i in range(self.BASE):
for rb in RedixBucket[i]:
arr[top] = rb
top += 1
pos += 1
for i in range(n):
arr[i] -= PowOfBase[self.MAX_T - 1]
return arr
九、堆排序
堆排序
辅助函数(节点关系):
leftSon(idx)
: 返回节点idx
的左子节点索引(2*idx + 1
)。
rightSon(idx)
: 返回节点idx
的右子节点索引(2*idx + 2
)。
parent(idx)
: 返回节点idx
的父节点索引((idx-1)//2
)。
better(a, b)
: 比较函数,默认返回a > b
,用于定义最大堆的比较规则。
Heapify 函数:
Ⅰ、计算子节点索引
leftSon(curr)
和**rightSon(curr)
** 分别计算当前节点的左右子节点索引。
optId
初始化为当前节点索引,用于记录最大值的位置。
Ⅱ、找出最大值索引
比较当前节点与左右子节点的值: 如果左子节点存在且值更大,则更新 optId
为左子节点索引。同理,对右子节点进行相同比较。better
函数默认实现为 a > b
,确保构建最大堆。
Ⅲ、交换并递归调整
如果最大值不在当前节点(curr != optId
),则交换当前节点与最大值节点。
递归调用 Heapify
处理被交换的子节点,确保其所有子树仍满足堆性质。
sortArray 函数:
构建最大堆: 从最后一个非叶子节点(n//2 -1
)开始,向上逐个调用Heapify
,确保整个数组成为最大堆。
循环过程 :将堆顶元素(最大值)与当前未排序部分的最后一个元素交换;减少堆的大小(size = i
),将最大值排除在后续调整范围外;调用 Heapify(0)
重新调整剩余元素为最大堆。
效果:每次循环将当前最大值移至数组末尾,最终形成升序排列。
python
def leftSon(idx):
return 2 * idx + 1
def rightSon(idx):
return 2 * idx + 2
def parent(idx):
return (idx - 1) // 2
def better(a, b):
return a > b
class Solution:
def Heapify(self, heap, size, curr):
leftSonId = leftSon(curr)
rightSonId = rightSon(curr)
optId = curr
if leftSonId < size and better(heap[leftSonId], heap[optId]):
optId = leftSonId
if rightSonId < size and better(heap[rightSonId], heap[optId]):
optId = rightSonId
if curr != optId:
heap[curr], heap[optId] = heap[optId], heap[curr]
self.Heapify(heap, size, optId)
def sortArray(self, nums: List[int]) -> List[int]:
n = len(nums)
for i in range(n // 2, -1, -1):
self.Heapify(nums, n, i)
for i in range(n-1, -1, -1):
nums[0], nums[i] = nums[i], nums[0]
self.Heapify(nums, i, 0)
return nums
十、希尔排序
① 希尔排序
Ⅰ、初始化间隔(增量)
首先计算初始间隔 gap
,通常取数组长度的一半(gap = n // 2
)。这个间隔决定了子序列的划分方式,后续会逐步缩小。
Ⅱ、分组与插入排序
外层循环(间隔控制) :当 gap > 0
时,执行循环体。每次循环结束后将间隔缩小一半(gap = gap // 2
)。
中层循环(遍历每个子序列) :从 gap
开始遍历到数组末尾,每个元素 arr[i]
属于不同的子序列。
内层循环(子序列插入排序) :保存当前元素 arr[i]
到临时变量 temp
。在当前子序列中(间隔为 gap
),从后往前比较元素。如果前一个元素 arr[j - gap]
大于 temp
,则将其向后移动 gap
个位置。重复上述比较和移动操作,直到找到正确的插入位置,然后将 temp
插入该位置。
Ⅲ、缩小间隔
每次完成当前间隔的所有子序列排序后,将间隔缩小一半(例如,从 gap = 4
到 gap = 2
,再到 gap = 1
)。当间隔最终缩小到 1 时,整个数组被视为一个子序列,此时执行的就是标准的插入排序,但由于前面的分组排序已经使数组接近有序,插入排序的效率会更高。
Ⅳ、终止条件
当间隔 gap
减小到 0 时,排序完成,返回排序后的数组。
② 排序数组
调用希尔排序函数,传入列表(数组)nums
python
class Solution:
def shell_sort(self, 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 = gap // 2 # 缩小间隔
return arr
def sortArray(self, nums: List[int]) -> List[int]:
return self.shell_sort(nums)
十一、十大排序算法对比
算法 | 时间复杂度 | 空间复杂度 | 稳定性 | 数据规模 | 数据特性 | 典型应用场景 |
---|---|---|---|---|---|---|
选择排序 | O(n²) | O(1) | 不稳定 | 小规模 | 任意顺序 | 教学示例、简单场景(如嵌入式设备) |
冒泡排序 | O(n²) | O(1) | 稳定 | 小规模 | 近乎有序 | 轻量级数据处理、教学演示 |
插入排序 | O(n²) | O(1) | 稳定 | 小规模 | 近乎有序或少量元素 | 链表排序、实时数据插入场景 |
计数排序 | O(n+k) | O(n+k) | 稳定 | 中等规模 | 数值范围小(k 已知) | 整数排序(如高考分数统计) |
归并排序 | O(n log n) | O(n) | 稳定 | 大规模 | 任意顺序,需稳定性 | 外排序、分布式系统(如 Hadoop) |
快速排序 | O(n log n) | O(log n) | 不稳定 | 大规模 | 任意顺序 | 通用排序(如编程语言内置 sort 函数) |
桶排序 | O(n + k) | O(n+k) | 稳定 | 大规模 | 均匀分布、浮点数据 | 海量数据排序(如日志分析) |
基数排序 | O(d(n + k)) | O(n + k) | 稳定 | 大规模 | 多关键字、固定长度数据 | 字符串排序(如姓名按拼音排序) |
堆排序 | O(n log n) | O(1) | 不稳定 | 大规模 | 任意顺序,需原地排序 | 内存受限场景(如嵌入式系统) |
希尔排序 | O(n log²n) - O(n²) | O(1) | 不稳定 | 中等规模 | 中等逆序度 | 数据库索引排序、文件系统排序 |
十二、各算法详解与应用场景
1. 选择排序(Selection Sort)
- 核心思想:每次从未排序部分选择最小值,与未排序部分的第一个元素交换。
- 优点:实现简单,原地排序。
- 缺点:时间复杂度高,不适用于大规模数据。
- 应用场景 :
- 教学场景或简单程序(如玩具项目)。
- 内存严格受限且数据量极小(如几百个元素)。
2. 冒泡排序(Bubble Sort)
- 核心思想:相邻元素两两比较,逆序时交换,将最大值逐步 "冒泡" 到末尾。
- 优点:稳定排序,实现简单。
- 缺点:平均效率低,仅适用于小规模数据。
- 优化:引入标志位提前终止(如鸡尾酒排序)。
- 应用场景 :
- 教育演示(理解排序逻辑)。
- 小规模、近乎有序的数据(如已排序数据的微小更新)。
3. 插入排序(Insertion Sort)
- 核心思想:将元素逐个插入已排序部分的合适位置,类似打牌时整理手牌。
- 优点:稳定排序,对近乎有序的数据效率高(O (n) 时间)。
- 缺点:大规模逆序数据下效率低。
- 变种:希尔排序(分组插入优化)。
- 应用场景 :
- 链表排序(无需随机访问,插入操作高效)。
- 实时数据处理(如传感器数据逐个插入排序)。
4. 计数排序(Counting Sort)
- 核心思想:统计每个值的出现次数,按顺序重建数组。
- 前提:数据范围有限(k 已知),通常为非负整数。
- 优点:线性时间复杂度,稳定排序。
- 缺点:空间复杂度高(需额外数组存储计数)。
- 应用场景 :
- 整数排序(如 100 以内的学生成绩排序)。
- 数据压缩前的预处理(如统计字符频率)。
5. 归并排序(Merge Sort)
- 核心思想:分治策略,递归分割数组,合并时排序。
- 优点:稳定排序,时间复杂度稳定为 O (n log n)。
- 缺点:需要 O (n) 额外空间(无法原地排序)。
- 变种:自然归并排序(利用已排序子序列)。
- 应用场景 :
- 外排序(处理无法一次性加载到内存的数据)。
- 需要稳定性的场景(如排序含有相同键的记录)。
6. 快速排序(Quick Sort)
- 核心思想:分治策略,选择枢轴元素,将数组划分为两部分递归排序。
- 优点:平均时间复杂度低,原地排序(空间复杂度 O (log n))。
- 缺点:最坏情况 O (n²)(可通过随机枢轴或三数取中法优化)。
- 应用场景 :
- 通用排序(如 Python 的
sorted()
、C++ 的std::sort
)。 - 大规模数据排序(如数据库查询结果排序)。
- 通用排序(如 Python 的
7. 桶排序(Bucket Sort)
- 核心思想:将数据分配到有限数量的桶中,每个桶内单独排序,再合并结果。
- 前提:数据分布均匀,桶的数量合理。
- 优点:线性时间复杂度(若桶内排序为 O (1))。
- 缺点:对非均匀分布数据效率低。
- 应用场景 :
- 浮点数据排序(如 [0,1) 区间的随机数)。
- 海量数据并行处理(如 MapReduce 中的排序阶段)。
8. 基数排序(Radix Sort)
- 核心思想:按数字的每一位(如个位、十位)依次排序,从最低位到最高位。
- 前提:数据有固定长度(如字符串、固定位数的整数)。
- 优点:稳定排序,适用于多关键字排序。
- 缺点:依赖数据进制(通常为 10 进制或 256 进制)。
- 应用场景 :
- 字符串排序(如按字典序排序姓名)。
- 整数排序(如身份证号码、邮政编码)。
9. 堆排序(Heap Sort)
- 核心思想:利用最大堆或最小堆,每次将堆顶元素与末尾交换,调整堆结构。
- 优点:原地排序(O (1) 空间),时间复杂度稳定为 O (n log n)。
- 缺点:不稳定排序,缓存性能较差(非连续访问数组)。
- 应用场景 :
- 内存受限环境(如嵌入式设备、实时系统)。
- 需要在线处理数据(如动态维护 Top-K 元素)。
10. 希尔排序(Shell Sort)
- 核心思想:分组插入排序,增量序列逐步缩小至 1,最终用插入排序收尾。
- 优点:比插入排序更快,适用于中等规模数据。
- 缺点:时间复杂度依赖增量序列,稳定性差。
- 优化:使用 Sedgewick 序列或 Hibbard 序列提升效率。
- 应用场景 :
- 中等规模数组(如几千到几万元素)。
- 数据库索引排序(如 B + 树节点内部排序)。
十三、各大排序算法的决策指南
1.数据规模
小规模(n<1000):选择插入排序、冒泡排序或选择排序(实现简单)。
大规模(n≥1000):优先快速排序、归并排序或堆排序。
2.数据特性
近乎有序:插入排序或冒泡排序(优化后效率高)。
整数 / 字符串:计数排序、基数排序(利用数据特性降维复杂度)。
分布均匀:桶排序(线性时间复杂度)。
3.稳定性需求
**需要稳定排序:**归并排序、基数排序、冒泡排序。
**无需稳定排序:**快速排序、堆排序、希尔排序。
4.空间限制
**内存紧张:**堆排序(O (1) 空间)、希尔排序(O (1) 空间)。
**空间充足:**归并排序(O (n) 空间)、计数排序(O (n+k) 空间)。
5.特殊场景:
**实时数据:**插入排序(动态插入)。
**并行处理:**桶排序(可分布式实现)。
**外存数据:**归并排序(分块处理)。
十大排序算法各有优劣,实际应用中需根据数据规模、特性、稳定性要求及环境限制综合选择。例如:
日常编程首选快速排序(高效、通用)。
嵌入式系统优先堆排序(省内存)。
教学或简单场景使用插入 / 冒泡排序(易理解)。
大数据场景考虑桶排序、基数排序或分布式归并排序。