数据结构(六):排序(二)
2023-11-07 15:58:04
前言
在数据结构(六):排序(一)中介绍了直接插入排序、希尔排序、选择排序、堆排序和冒泡排序,本文继续介绍剩下的快速排序、归并排序和计数排序。
一、快速排序
快速排序是一种分治排序算法,它通过递归的方式将待排序的数组分成较小的部分,然后分别对这些较小的部分进行排序,最后将这些排序后的部分合并成一个排序后的数组。快速排序的平均时间复杂度为O(nlogn),但最坏情况下的时间复杂度为O(n^2)。
1. 快速排序的步骤:
- 选择一个基准元素。
- 将待排序的数组分成两部分,一部分包含比基准元素小的元素,另一部分包含比基准元素大的元素。
- 对这两部分分别进行快速排序。
- 将这两部分排序后的结果合并成一个排序后的数组。
2. 快速排序的代码实现:
def quick_sort(array):
"""快速排序算法"""
if len(array) <= 1:
return array
# 选择基准元素
pivot = array[0]
# 将数组分成两部分
left = []
right = []
for i in range(1, len(array)):
if array[i] < pivot:
left.append(array[i])
else:
right.append(array[i])
# 对这两部分分别进行快速排序
left = quick_sort(left)
right = quick_sort(right)
# 将这两部分排序后的结果合并成一个排序后的数组
return left + [pivot] + right
3. 快速排序的性能分析:
快速排序的平均时间复杂度为O(nlogn),但最坏情况下的时间复杂度为O(n^2)。这是因为,在最坏的情况下,快速排序会退化为冒泡排序。
二、归并排序
归并排序也是一种分治排序算法,它通过递归的方式将待排序的数组分成较小的部分,然后分别对这些较小的部分进行排序,最后将这些排序后的部分合并成一个排序后的数组。归并排序的平均时间复杂度和最坏情况下的时间复杂度均为O(nlogn)。
1. 归并排序的步骤:
- 将待排序的数组分成两部分。
- 对这两部分分别进行归并排序。
- 将这两部分排序后的结果合并成一个排序后的数组。
2. 归并排序的代码实现:
def merge_sort(array):
"""归并排序算法"""
if len(array) <= 1:
return array
# 将数组分成两部分
mid = len(array) // 2
left = array[:mid]
right = array[mid:]
# 对这两部分分别进行归并排序
left = merge_sort(left)
right = merge_sort(right)
# 将这两部分排序后的结果合并成一个排序后的数组
return merge(left, right)
def merge(left, right):
"""合并两个有序数组"""
result = []
i = 0
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
3. 归并排序的性能分析:
归并排序的平均时间复杂度和最坏情况下的时间复杂度均为O(nlogn)。这是因为,归并排序总是将数组分成两部分,然后分别对这两部分进行排序,最后将这两部分排序后的结果合并成一个排序后的数组。
三、计数排序
计数排序是一种非比较排序算法,它通过计算每个元素出现的次数来对数组进行排序。计数排序的时间复杂度为O(n+k),其中n是数组的长度,k是数组中元素的最大值。
1. 计数排序的步骤:
- 找出数组中元素的最大值。
- 创建一个长度为k+1的数组,其中k是数组中元素的最大值。
- 遍历数组,并计算每个元素出现的次数,并将出现的次数存储在计数数组中。
- 将计数数组中的元素累加,得到每个元素的排序后的位置。
- 将数组中的元素按照排序后的位置重新排列。
2. 计数排序的代码实现:
def counting_sort(array):
"""计数排序算法"""
# 找出数组中元素的最大值
max_value = max(array)
# 创建一个长度为k+1的数组,其中k是数组中元素的最大值
counts = [0] * (max_value + 1)
# 遍历数组,并计算每个元素出现的次数,并将出现的次数存储在计数数组中
for element in array:
counts[element] += 1
# 将计数数组中的元素累加,得到每个元素的排序后的位置
for i in range(1, len(counts)):
counts[i] += counts[i-1]
# 将数组中的元素按照排序后的位置重新排列
result = [0] * len(array)
for element in array:
result[counts[element]-1] = element
counts[element] -= 1
return result
3. 计数排序的性能分析:
计数排序的时间复杂度为O(n+k),其中n是数组的长度,k是数组中元素的最大值。这是因为,计数排序只需要遍历数组一次,并计算每个元素出现的次数,然后将计数数组中的元素累加,得到每个元素的排序后的位置,最后将数组中的元素按照排序后的位置重新排列。
四、比较
快速排序、归并排序和计数排序都是常见的排序算法,它们各有优缺点。
- 快速排序的平均时间复杂度为O(nlogn),但最坏情况下的时间复杂度为O(n^2)。归并排序的平均时间复杂度和最坏情况下的时间复杂度均为O(nlogn)。计数排序的时间复杂度为O(n+k),其中n是数组的长度,k是数组中元素的最大值。
- 快速排序是一种原地排序算法,它不需要额外的空间。归并排序和计数排序都是非原地排序算法,它们需要额外的空间。
- 快速排序对数据分布敏感,当数据分布不均匀时,快速排序的性能会下降。归并排序和计数排序对数据分布不敏感,它们总是能够保持O(nlogn)的时间复杂度。
五、总结
快速排序、归并排序和计数排序都是常用的排序算法,它们各有优缺点。在实际应用中,应该根据具体情况选择合适的排序算法。