排序算法介绍

排序就是把一组数按从小到大(或从大到小)排好。比较排序靠两两比大小,平均最快大约是 O(n log n)。计数排序这类不比大小,在整数范围不大时可以做到 O(n)。

下面都按升序,输入用 [5, 2, 4, 6, 1, 3],代码不改原列表,返回新列表。

算法 平均 最坏 额外空间 稳定
冒泡 O(n²) O(n²) O(1) 是
选择 O(n²) O(n²) O(1) 否
插入 O(n²) O(n²) O(1) 是
归并 O(n log n) O(n log n) O(n) 是
快速 O(n log n) O(n²) O(log n) 栈 否
堆 O(n log n) O(n log n) O(1) 否
计数 O(n + k) O(n + k) O(n + k) 是

k 是数值范围。稳定的意思是:相等的两个数,排序后仍保持原来的先后顺序。

冒泡排序

相邻两个数如果左边更大,就交换。每一轮都会把当前未排序部分里最大的数「冒」到末尾。某一轮没有发生交换,说明已经有序,可以提前结束。

[5, 2, 4, 1] 第一轮:

5, 2, 4, 1

2, 5, 4, 1

2, 4, 5, 1

2, 4, 1, 5

5 已经到位。再继续,直到剩下的部分也有序。

def bubble_sort(a):

a = a:

n = len(a)

for i in range(n):

swapped = False

for j in range(n - 1 - i):

if aj > aj + 1:

aj, aj + 1 = aj + 1, aj

swapped = True

if not swapped:

break

return a

选择排序

每一轮在未排序区间里找出最小的数,放到这个区间的最前面。

[5, 2, 4, 6, 1, 3]:

第 1 轮最小值 1,和 5 交换 → 1, 2, 4, 6, 5, 3

第 2 轮从下标 1 起,2 已是最小 → 1, 2, 4, 6, 5, 3

第 3 轮最小值 3,和 4 交换 → 1, 2, 3, 6, 5, 4

第 4 轮最小值 4,和 6 交换 → 1, 2, 3, 4, 5, 6

def selection_sort(a):

a = a:

for i in range(len(a)):

min_i = i

for j in range(i + 1, len(a)):

if aj < amin_i:

min_i = j

ai, amin_i = amin_i, ai

return a

插入排序

把序列看成「左手已排好、右手待插入」。每次取出右手第一个数,在左手里从右往左找位置插进去。数据几乎有序时很快,接近 O(n)。

[5, 2, 4, 6, 1, 3]:

5 | 2, 4, 6, 1, 3

2, 5 | 4, 6, 1, 3

2, 4, 5 | 6, 1, 3

2, 4, 5, 6 | 1, 3

1, 2, 4, 5, 6 | 3

1, 2, 3, 4, 5, 6

def insertion_sort(a):

a = a:

for i in range(1, len(a)):

key = ai

j = i - 1

while j >= 0 and aj > key:

aj + 1 = aj

j -= 1

aj + 1 = key

return a

归并排序

先对半拆开,各自排好,再把两个有序序列合并。合并时每次取两边当前更小的那个。拆分是 log n 层,每层合并一共看 n 个数,所以是 O(n log n)。相等时先取左边,因此是稳定的。

[5, 2, 4, 1] 的合并过程:

5, 2\] 和 \[4, 1

5\] 与 \[2\] → \[2, 5

4\] 与 \[1\] → \[1, 4

2, 5\] 与 \[1, 4\] → \[1, 2, 4, 5

def merge_sort(a):

if len(a) <= 1:

return a:

mid = len(a) // 2

left = merge_sort(a:mid)

right = merge_sort(amid:)

out = \[\]

i = j = 0

while i < len(left) and j < len(right):

if lefti <= rightj:

out.append(lefti)

i += 1

else:

out.append(rightj)

j += 1

out.extend(lefti:)

out.extend(rightj:)

return out

快速排序

选一个基准(这里用区间最后一个)。比基准小或相等的放到左边,比基准大的放到右边,基准落在最终位置,再递归排左右两段。平均 O(n log n)。如果每次基准都是当前区间的最大或最小,会退化成 O(n²)。

[5, 2, 4, 6, 1, 3],基准是 3:

扫描后:2, 1, 3, 6, 5, 4

↑ 3 已在最终位置

左边 2, 1 ,右边 6, 5, 4

def quick_sort(a):

a = a:

def sort(lo, hi):

if lo >= hi:

return

pivot = ahi

i = lo

for j in range(lo, hi):

if aj <= pivot:

ai, aj = aj, ai

i += 1

ai, ahi = ahi, ai

sort(lo, i - 1)

sort(i + 1, hi)

if a:

sort(0, len(a) - 1)

return a

堆排序

先把数组建成大顶堆:每个父节点都大于等于它的子节点,堆顶是最大值。然后反复把堆顶换到末尾,堆的大小减一,再把新的堆顶沉下去。全程 O(n log n),额外空间 O(1)。

下标 i 的左孩子是 2*i+1,右孩子是 2*i+2。

def heap_sort(a):

a = a:

def sift_down(start, end):

root = start

while True:

child = 2 * root + 1

if child >= end:

break

if child + 1 < end and achild < achild + 1:

child += 1

if aroot < achild:

aroot, achild = achild, aroot

root = child

else:

break

n = len(a)

for i in range(n // 2 - 1, -1, -1):

sift_down(i, n)

for end in range(n - 1, 0, -1):

a0, aend = aend, a0

sift_down(0, end)

return a

计数排序

适合整数,而且最大值和最小值差距不大。先统计每个值出现了几次,再按值从小到大写回。范围是 k 时,时间是 O(n + k)。下面这份对负数也适用。

[-1, 2, -1, 0] 的计数(偏移后下标对应 -1、0、1、2):

次数:2, 1, 0, 1

写回:-1, -1, 0, 2

def counting_sort(a):

if not a:

return \[\]

lo, hi = min(a), max(a)

counts = 0 * (hi - lo + 1)

for x in a:

countsx - lo += 1

out = \[\]

for value, count in enumerate(counts):

out.extend(value + lo * count)

return out

怎么选

  • 数据量小,或基本有序:插入排序就够用。
  • 要稳定、又要 O(n log n):用归并。
  • 平均要快、可以接受最坏 O(n²):用快速排序;基准选得随机一些,最坏情况更少见。
  • 要原地、又要稳定的最坏时间:用堆排序。
  • 整数且范围不大:用计数排序。

Python 自带的 sorted() 和 list.sort() 用的是 Timsort:归并和插入的组合,稳定,最坏 O(n log n),对接近有序的数据更快。日常直接用它。上面这些实现适合看清每一步在做什么。

可以放在一个文件里直接跑:

data = 5, 2, 4, 6, 1, 3

print(bubble_sort(data))

print(selection_sort(data))

print(insertion_sort(data))

print(merge_sort(data))

print(quick_sort(data))

print(heap_sort(data))

print(counting_sort(data))

print(sorted(data))

全部得到 1, 2, 3, 4, 5, 6

相关推荐
笔墨登场说说2 小时前
flink bin/start-cluster.sh 帮我做成开机启动
开发语言·python
MoRanzhi12033 小时前
第 3 篇 · 数据清洗与特征工程:从问题判定到特征构建
python·pandas·数据清洗·特征工程·缺失值处理·异常值检测·特征编码
动词ing3 小时前
【题目练习】算法 组合总和||
算法
longlongzihan3 小时前
链表找环:Floyd 判圈算法(LeetCode 141 & 142 )
c++·算法·leetcode·链表
刘科领3 小时前
使用ollama & openweb-ui 本地搭建自己的AI
人工智能·python·ui·ai
专业程序开发源3 小时前
flask家电故障预测系统92491-计算机课程设计/毕业设计
vscode·python·sql·算法·flask·课程设计
夜雪一千3 小时前
Python 生成模拟地址数据:Faker之外的备选库
网络·windows·python
半亩码田4 小时前
C#转Python第4.8篇:正则表达式:Python re 模块 vs C# System.Text.RegularExpressions
python·正则表达式·c#
夜雪一千4 小时前
如何取消PyCharm自动识别test开头文件并进入测试模式
ide·python·pycharm