返回

数据结构(六):排序(二)

后端

前言

在数据结构(六):排序(一)中介绍了直接插入排序、希尔排序、选择排序、堆排序和冒泡排序,本文继续介绍剩下的快速排序、归并排序和计数排序。

一、快速排序

快速排序是一种分治排序算法,它通过递归的方式将待排序的数组分成较小的部分,然后分别对这些较小的部分进行排序,最后将这些排序后的部分合并成一个排序后的数组。快速排序的平均时间复杂度为O(nlogn),但最坏情况下的时间复杂度为O(n^2)。

1. 快速排序的步骤:

  1. 选择一个基准元素。
  2. 将待排序的数组分成两部分,一部分包含比基准元素小的元素,另一部分包含比基准元素大的元素。
  3. 对这两部分分别进行快速排序。
  4. 将这两部分排序后的结果合并成一个排序后的数组。

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. 归并排序的步骤:

  1. 将待排序的数组分成两部分。
  2. 对这两部分分别进行归并排序。
  3. 将这两部分排序后的结果合并成一个排序后的数组。

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. 计数排序的步骤:

  1. 找出数组中元素的最大值。
  2. 创建一个长度为k+1的数组,其中k是数组中元素的最大值。
  3. 遍历数组,并计算每个元素出现的次数,并将出现的次数存储在计数数组中。
  4. 将计数数组中的元素累加,得到每个元素的排序后的位置。
  5. 将数组中的元素按照排序后的位置重新排列。

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)的时间复杂度。

五、总结

快速排序、归并排序和计数排序都是常用的排序算法,它们各有优缺点。在实际应用中,应该根据具体情况选择合适的排序算法。