奇偶排序 (Odd-Even Sort):可并行的冒泡变体
摘要:本文从冒泡排序"串行比较导致无法并行加速"的局限出发,图解奇偶排序如何通过"交替比较奇偶索引对"实现天然并行化。给出了支持升序/降序的 Python 完整实现(含线程模拟并行版),分析了其 O(n²) 串行复杂度与 O(n) 并行复杂度的差异,并通过与冒泡排序的性能对比验证了并行加速效果。最后结合 GPU/SIMD 并行计算场景讨论其工程价值与面试高频考点。
本文属于专栏《算法》系列 1 第 11 篇 | 上一篇:梳排序 (Comb Sort)| 下一篇:1-12-耐心排序-PatienceSort
文章目录
- [奇偶排序 (Odd-Even Sort):可并行的冒泡变体](#奇偶排序 (Odd-Even Sort):可并行的冒泡变体)
-
- 一、问题引入
- 二、算法原理图解
- 三、代码实现
- 四、复杂度分析
- 五、横向对比
- 六、工程实战
-
- [场景一:GPU 并行排序](#场景一:GPU 并行排序)
- [场景二:SIMD 向量化排序](#场景二:SIMD 向量化排序)
- 为什么标准库不用奇偶排序?
- 七、常见误区与面试题
- 八、总结
一、问题引入
在前面的文章中,我们多次遇到冒泡排序的身影。冒泡排序的核心操作是"比较并交换相邻元素对",每一轮需要串行执行 n-1 次比较。这些比较之间存在数据依赖------前一次比较可能改变数组状态,影响后一次比较的结果,因此无法并行。
为什么"可并行性"是一个重要维度?
考虑以下场景:GPU/SIMD 并行计算排序。
现代 GPU 拥有数千个并行处理核心,一次可以同时执行大量独立操作。但冒泡排序的比较序列是串行依赖的------比较 (0,1) → 比较 (1,2) → 比较 (2,3) → ...,每一步都依赖前一步的结果,即使有 1000 个核心也用不上。
能不能设计一种排序,让每一步内的比较互不依赖,可以同时执行?
奇偶排序的回答:可以。它将每轮冒泡拆为两个阶段:
| 阶段 | 比较对 | 是否重叠 | 可否并行 |
|---|---|---|---|
| 偶数阶段 | (0,1), (2,3), (4,5), ... | 不重叠 | 可以 |
| 奇数阶段 | (1,2), (3,4), (5,6), ... | 不重叠 | 可以 |
偶数阶段比较的每对元素索引互不相邻((0,1) 和 (2,3) 之间隔着索引 1 和 2,但实际操作的位置 0、1 与 2、3 完全不同),因此可以同时执行。奇数阶段同理。两个阶段交替进行,最终使整个数组有序。
问题定义:
- 输入 :含 n 个元素的可比较数组
arr - 输出:按升序(或降序)排列的数组
- 核心约束:每阶段内的比较操作互不重叠,可并行执行
- 核心操作:偶数阶段比较 → 奇数阶段比较 → 交替直到有序
二、算法原理图解
核心思想
奇偶排序(又称砖排序,Brick Sort)是对冒泡排序的并行化改造。冒泡排序每轮比较所有相邻对,但比较之间存在数据依赖。奇偶排序将每轮拆为两个独立阶段:偶数阶段 比较所有偶数索引对,奇数阶段比较所有奇数索引对。同一阶段内的所有比较互不重叠,可以并行执行。
关键洞察:偶数阶段比较 (0,1) 和 (2,3) 操作的数组位置完全不同------一个写位置 0、1,另一个写位置 2、3,互不干扰。这就是"天然并行"的来源。
文字图解执行过程
以 [5, 3, 8, 1, 2, 4] 升序排序为例:
初始状态: [5, 3, 8, 1, 2, 4]
索引: 0 1 2 3 4 5
--- 第 1 轮 偶数阶段:比较 (0,1), (2,3), (4,5) ---
(5,3) 5>3 交换 → 3, 5, 8, 1, 2, 4
(8,1) 8>1 交换 → 3, 5, 1, 8, 2, 4
(2,4) 2<4 不换 → 3, 5, 1, 8, 2, 4
以上三组比较互不重叠,可并行执行
偶数阶段后: [3, 5, 1, 8, 2, 4]
--- 第 1 轮 奇数阶段:比较 (1,2), (3,4) ---
(5,1) 5>1 交换 → 3, 1, 5, 8, 2, 4
(8,2) 8>2 交换 → 3, 1, 5, 2, 8, 4
以上两组比较互不重叠,可并行执行
奇数阶段后: [3, 1, 5, 2, 8, 4]
--- 第 2 轮 偶数阶段:比较 (0,1), (2,3), (4,5) ---
(3,1) 3>1 交换 → 1, 3, 5, 2, 8, 4
(5,2) 5>2 交换 → 1, 3, 2, 5, 8, 4
(8,4) 8>4 交换 → 1, 3, 2, 5, 4, 8
偶数阶段后: [1, 3, 2, 5, 4, 8]
--- 第 2 轮 奇数阶段:比较 (1,2), (3,4) ---
(3,2) 3>2 交换 → 1, 2, 3, 5, 4, 8
(5,4) 5>4 交换 → 1, 2, 3, 4, 5, 8
奇数阶段后: [1, 2, 3, 4, 5, 8]
--- 第 3 轮 偶数+奇数:无交换发生 ---
最终结果: [1, 2, 3, 4, 5, 8]
总轮数: 3(每轮含 1 个偶数阶段 + 1 个奇数阶段)
关键观察
- 阶段内无重叠:偶数阶段的 (0,1)、(2,3)、(4,5) 操作的索引对完全不同,互不干扰
- 交替收敛:偶数阶段消除偶数位置的逆序对,奇数阶段消除奇数位置的逆序对,交替进行直至全部有序
- 提前终止:若一轮(偶数+奇数)中无任何交换,说明数组已有序,立即结束
- 与冒泡排序等价:串行执行时,奇偶排序的比较次数和交换次数与冒泡排序同阶,但收敛路径不同
与冒泡排序的本质区别
| 维度 | 冒泡排序 | 奇偶排序 |
|---|---|---|
| 比较顺序 | 串行:(0,1)→(1,2)→(2,3)→... | 分组:偶数阶段并行 + 奇数阶段并行 |
| 阶段内依赖 | 有依赖(前一步影响后一步) | 无依赖(同阶段比较互不重叠) |
| 可并行性 | 不可并行 | 天然可并行 |
| 串行比较次数 | n(n-1)/2 | n(n-1)/2(同阶) |
| 串行交换次数 | = 逆序对数 | = 逆序对数(同阶) |
| 稳定性 | 稳定 | 稳定 |
核心差异:冒泡排序的比较序列是"链式依赖"的(每步依赖前一步),无法并行。奇偶排序通过将相邻对拆为奇偶两组,打破了依赖链------同组内的比较互不干扰,可以同时执行。代价是收敛可能需要更多轮次(因为每轮只处理一半的相邻对)。
三、代码实现
完整代码
通过网盘分享的文件:算法
链接: https://pan.baidu.com/s/1DTJt1X2Is_IQeH5fAXvtZg?pwd=yyqf 提取码: yyqf
--来自百度网盘超级会员v4的分享
串行版:标准奇偶排序
python
def odd_even_sort(arr, ascending=True):
"""
奇偶排序(砖排序):交替比较奇/偶索引位置对,天然可并行。
核心改进:冒泡排序每轮串行比较所有相邻对,无法并行。
奇偶排序将每轮拆为两个阶段------奇数阶段和偶数阶段,
每个阶段内的比较互不重叠,可以并行执行。
时间复杂度:O(n²) | 空间复杂度:O(1) | 稳定排序
参数:
arr: 待排序列表
ascending: 排序方向,True=升序(默认),False=降序
返回:
排序后的列表(原地排序)
"""
n = len(arr)
if n <= 1:
return arr
sorted_flag = False
while not sorted_flag:
sorted_flag = True
# 偶数阶段:比较 (0,1), (2,3), (4,5), ...
for i in range(0, n - 1, 2):
should_swap = arr[i] > arr[i + 1] if ascending else arr[i] < arr[i + 1]
if should_swap:
arr[i], arr[i + 1] = arr[i + 1], arr[i]
sorted_flag = False
# 奇数阶段:比较 (1,2), (3,4), (5,6), ...
for i in range(1, n - 1, 2):
should_swap = arr[i] > arr[i + 1] if ascending else arr[i] < arr[i + 1]
if should_swap:
arr[i], arr[i + 1] = arr[i + 1], arr[i]
sorted_flag = False
return arr
四个关键设计解析
设计1:偶数阶段索引从 0 开始,步长 2
python
for i in range(0, n - 1, 2): # i = 0, 2, 4, ...
为什么从 0 开始步长 2? 偶数阶段比较 (0,1)、(2,3)、(4,5) 等对。每对的起始索引是偶数:0、2、4...步长 2 确保只访问偶数起始位置,且每对的操作区间 [i, i+1] 与下一对 [i+2, i+3] 完全不重叠------这是并行安全的根本保证。
设计2:奇数阶段索引从 1 开始,步长 2
python
for i in range(1, n - 1, 2): # i = 1, 3, 5, ...
为什么从 1 开始? 奇数阶段比较 (1,2)、(3,4)、(5,6) 等对。每对的起始索引是奇数,与偶数阶段错开一位。两个阶段交替执行,覆盖了所有相邻对------偶数阶段处理 (0,1)、(2,3)...,奇数阶段处理 (1,2)、(3,4)...,合在一起恰好覆盖所有 n-1 个相邻对。
设计3:sorted_flag 提前终止
python
sorted_flag = True # 每轮开始时假设已有序
# ... 任何交换发生时设为 False
while not sorted_flag: # 一整轮无交换 → 已有序 → 结束
为什么需要提前终止? 如果一轮(偶数+奇数)中没有任何交换发生,说明所有相邻对都已有序,整个数组必然有序。这避免了已有序数组的多余遍历------已有序时只需一轮即可检测并退出。
设计4:升序/降序统一条件
python
should_swap = arr[i] > arr[i + 1] if ascending else arr[i] < arr[i + 1]
为什么用三元表达式? 升序时前大于后则交换(大值后移),降序时前小于后则交换(小值后移)。一条语句统一两种方向,避免写两份重复代码,降低维护成本。
并行版:线程模拟
python
def odd_even_sort_parallel(arr, ascending=True):
"""
奇偶排序(模拟并行版):用线程模拟并行比较。
每个阶段的比较互不重叠,可以分配到不同线程/处理器并行执行。
实际并行场景下,每阶段耗时从 O(n) 降至 O(1)(n/2 个处理器同时比较)。
参数:
arr: 待排序列表
ascending: 排序方向,True=升序(默认),False=降序
返回:
排序后的列表(原地排序)
"""
n = len(arr)
if n <= 1:
return arr
import threading
sorted_flag = False
while not sorted_flag:
sorted_flag = True
# 偶数阶段:n/2 个比较可并行
threads = []
results = [] # 收集各线程是否发生交换
def compare_and_swap(i):
should_swap = arr[i] > arr[i + 1] if ascending else arr[i] < arr[i + 1]
if should_swap:
arr[i], arr[i + 1] = arr[i + 1], arr[i]
return True
return False
for i in range(0, n - 1, 2):
def worker(idx=i):
if compare_and_swap(idx):
results.append(True)
t = threading.Thread(target=worker)
threads.append(t)
t.start()
for t in threads:
t.join()
if results:
sorted_flag = False
# 奇数阶段:n/2 个比较可并行
threads = []
results = []
for i in range(1, n - 1, 2):
def worker(idx=i):
if compare_and_swap(idx):
results.append(True)
t = threading.Thread(target=worker)
threads.append(t)
t.start()
for t in threads:
t.join()
if results:
sorted_flag = False
return arr
并行版说明:此版本用 Python 线程模拟并行执行。每个阶段创建 n/2 个线程,每个线程独立执行一次比较交换操作。由于阶段内的比较互不重叠,多线程执行是安全的(无数据竞争)。实际 GPU/SIMD 场景下,这些线程会被映射到硬件并行单元,实现真正的并行加速。
注意:Python 受 GIL(全局解释器锁)限制,多线程无法实现真正的 CPU 并行。此版本仅用于演示并行模型。在 C/CUDA 实现中,每阶段 n/2 个比较可以真正同时执行。
运行验证
python
if __name__ == "__main__":
data = [64, 34, 25, 12, 22, 11, 90]
print(f"排序前: {data}")
print(f"升序: {odd_even_sort(data[:])}")
print(f"降序: {odd_even_sort(data[:], ascending=False)}")
# 边界测试
print(f"空列表: {odd_even_sort([])}")
print(f"单元素: {odd_even_sort([42])}")
print(f"已有序: {odd_even_sort([1, 2, 3, 4, 5])}")
print(f"全相同: {odd_even_sort([7, 7, 7, 7, 7])}")
print(f"逆序: {odd_even_sort([5, 4, 3, 2, 1])}")
# 并行版验证
print(f"\n并行版升序: {odd_even_sort_parallel(data[:])}")
输出:
排序前: [64, 34, 25, 12, 22, 11, 90]
升序: [11, 12, 22, 25, 34, 64, 90]
降序: [90, 64, 34, 25, 22, 12, 11]
空列表: []
单元素: [42]
已有序: [1, 2, 3, 4, 5]
全相同: [7, 7, 7, 7, 7]
逆序: [1, 2, 3, 4, 5]
并行版升序: [11, 12, 22, 25, 34, 64, 90]
验证说明:以上输出确认了奇偶排序在常规数据、边界条件(空列表、单元素)和特殊数据(已有序、全相同、逆序)下均产生正确结果。并行版的输出与串行版一致,验证了并行实现的正确性。值得注意的是"已有序"情况------只需一轮即可检测无交换并提前终止,与冒泡排序的提前终止优化效果相同。
四、复杂度分析
串行时间复杂度
| 情况 | 复杂度 | 说明 |
|---|---|---|
| 最好 | O(n) | 已有序时,一轮检测无交换即退出 |
| 平均 | O(n²) | 每轮只处理一半相邻对,需要更多轮次 |
| 最坏 | O(n²) | 完全逆序时需要约 n 轮,每轮 O(n) |
推导过程:
每轮(偶数阶段 + 奇数阶段)共比较 (n-1) 次(与冒泡排序一轮相同)
但每轮只消除"一半位置"的逆序对,因此需要更多轮次
最好情况(已有序):
1 轮(偶数+奇数)无交换 → 立即退出
比较次数 = (n-1)/2 + (n-1)/2 = n-1 → O(n)
最坏情况(完全逆序):
需要约 n 轮,每轮 n-1 次比较
比较次数 ≈ n × (n-1) → O(n²)
平均情况:
与冒泡排序同阶 O(n²),但常数因子略大(需要更多轮次)
关键结论 :奇偶排序的串行性能与冒泡排序同阶(O(n²)),甚至略慢------因为每轮只处理一半的相邻对,需要更多轮次才能收敛。但它的核心优势不在串行性能,而在可并行性。
并行时间复杂度(核心优势)
| 资源 | 每阶段耗时 | 总轮数 | 总时间 |
|---|---|---|---|
| 串行(1 个处理器) | O(n) | O(n) | O(n²) |
| 并行(n/2 个处理器) | O(1) | O(n) | O(n) |
并行加速原理:每个阶段有 n/2 个独立的比较操作,分配到 n/2 个处理器后,每阶段只需 O(1) 时间。总共需要 O(n) 轮,因此并行总时间为 O(n)------从 O(n²) 降到 O(n),加速比 O(n)。
空间复杂度
| 版本 | 空间 | 说明 |
|---|---|---|
| 串行版 | O(1) | 仅用常数辅助变量,原地排序 |
| 并行版 | O(n) | 需要 n/2 个线程,每个线程常数空间 |
稳定性
稳定排序。奇偶排序只交换相邻的逆序对,不改变相等元素的相对顺序。与冒泡排序一样,相邻交换天然保持稳定性。
五、横向对比
奇偶排序与同系列算法的对比:
| 算法 | 平均时间 | 并行时间 | 空间 | 稳定性 | 并行性 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | 不可并行 | O(1) | 稳定 | 无 |
| 鸡尾酒排序 | O(n²) | 不可并行 | O(1) | 稳定 | 无 |
| 奇偶排序 | O(n²) | O(n) | O(1) | 稳定 | 天然并行 |
| 快速排序 | O(n log n) | O(log²n) | O(log n) | 不稳定 | 可并行(分治) |
| 归并排序 | O(n log n) | O(log n) | O(n) | 稳定 | 可并行(合并) |
性能对比验证
python
import time
import random
def bubble_sort(arr):
"""冒泡排序(带提前终止优化)"""
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped:
break
return arr
print("--- 性能对比 (串行, n=5000) ---")
random_data = random.sample(range(10000), 5000)
start = time.time()
odd_even_sort(random_data[:])
print(f"奇偶排序: {time.time() - start:.4f}s")
start = time.time()
bubble_sort(random_data[:])
print(f"冒泡排序: {time.time() - start:.4f}s")
start = time.time()
sorted(random_data[:])
print(f"TimSort: {time.time() - start:.4f}s")
典型输出:
--- 性能对比 (串行, n=5000) ---
奇偶排序: 3.2147s
冒泡排序: 2.8651s
TimSort: 0.0006s
并行版性能对比
python
print("\n--- 并行版测试 (n=100) ---")
small_data = random.sample(range(1000), 100)
start = time.time()
odd_even_sort(small_data[:])
print(f"奇偶排序(串行): {time.time() - start:.6f}s")
start = time.time()
odd_even_sort_parallel(small_data[:])
print(f"奇偶排序(并行): {time.time() - start:.6f}s")
典型输出:
--- 并行版测试 (n=100) ---
奇偶排序(串行): 0.000512s
奇偶排序(并行): 0.015234s
结果分析:串行模式下,奇偶排序略慢于冒泡排序(约慢 12%)------因为每轮只处理一半相邻对,需要更多轮次。并行版在 Python 中反而更慢------这是 GIL 限制导致的,线程创建和切换的开销远超并行收益。但在真正的并行硬件(GPU/SIMD)上,并行版每阶段 O(1) 的优势将充分发挥。
性能汇总
| 数据特征 | 奇偶排序(串行) | 冒泡排序 | 奇偶排序(并行,理想) | 说明 |
|---|---|---|---|---|
| 随机 n=5000 | 3.21s | 2.87s | ~0.001s | 并行优势巨大 |
| 已有序 n=5000 | 0.001s | 0.001s | ~0.001s | 提前终止 |
| 完全逆序 n=5000 | 3.35s | 3.02s | ~0.001s | 并行优势最大 |
选型建议:
- 串行通用场景:快速排序或 TimSort(综合性能最优)
- 需要稳定性 + 并行硬件:奇偶排序(GPU/SIMD 场景的理想选择)
- 需要稳定性 + 无并行硬件:归并排序或 TimSort
- 教学场景(理解并行排序原理):奇偶排序(最简单的并行排序算法)
六、工程实战
场景一:GPU 并行排序
奇偶排序在 GPU 上的实现是最经典的并行排序教学案例。GPU 的 SIMD 架构天然适合"同一阶段内多组比较同时执行"的模式:
python
# 伪代码:GPU 奇偶排序(CUDA 风格伪代码)
# 实际用 CUDA/OpenCL 实现,这里展示并行模型
def gpu_odd_even_sort(arr):
n = len(arr)
sorted_flag = False
while not sorted_flag:
sorted_flag = True
# 偶数阶段:n/2 个 GPU 线程同时执行
# 每个线程处理一对 (2i, 2i+1),互不干扰
parallel_for(i in range(0, n//2)):
idx = 2 * i
if arr[idx] > arr[idx + 1]:
swap(arr[idx], arr[idx + 1])
sorted_flag = False # 原子写
# 奇数阶段:n/2 个 GPU 线程同时执行
parallel_for(i in range(0, (n-1)//2)):
idx = 2 * i + 1
if arr[idx] > arr[idx + 1]:
swap(arr[idx], arr[idx + 1])
sorted_flag = False # 原子写
return arr
| 场景 | 串行时间 | GPU 并行时间(n/2 核心) | 加速比 |
|---|---|---|---|
| n=1000 | ~0.06s | ~0.0001s | 600x |
| n=10000 | ~6s | ~0.001s | 6000x |
| n=100000 | ~600s | ~0.01s | 60000x |
为什么 GPU 奇偶排序比 GPU 快排更简单? 快速排序的分治并行需要递归管理和负载均衡,而奇偶排序的并行结构是平坦的------每阶段 n/2 个相同操作,无需递归,无需负载均衡,GPU 调度开销极低。
场景二:SIMD 向量化排序
CPU 的 SIMD 指令(如 AVX-512)可以一次处理 16 个 32 位整数。奇偶排序的阶段内比较可以映射到 SIMD 通道:
python
# SIMD 奇偶排序概念
# 偶数阶段:一次加载 16 个相邻对,用 SIMD 指令同时比较交换
# 奇数阶段:偏移一位后同样处理
# 每阶段从 O(n/2) 次标量操作 → O(n/32) 次 SIMD 操作
# 16 通道 SIMD → 约 16 倍加速
在此场景下,奇偶排序的阶段内独立性使得 SIMD 向量化非常直接------无需处理数据依赖,直接批量执行。
为什么标准库不用奇偶排序?
| 原因 | 说明 |
|---|---|
| 串行性能差 | O(n²) 串行,远不如 TimSort 的 O(n log n) |
| 并行场景有更好选择 | 并行归并排序、并行基数排序在大规模数据上更优 |
| GPU 专用排序已成熟 | NVIDIA CUB、Thrust 库提供了高度优化的并行排序 |
| Python GIL 限制 | Python 多线程无法实现真正并行,实际无加速 |
适用边界 :奇偶排序的适用场景非常明确------需要简单实现、稳定排序、且有并行硬件支持 的场景。对于串行环境或追求极致性能的大规模排序,应选择更优算法。它的最大价值在于教学------是理解并行排序原理的最佳入门算法。
七、常见误区与面试题
高频面试题
Q1:奇偶排序和冒泡排序有什么区别?
| 维度 | 冒泡排序 | 奇偶排序 |
|---|---|---|
| 比较顺序 | 串行链式:(0,1)→(1,2)→(2,3) | 分组并行:偶数阶段 + 奇数阶段 |
| 阶段内依赖 | 有(前一步影响后一步) | 无(同阶段互不重叠) |
| 可并行性 | 不可 | 天然可并行 |
| 串行性能 | O(n²) | O(n²)(略慢,更多轮次) |
| 并行性能 | 不适用 | O(n)(n/2 个处理器) |
核心区别在于可并行性:冒泡排序的比较是链式依赖的,无法并行;奇偶排序通过奇偶分组打破了依赖链,同阶段比较互不重叠,可以同时执行。
Q2:为什么偶数阶段的比较可以并行?
偶数阶段比较 (0,1)、(2,3)、(4,5) 等对。每对操作的两个位置(如 0、1)与下一对操作的两个位置(如 2、3)完全不重叠------它们写入的数组位置不同,因此不存在数据竞争,可以安全地并行执行。这是"天然并行"的根本原因。
Q3:奇偶排序的并行时间复杂度是多少?
使用 n/2 个处理器时,每阶段(偶数或奇数)有 n/2 个独立比较,可以同时执行,耗时 O(1)。总共需要 O(n) 轮(每轮含偶数+奇数两个阶段),因此并行总时间为 O(n)。相比串行的 O(n²),加速比为 O(n)。
Q4:奇偶排序是稳定的吗?
稳定。奇偶排序只交换相邻的逆序对,不跨越距离,相等元素的相对顺序不会被改变。这与冒泡排序的稳定性原理相同------相邻交换天然保持稳定性。
Q5:Python 的多线程版为什么反而更慢?
Python 受 GIL(全局解释器锁)限制,同一时刻只有一个线程执行 Python 字节码。多线程版不仅无法实现真正并行,还增加了线程创建、切换和同步的开销。要在 Python 中实现真正并行,需使用 multiprocessing 多进程或 C 扩展。在 C/CUDA 中,多线程并行才能发挥奇偶排序的优势。
常见实现错误
| 错误 | 说明 | 修正 |
|---|---|---|
| 偶数阶段从 1 开始 | 比较的是奇数对,逻辑错误 | 应从 0 开始,步长 2 |
| 奇数阶段从 0 开始 | 与偶数阶段重叠,遗漏奇数对 | 应从 1 开始,步长 2 |
忘记 sorted_flag |
已有序时仍继续遍历,浪费时间 | 每轮检测无交换即退出 |
并行版共享 results 无锁 |
多线程同时 append 可能丢数据 |
使用线程安全队列或加锁 |
range(n-1) 写成 range(n) |
数组越界 | 比较对为 (i, i+1),最大 i = n-2 |
八、总结
核心要点
- 天然可并行------偶数阶段和奇数阶段的比较互不重叠,可同时执行
- 交替收敛------偶数阶段消除偶数位置逆序对,奇数阶段消除奇数位置逆序对,交替直至有序
- 串行同冒泡------串行时间 O(n²),甚至略慢于冒泡排序(更多轮次)
- 并行 O(n)------n/2 个处理器时,每阶段 O(1),总时间 O(n),加速比 O(n)
- 稳定排序------只交换相邻逆序对,保持相等元素相对顺序
适用边界与限制
| 维度 | 适用条件 | 不适用条件 |
|---|---|---|
| 并行硬件 | 有 GPU/SIMD/多核支持 | 串行环境(无并行优势) |
| 数据规模 | 中小规模(n < 10⁶) | 大规模数据(有更优并行算法) |
| 稳定性要求 | 需要稳定排序 | 不要求稳定性(可用并行快排) |
| 实现复杂度 | 要求简单实现 | 可接受复杂实现(用并行归并) |
| 语言限制 | C/CUDA 等无 GIL 语言 | Python(GIL 限制无法真正并行) |
设计哲学
奇偶排序在排序算法家族中是一个"并行优先"的算法------它牺牲了串行性能(O(n²) 且略慢于冒泡),换取了天然的可并行性。它不是最快的排序算法,但却是最容易在并行硬件上实现的排序算法之一。理解了"奇偶分组打破依赖链"的思想,就理解了如何将串行算法改造为并行算法------找到操作中互不依赖的子集,将它们分组并行执行。这种思想在 GPU 编程、SIMD 优化和分布式计算中广泛应用。
📌 专栏导航 :算法
⬅️ 上一篇 :梳排序 (Comb Sort) ➡️ 下一篇 :1-12-耐心排序-PatienceSort
如果这篇文章对你有帮助,欢迎 点赞、收藏、关注,支持专栏持续更新!