当我读到排序算法的解释时,我想,“光看我记不住”。
我们实现了 7 种代表性算法,并通过实际运行它们来比较性能。
我觉得用Python写会很容易,所以我用Python写了
目录
源代码
冒泡排序
- 对于所有元素,与相邻元素进行比较,如果顺序颠倒则替换。通过对元素数-1次重复此排序
- 最简单的排序
| 平均计算时间 | 最坏情况计算时间 | 内存使用情况 | 稳定 |
|---|---|---|---|
| $n^{2}$ | $n^{2}$ | $1$ | 可 |
def bubble_sort(lst):
n = len(lst)
for i in range(n):
for j in range(n-1):
if lst[j] >= lst[j+1]:
lst[j], lst[j+1] = lst[j+1], lst[j]
return lst
选择排序
- 找到具有最低值的元素并将其与第一个元素交换(排序到第一个元素)同样在
- 之后,找到未排序部分的最小元素,并将其替换为未排序部分的第一个元素
- 当所有元素都排序后结束处理
| 平均计算时间 | 最坏情况计算时间 | 内存使用情况 | 稳定 |
|---|---|---|---|
| $n^{2}$ | $n^{2}$ | $1$ | 没有任何 |
def selection_sort(lst):
n = len(lst)
for i in range(0, n-1):
min = i
for j in range(i+1, n):
if lst[j] < lst[min]:
min = j
lst[i], lst[min] = lst[min], lst[i]
return lst
插入排序
- 比较第 0 和第 1 个元素,如果顺序颠倒,则交换它们
- 如果第二个元素小于第一个元素,则以正确的顺序“插入”它(对于数组,将前一个元素向后移动一个)
- 对第三个及以后的元素继续相同的过程,当所有元素都排序后结束该过程
| 平均计算时间 | 最坏情况计算时间 | 内存使用情况 | 稳定 |
|---|---|---|---|
| $n^{2}$ | $n^{2}$ | $1$ | 可 |
def insertion_sort(lst):
for i in range(1, len(lst)):
tmp = lst[i]
j = i - 1
while j >= 0 and lst[j] > tmp:
lst[j+1] = lst[j]
j -= 1
lst[j+1] = tmp
return lst
壳排序
- 确定合适的区间 h
- 对以间隔 h 获取的数据列应用插入排序
- 缩小区间 h 并重复提取数据列和应用插入排序的过程,直到 h 变为 1
| 平均计算时间 | 最坏情况计算时间 | 内存使用情况 | 稳定 |
|---|---|---|---|
| $nlog n$ | $geqqn^{1.5}$ | $1$ | 没有任何 |
def shell_sort(lst):
n = len(lst)
h = 1
while h < n / 9:
h = h * 3 + 1
while h > 0:
for i in range(h, n):
j = i
while j >= h and lst[j-h] > lst[j]:
lst[j], lst[j-h] = lst[j-h], lst[j]
j -= h
h = h // 3
return lst
快速排序
- 选择名为 Pivot 的边界值
- 在数组开头收集枢轴下方的元素,并将仅包含枢轴下方元素的部分与其余部分分开
- 为分割的部分再次选择并分割枢轴。重复此过程,直到对分割的部分进行排序
- 最后合并分割区间
| 平均计算时间 | 最坏情况计算时间 | 内存使用情况 | 稳定 |
|---|---|---|---|
| $nlog n$ | $n^2$ | $nlog n$ | 没有任何 |
def quick_sort(lst):
if len(lst) <= 1:
return lst
pivot = lst[0]
left = []
center = []
right = []
for v in lst:
if v < pivot:
left.append(v)
elif v > pivot:
right.append(v)
else:
center.append(v)
left = quick_sort(left)
right = quick_sort(right)
return left + center + right
堆排序
- 从未对齐的数据字符串中删除元素并将它们按顺序添加到堆中。重复直到添加所有元素
- 取根(最大值或最小值)并将其添加到排序列表中。重复直到检索到所有元素
| 平均计算时间 | 最坏情况计算时间 | 内存使用情况 | 稳定 |
|---|---|---|---|
| $nlog n$ | $nlog n$ | $1$ | 没有任何 |
def heap_sort(lst):
n = len(lst)
for i in range(n//2-1, -1, -1):
heapify(lst, i, n)
for i in range(n-1, 0, -1):
lst[0], lst[i] = lst[i], lst[0]
heapify(lst, 0, i)
return lst
def heapify(lst, i, heap_size):
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < heap_size and lst[left] > lst[largest]:
largest = left
if right < heap_size and lst[right] > lst[largest]:
largest = right
if largest != i:
lst[largest], lst[i] = lst[i], lst[largest]
heapify(lst, largest, heap_size)
归并排序
- 拆分(通常是两半)数据列
- 如果拆分数据列有1条数据,则返回,如果有2条或更多,则进一步拆分数据列。
- 对上述递归返回的数据列做简单的元素比较排序
- 合并两个已排序的数据列(如果是一个,则为自身)
| 平均计算时间 | 最坏情况计算时间 | 内存使用情况 | 稳定 |
|---|---|---|---|
| $nlog n$ | $nlog n$ | $n$ | 可 |
def merge_sort(lst):
if len(lst) <= 1:
return lst
mid = len(lst) // 2
left = lst[:mid]
right = lst[mid:]
left = merge_sort(left)
right = merge_sort(right)
return merge(left, right)
def merge(left, right):
merged = []
left_i, right_i = 0, 0
while left_i < len(left) and right_i < len(right):
if left[left_i] <= right[right_i]:
merged.append(left[left_i])
left_i += 1
else:
merged.append(right[right_i])
right_i += 1
if left_i < len(left):
merged.extend(left[left_i:])
if right_i < len(right):
merged.extend(right[right_i:])
return merged
我比较了执行时间
我尝试使用每种算法从 -100,000 到 100,000 范围内随机选择 100,000 个数字
我对三个数据集进行了排序,并检查了每个会话所需的时间和平均所需的时间。
| 算法 | 所需时间(秒) | 所需时间(秒) | 所需时间(秒) | 平均所需时间(秒) |
|---|---|---|---|---|
| 冒泡排序 | 706.051 | 722.234 | 727.029 | 718.438 |
| 选择排序 | 200.039 | 204.399 | 200.981 | 201.806 |
| 插入排序 | 203.928 | 202.049 | 203.315 | 203.097 |
| 壳排序 | 0.516 | 0.517 | 0.557 | 0.530 |
| 快速排序 | 0.146 | 0.132 | 0.139 | 0.139 |
| 堆排序 | 0.481 | 0.433 | 0.473 | 0.462 |
| 合并排序 | 0.287 | 0.271 | 0.279 | 0.279 |
同样,基本排序冒泡/选择/插入和加速排序外壳/快速/堆/合并之间存在显着的性能差异。
在加速排序中,快速排序是最快的
(不过,由于我这次使用的是相对稳定的数据集,所以在最坏的情况下查看可能会很慢。)
亚军,归并排序也相当快
顺便说一下,每个要排序的对象数量所需的时间如下表所示。
| 算法 | 1,000 | 10,000 | 100,000 |
|---|---|---|---|
| 冒泡排序 | 0.073 | 6.984 | 718.438 |
| 选择排序 | 0.019 | 1.976 | 201.806 |
| 插入排序 | 0.020 | 2.046 | 203.097 |
| 壳排序 | 0.002 | 0.030 | 0.530 |
| 快速排序 | 0.001 | 0.012 | 0.139 |
| 堆排序 | 0.002 | 0.034 | 0.462 |
| 合并排序 | 0.002 | 0.023 | 0.279 |
随着数量的增加,计算复杂度为 $O(n^{2})$ 的算法会显着增加处理秒数,
复杂度为 $O(nlogn)$ 的算法在几秒钟内保持持续增长
奖金
从上面的实现派生出来,python标准提供的排序函数是
我很好奇它是如何实现的,所以我查了一下。
显然,python的排序功能是时间排序它似乎使用了一种称为
性能如下
| 平均计算时间 | 最坏情况计算时间 | 内存使用情况 | 稳定 |
|---|---|---|---|
| $nlog n$ | $nlog n$ | $n$ | 可 |
当我实际运行它时,执行时间如下
| 算法 | 1,000 | 10,000 | 100,000 |
|---|---|---|---|
| timsort(python标准) | 0.000 | 0.001 | 0.013 |
相当快! (为了能够以这次实施的快速排序速度的 1/10 进行排序......)
感觉是标准实现
即使从理论性能来看,最坏情况的计算时间也比快速排序(quicksort: $O(n^{2})$, timsort: $O(nlogn)$)快很多,所以要考虑内存使用情况。如果你不不做,我觉得这是一个很不错的算法
实施地点大概是
如果我足够努力,也许能看懂,但由于是用C语言写的(经验不足),看不懂,所以这次放弃了……
(总有一天,我也想了解 Timsort 的实现......)
因此,我将在本文中不再赘述。
如果您想了解更多有关 TimSort 的信息,请阅读以下页面!
参考页
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308632240.html