AI刷题中的排序 | 豆包MarsCode AI刷题

69 阅读6分钟

排序问题详解

在用掘金AI刷题的时候,我们或多或少都碰到了排序的问题,这也是在找工作的笔试和面试中经常碰到的知识点。排序算法的目标是将一个无序的序列(如数组或列表)按升序或降序排列。Python 提供了内置的排序方法,但理解排序算法的原理和实现可以帮助你在笔试、面试或其他算法应用中表现更好。

一、排序算法分类

排序算法可以根据其不同的实现原理和性能特点分为几类:

  1. 插入排序类

    • 插入排序 (Insertion Sort)
    • 希尔排序 (Shell Sort)
  2. 交换排序类

    • 冒泡排序 (Bubble Sort)
    • 快速排序 (Quick Sort)
  3. 选择排序类

    • 选择排序 (Selection Sort)
  4. 归并排序类

    • 归并排序 (Merge Sort)
  5. 计数排序类

    • 计数排序 (Counting Sort)
    • 基数排序 (Radix Sort)
    • 桶排序 (Bucket Sort)

接下来详细结合python代码讲解这几种排序。

二、详细讲解

1. 插入排序 (Insertion Sort)

原理:将当前元素与前面已排序部分的元素逐个比较,找到合适位置插入。

时间复杂度:最坏和平均情况 O(n²),最好 O(n)(当输入数组已经排序时)

代码实现

def insertion_sort(arr):
    for i in range(1, len(arr)):
        key = arr[i]  # 当前要插入的元素
        j = i - 1
        while j >= 0 and arr[j] > key:  # 向左扫描已排序部分
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key
    return arr
  • 优点

    • 实现简单。
    • 对小规模数据和近乎有序的数据非常高效。
  • 缺点

    • 对大规模数据,性能较差。

2. 冒泡排序 (Bubble Sort)

原理:通过多次比较相邻元素,并交换它们的位置,直到所有元素按顺序排列。

时间复杂度:最坏和平均情况 O(n²),最好 O(n)(当输入数组已经排序时)

代码实现

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        swapped = False  # 检测是否发生交换
        for j in range(0, n-i-1):
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]  # 交换
                swapped = True
        if not swapped:
            break  # 如果没有发生交换,则数组已经排序
    return arr
  • 优点

    • 简单直观。
    • 对于小数据量和近乎有序的数据,性能较好。
  • 缺点

    • 对大数据集,效率低,特别是在没有提前优化的情况下。

3. 选择排序 (Selection Sort)

原理:每次从未排序部分选择最小(或最大)元素,并将其放到已排序部分的末尾。

时间复杂度:O(n²)

代码实现

def selection_sort(arr):
    n = len(arr)
    for i in range(n):
        min_idx = i
        for j in range(i+1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j
        arr[i], arr[min_idx] = arr[min_idx], arr[i]
    return arr
  • 优点

    • 实现简单。
    • 对内存要求低。
  • 缺点

    • 对于大数据,效率差,尤其是比其他更高效的排序算法。

4. 快速排序 (Quick Sort)

原理:选择一个基准元素,将数组分为两部分,一部分比基准元素小,一部分比基准元素大,然后递归排序两个部分。

时间复杂度

  • 最坏情况 O(n²)(当选取的基准元素不合适时)
  • 平均情况 O(n log n)

代码实现

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]  # 选择中间的元素作为基准
    left = [x for x in arr if x < pivot]  # 小于基准的元素
    middle = [x for x in arr if x == pivot]  # 等于基准的元素
    right = [x for x in arr if x > pivot]  # 大于基准的元素
    return quick_sort(left) + middle + quick_sort(right)
  • 优点

    • 平均情况下非常高效,适合大数据集。
    • 原地排序,不需要额外空间。
  • 缺点

    • 最坏情况下性能较差,需要优化基准选择。
    • 不稳定。

5. 归并排序 (Merge Sort)

原理:将数组递归分割为两个子数组,直到每个子数组只有一个元素,然后合并这些子数组,形成排序好的数组。

时间复杂度:O(n log n),空间复杂度 O(n)

代码实现

def merge_sort(arr):
    if len(arr) > 1:
        mid = len(arr) // 2  # 找到中点
        left_half = arr[:mid]
        right_half = arr[mid:]

        merge_sort(left_half)  # 递归排序左半部分
        merge_sort(right_half)  # 递归排序右半部分

        i = j = k = 0

        # 合并两个已排序的部分
        while i < len(left_half) and j < len(right_half):
            if left_half[i] < right_half[j]:
                arr[k] = left_half[i]
                i += 1
            else:
                arr[k] = right_half[j]
                j += 1
            k += 1

        # 如果左半部分还有剩余
        while i < len(left_half):
            arr[k] = left_half[i]
            i += 1
            k += 1

        # 如果右半部分还有剩余
        while j < len(right_half):
            arr[k] = right_half[j]
            j += 1
            k += 1

    return arr
  • 优点

    • 平均情况下非常高效,适合大数据集。
    • 原地排序,不需要额外空间。
  • 缺点

    • 最坏情况下性能较差,需要优化基准选择。
    • 不稳定。

6. 计数排序 (Counting Sort)

原理:假设数组中的元素是整数(或者可以映射为整数),统计每个元素的出现次数,然后根据次数的累计进行排序。

时间复杂度:O(n + k),其中 n 是数组长度,k 是元素的范围。

代码实现

def counting_sort(arr):
    if len(arr) == 0:
        return arr

    # 找到最大和最小值
    max_val = max(arr)
    min_val = min(arr)

    # 创建计数数组
    count = [0] * (max_val - min_val + 1)

    # 统计每个元素的出现次数
    for num in arr:
        count[num - min_val] += 1

    # 重构排序后的数组
    sorted_arr = []
    for i, count_val in enumerate(count):
        sorted_arr.extend([i + min_val] * count_val)

    return sorted_arr
  • 优点

    • 对数据范围较小的情况非常高效。
    • 可以排序负数和浮点数(通过映射转换)。
  • 缺点

    • 适用于数据范围较小的情况,对于大范围的数据,效率低,且占用空间大。

三、总结

  • 简单排序算法(如冒泡、插入、选择排序)通常适用于小数据集或当数据基本有序时。它们的时间复杂度通常较高,但实现简单,且对于学习排序算法很有帮助。
  • 高效排序算法(如快速排序、归并排序)适合大规模数据,通常在现实应用中使用得较多。快速排序在大多数情况下表现很好,但最坏情况下可能退化为 O(n²)。
  • 特殊排序算法(如计数排序、基数排序、桶排序)在某些特定数据分布或约束下非常高效,但对数据类型和范围有要求。

选择排序算法时,应根据数据的规模、分布特点以及需要的稳定性来决定合适的算法。