细说排序算法之冒泡、选择、插入、快速排序
2023-10-18 13:54:59
冒泡排序
冒泡排序是一种简单的排序算法,它反复地比较相邻的元素,如果前一个元素大于后一个元素,则交换它们的位置。这个过程一直持续到没有任何元素需要交换为止。
算法原理
- 比较相邻的两个元素。
- 如果前一个元素大于后一个元素,则交换它们的位置。
- 重复步骤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,直到整个序列有序。
时间复杂度
选择排序的时间复杂度为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
插入排序
插入排序是一种简单高效的排序算法,它通过将每个元素依次插入到已经排序好的子序列中,来构建最终的有序序列。
算法原理
- 将第一个元素视为有序序列。
- 将第二个元素与有序序列中的元素进行比较,找到其正确的位置并插入。
- 重复步骤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
快速排序
快速排序是一种高效的排序算法,它通过分治法将待排序的数组划分为较小的子数组,然后对这些子数组进行排序,最后将这些排序好的子数组合并为一个有序的数组。
算法原理
- 选择一个枢轴元素。
- 将数组划分为两个子数组,一个子数组包含比枢轴元素小的元素,另一个子数组包含比枢轴元素大的元素。
- 对这两个子数组分别进行快速排序。
- 合并两个排好序的子数组。
时间复杂度
快速排序的时间复杂度为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) |
总结
冒泡排序、选择排序、插入排序和快速排序都是常用的排序算法,每种算法都有其优缺点。选择合适的排序算法取决于具体问题和数据规模。一般来说,快速排序是效率最高的排序算法,但它需要额外的空间来存储递归调用的栈。对于小规模的数据,冒泡排序和选择排序也是不错的选择。插入排序在某些情况下比冒泡排序和选择排序更有效,例如当数据已经部分有序时。