返回

细说排序算法之冒泡、选择、插入、快速排序

前端

冒泡排序

冒泡排序是一种简单的排序算法,它反复地比较相邻的元素,如果前一个元素大于后一个元素,则交换它们的位置。这个过程一直持续到没有任何元素需要交换为止。

算法原理

  1. 比较相邻的两个元素。
  2. 如果前一个元素大于后一个元素,则交换它们的位置。
  3. 重复步骤1和步骤2,直到没有任何元素需要交换为止。

时间复杂度

冒泡排序的时间复杂度为O(n^2),这意味着当输入规模增加时,排序所花费的时间将平方增长。

空间复杂度

冒泡排序的空间复杂度为O(1),这意味着它不需要额外的空间来完成排序。

稳定性

冒泡排序是一种稳定的排序算法,这意味着如果输入中具有相同值的元素,则它们在排序后的顺序与输入中相同。

比较次数和交换次数

冒泡排序的比较次数和交换次数都为O(n^2)。

代码示例

def bubble_sort(arr):
  """
  冒泡排序算法

  参数:
    arr:要排序的数组

  返回:
    排序后的数组
  """

  for i in range(len(arr) - 1):
    for j in range(len(arr) - i - 1):
      if arr[j] > arr[j + 1]:
        arr[j], arr[j + 1] = arr[j + 1], arr[j]

  return arr

选择排序

选择排序是一种简单高效的排序算法,它通过在未排序序列中找到最小(或最大)的元素,然后将其移动到序列的开头,依次类推,直到整个序列有序。

算法原理

  1. 从未排序序列中找到最小(或最大)的元素。
  2. 将其移动到序列的开头(或末尾)。
  3. 重复步骤1和步骤2,直到整个序列有序。

时间复杂度

选择排序的时间复杂度为O(n^2),这意味着当输入规模增加时,排序所花费的时间将平方增长。

空间复杂度

选择排序的空间复杂度为O(1),这意味着它不需要额外的空间来完成排序。

稳定性

选择排序是一种不稳定的排序算法,这意味着如果输入中具有相同值的元素,则它们在排序后的顺序与输入中不同。

比较次数和交换次数

选择排序的比较次数和交换次数都为O(n^2)。

代码示例

def selection_sort(arr):
  """
  选择排序算法

  参数:
    arr:要排序的数组

  返回:
    排序后的数组
  """

  for i in range(len(arr) - 1):
    min_idx = i
    for j in range(i + 1, len(arr)):
      if arr[j] < arr[min_idx]:
        min_idx = j

    arr[i], arr[min_idx] = arr[min_idx], arr[i]

  return arr

插入排序

插入排序是一种简单高效的排序算法,它通过将每个元素依次插入到已经排序好的子序列中,来构建最终的有序序列。

算法原理

  1. 将第一个元素视为有序序列。
  2. 将第二个元素与有序序列中的元素进行比较,找到其正确的位置并插入。
  3. 重复步骤2,直到所有元素都被插入到有序序列中。

时间复杂度

插入排序的时间复杂度为O(n^2),这意味着当输入规模增加时,排序所花费的时间将平方增长。

空间复杂度

插入排序的空间复杂度为O(1),这意味着它不需要额外的空间来完成排序。

稳定性

插入排序是一种稳定的排序算法,这意味着如果输入中具有相同值的元素,则它们在排序后的顺序与输入中相同。

比较次数和交换次数

插入排序的比较次数和交换次数都为O(n^2)。

代码示例

def insertion_sort(arr):
  """
  插入排序算法

  参数:
    arr:要排序的数组

  返回:
    排序后的数组
  """

  for i in range(1, len(arr)):
    key = arr[i]
    j = i - 1
    while j >= 0 and key < arr[j]:
      arr[j + 1] = arr[j]
      j -= 1
    arr[j + 1] = key

  return arr

快速排序

快速排序是一种高效的排序算法,它通过分治法将待排序的数组划分为较小的子数组,然后对这些子数组进行排序,最后将这些排序好的子数组合并为一个有序的数组。

算法原理

  1. 选择一个枢轴元素。
  2. 将数组划分为两个子数组,一个子数组包含比枢轴元素小的元素,另一个子数组包含比枢轴元素大的元素。
  3. 对这两个子数组分别进行快速排序。
  4. 合并两个排好序的子数组。

时间复杂度

快速排序的时间复杂度为O(n log n),这意味着当输入规模增加时,排序所花费的时间将以对数增长。

空间复杂度

快速排序的空间复杂度为O(log n),这意味着它需要额外的空间来存储递归调用的栈。

稳定性

快速排序是一种不稳定的排序算法,这意味着如果输入中具有相同值的元素,则它们在排序后的顺序与输入中不同。

比较次数和交换次数

快速排序的比较次数为O(n log n),交换次数为O(n log n)。

代码示例

def quick_sort(arr, low, high):
  """
  快速排序算法

  参数:
    arr:要排序的数组
    low:要排序的子数组的起始索引
    high:要排序的子数组的结束索引

  返回:
    排序好的数组
  """

  if low < high:
    partition_index = partition(arr, low, high)

    quick_sort(arr, low, partition_index - 1)
    quick_sort(arr, partition_index + 1, high)

  return arr

def partition(arr, low, high):
  """
  分区函数

  参数:
    arr:要排序的数组
    low:要排序的子数组的起始索引
    high:要排序的子数组的结束索引

  返回:
    枢轴元素的索引
  """

  pivot = arr[high]
  i = low - 1

  for j in range(low, high):
    if arr[j] <= pivot:
      i += 1
      arr[i], arr[j] = arr[j], arr[i]

  arr[i + 1], arr[high] = arr[high], arr[i + 1]

  return i + 1

比较

下表总结了四种排序算法的比较结果:

排序算法 时间复杂度 空间复杂度 稳定性 比较次数 交换次数
冒泡排序 O(n^2) O(1) O(n^2) O(n^2)
选择排序 O(n^2) O(1) O(n^2) O(n^2)
插入排序 O(n^2) O(1) O(n^2) O(n^2)
快速排序 O(n log n) O(log n) O(n log n) O(n log n)

总结

冒泡排序、选择排序、插入排序和快速排序都是常用的排序算法,每种算法都有其优缺点。选择合适的排序算法取决于具体问题和数据规模。一般来说,快速排序是效率最高的排序算法,但它需要额外的空间来存储递归调用的栈。对于小规模的数据,冒泡排序和选择排序也是不错的选择。插入排序在某些情况下比冒泡排序和选择排序更有效,例如当数据已经部分有序时。