一、冒泡排序(Bubble Sort)
1、原理
冒泡排序(Bubble Sort)是最简单和最通用的排序方法,其基本思想是:在待排序的一组数中,将相邻的两个数进行比较,若前面的数比后面的数大就交换两数,否则不交换;如此下去,直至最终完成排序 。由此可得,在排序过程中,大的数据往下沉,小的数据往上浮,就像气泡一样,于是将这种排序算法形象地称为冒泡排序。
2、算法示例
对于序列[26,28,24,11],采用非递减规则进行排序,排序过程如下所示。
3、性能分析
- 时间复杂度:最好情况下,即数组已经有序时,时间复杂度为(O(n)) ,因为只需遍历一次数组,且没有元素交换。平均情况和最差情况均为(O(n^{2})),需要完整的 n-1 轮遍历,每轮遍历比较次数为 (n-1)+(n-2)+...+1 = n(n-1)/2。
- 空间复杂度:(O(1)) ,只需要几个临时变量用于交换元素,不随数据规模增长而增加额外空间(原地排序,无需额外空间)。
- 稳定性:是一种稳定排序算法(相等元素的相对位置不变)。冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
4、适用场景
- 小规模数据:实现简单,代码量少,适合数据量小(如 n ≤ 100)的场景。
- 部分有序数据:若数据已接近有序,经过优化后的冒泡排序效率较高。
- 教学用途:作为排序算法入门示例,便于理解基本排序思想。
5、代码实现
- 基础版本python:
def bubble_sort(arr):
n = len(arr)
for i in range(n-1): # 遍历 n-1 次
for j in range(n-1-i): # 每轮遍历未排序部分
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j] # 交换
return arr
# 示例
nums = [5, 3, 8, 4, 2]
print(bubble_sort(nums)) # 输出 [2, 3, 4, 5, 8]
- 优化版本python(提前结束) :
def optimized_bubble_sort(arr):
n = len(arr)
for i in range(n-1):
swapped = False # 标记是否发生交换
for j in range(n-1-i):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped: # 无交换说明已有序,提前结束
break
return arr
# 示例
nums = [5, 3, 8, 4, 2]
print(bubble_sort(nums)) # 输出:[2, 3, 4, 5, 8]
- go 版本:
package main
import "fmt"
func bubbleSort(arr []int) {
n := len(arr)
for i := 0; i < n-1; i++ {
swapped := false
for j := 0; j < n-1-i; j++ {
if arr[j] > arr[j+1] {
// 交换元素
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = true
}
}
if !swapped {
break // 提前终止
}
}
}
func main() {
nums := []int{5, 3, 8, 4, 2}
bubbleSort(nums)
fmt.Println(nums) // 输出:[2 3 4 5 8]
}
php 版本:
function bubbleSort(&$arr) {
$n = count($arr);
for ($i = 0; $i < $n-1; $i++) {
$swapped = false;
for ($j = 0; $j < $n-1-$i; $j++) {
if ($arr[$j] > $arr[$j+1]) {
list($arr[$j], $arr[$j+1]) = array($arr[$j+1], $arr[$j]);
$swapped = true;
}
}
if (!$swapped) break;
}
}
// 示例
$nums = [5, 3, 8, 4, 2];
bubbleSort($nums);
print_r($nums); // 输出 [2, 3, 4, 5, 8]
- java 版本:
public class BubbleSort {
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
boolean swapped = false;
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
if (!swapped) break; // 提前终止
}
}
public static void main(String[] args) {
int[] nums = {5, 3, 8, 4, 2};
bubbleSort(nums);
System.out.println(java.util.Arrays.toString(nums)); // 输出:[2, 3, 4, 5, 8]
}
}
二、快速排序(Quick Sort)
1、原理
快速排序(Quick Sort)是一种基于分治法的高效排序算法。其核心思想是:
- 分治:选择一个基准元素(pivot),将数组划分为两个子数组,使得左侧元素均 ≤ pivot,右侧元素均 ≥ pivot。
- 递归:对左右子数组递归执行上述步骤,直到子数组长度为1或0时终止。
核心步骤:
- 分区(Partition):通过交换元素将数组按基准划分为两部分。
- 递归排序:对左右子数组重复分区过程。
2、性能分析
- 时间复杂度:
- 平均情况:O(n log n)
- 最坏情况:O(n²)(当数组已有序且基准选择不当时)
- 空间复杂度:
- 原地排序,主要消耗递归栈空间。
- 平均:O(log n)(递归深度)
- 最坏:O(n)(如数组完全有序)
- 稳定性:不稳定(元素交换可能破坏相同值的相对顺序)
优化策略:
- 随机选择基准:避免最坏时间复杂度。
- 三数取中法:选择首、中、尾元素的中位数作为基准。
- 三路快排:对含大量重复元素的数据,分成“小、等、大”三部分,减少递归次数。
3、适用场景
- 推荐场景:
- 大规模数据排序,平均性能最优。
- 内存有限时(原地排序,空间效率高)。
- 不推荐场景:
- 数据规模小(此时插入排序更高效)。
- 严格要求稳定性(需改用归并排序)。
- 数据已基本有序且未优化基准选择策略。
4、代码实现
-
php 版本
<?php
$arr = array(25,133,452,364,5876,293,607,365,8745,534,18,33);
function quick_sort($arr)
{
// 判断是否需要继续
if (count($arr) <= 1) {
return $arr;
}
$middle = $arr[0]; // 中间值
$left = array(); // 小于中间值
$right = array();// 大于中间值
// 循环比较
for ($i=1; $i < count($arr); $i++) {
if ($middle < $arr[$i]) {
// 大于中间值
$right[] = $arr[$i];
} else {
// 小于中间值
$left[] = $arr[$i];
}
}
// 递归排序两边
$left = quick_sort($left);
$right = quick_sort($right);
// 合并排序后的数据,别忘了合并中间值
return array_merge($left, array($middle), $right);
}
var_dump($arr);
var_dump(quick_sort($arr));
-
Python版本之 分而治之+递归
def quick_sort(data):
if len(data) >= 2: # 递归入口及出口
mid = data[len(data)//2] # 选取基准值,也可以选取第一个或最后一个元素
left, right = [], [] # 定义基准值左右两侧的列表
data.remove(mid) # 从原始数组中移除基准值
for num in data:
if num >= mid:
right.append(num)
else:
left.append(num)
return quick_sort(left) + [mid] + quick_sort(right)
else:
return data
# 示例:
array = [2,3,5,7,1,4,6,15,5,2,7,9,10,15,9,17,12]
print(quick_sort(array))
# 输出为[1, 2, 2, 3, 4, 5, 5, 6, 7, 7, 9, 9, 10, 12, 15, 15, 17]
go 版本:
// 第一种写法
func quickSort(values []int, left, right int) {
temp := values[left]
p := left
i, j := left, right
for i <= j {
for j >= p && values[j] >= temp {
j--
}
if j >= p {
values[p] = values[j]
p = j
}
for i <= p && values[i] <= temp {
i++
}
if i <= p {
values[p] = values[i]
p = i
}
}
values[p] = temp
if p-left > 1 {
quickSort(values, left, p-1)
}
if right-p > 1 {
quickSort(values, p+1, right)
}
}
func QuickSort(values []int) {
if len(values) <= 1 {
return
}
quickSort(values, 0, len(values)-1)
}
// 第二种写法
func Quick2Sort(values []int) {
if len(values) <= 1 {
return
}
mid, i := values[0], 1
head, tail := 0, len(values)-1
for head < tail {
fmt.Println(values)
if values[i] > mid {
values[i], values[tail] = values[tail], values[i]
tail--
} else {
values[i], values[head] = values[head], values[i]
head++
i++
}
}
values[head] = mid
Quick2Sort(values[:head])
Quick2Sort(values[head+1:])
}
// 第三种写法
func Quick3Sort(a []int,left int, right int) {
if left >= right {
return
}
explodeIndex := left
for i := left + 1; i <= right ; i++ {
if a[left] >= a[i]{
//分割位定位++
explodeIndex ++;
a[i],a[explodeIndex] = a[explodeIndex],a[i]
}
}
//起始位和分割位
a[left], a[explodeIndex] = a[explodeIndex],a[left]
Quick3Sort(a,left,explodeIndex - 1)
Quick3Sort(a,explodeIndex + 1,right)
}
java 版本:
/**
* 入口函数(递归方法),算法的调用从这里开始。
*/
public void quickSort(int[] arr, int startIndex, int endIndex) {
if (startIndex >= endIndex) {
return;
}
// 核心算法部分:分别介绍 双边指针(交换法),双边指针(挖坑法),单边指针
int pivotIndex = doublePointerSwap(arr, startIndex, endIndex);
// int pivotIndex = doublePointerHole(arr, startIndex, endIndex);
// int pivotIndex = singlePointer(arr, startIndex, endIndex);
// 用分界值下标区分出左右区间,进行递归调用
quickSort(arr, startIndex, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, endIndex);
}
/**
* 双边指针(交换法)
* 思路:
* 记录分界值 pivot,创建左右指针(记录下标)。
* (分界值选择方式有:首元素,随机选取,三数取中法)
*
* 首先从右向左找出比pivot小的数据,
* 然后从左向右找出比pivot大的数据,
* 左右指针数据交换,进入下次循环。
*
* 结束循环后将当前指针数据与分界值互换,
* 返回当前指针下标(即分界值下标)
*/
private int doublePointerSwap(int[] arr, int startIndex, int endIndex) {
int pivot = arr[startIndex];
int leftPoint = startIndex;
int rightPoint = endIndex;
while (leftPoint < rightPoint) {
// 从右向左找出比pivot小的数据
while (leftPoint < rightPoint
&& arr[rightPoint] > pivot) {
rightPoint--;
}
// 从左向右找出比pivot大的数据
while (leftPoint < rightPoint
&& arr[leftPoint] <= pivot) {
leftPoint++;
}
// 没有过界则交换
if (leftPoint < rightPoint) {
int temp = arr[leftPoint];
arr[leftPoint] = arr[rightPoint];
arr[rightPoint] = temp;
}
}
// 最终将分界值与当前指针数据交换
arr[startIndex] = arr[rightPoint];
arr[rightPoint] = pivot;
// 返回分界值所在下标
return rightPoint;
}
/**
* 双边指针(挖坑法)
* 思路:
* 创建左右指针。
* 记录左指针数据为分界值 pivot,
* 此时左指针视为"坑",里面的数据可以被覆盖。
*
* 首先从右向左找出比pivot小的数据,
* 找到后立即放入左边坑中,当前位置变为新的"坑",
* 然后从左向右找出比pivot大的数据,
* 找到后立即放入右边坑中,当前位置变为新的"坑",
*
* 结束循环后将最开始存储的分界值放入当前的"坑"中,
* 返回当前"坑"下标(即分界值下标)
*/
private int doublePointerHole(int[] arr, int startIndex, int endIndex) {
int pivot = arr[startIndex];
int leftPoint = startIndex;
int rightPoint = endIndex;
while (leftPoint < rightPoint) {
// 从右向左找出比pivot小的数据,
while (leftPoint < rightPoint
&& arr[rightPoint] > pivot) {
rightPoint--;
}
// 找到后立即放入左边坑中,当前位置变为新的"坑"
if (leftPoint < rightPoint) {
arr[leftPoint] = arr[rightPoint];
leftPoint++;
}
// 从左向右找出比pivot大的数据
while (leftPoint < rightPoint
&& arr[leftPoint] <= pivot) {
leftPoint++;
}
// 找到后立即放入右边坑中,当前位置变为新的"坑"
if (leftPoint < rightPoint) {
arr[rightPoint] = arr[leftPoint];
rightPoint--;
}
}
// 将最开始存储的分界值放入当前的"坑"中
arr[rightPoint] = pivot;
return rightPoint;
}
/**
* 单边指针
* 思路:
* 记录首元素为分界值 pivot, 创建标记 mark 指针。
* 循环遍历与分界值对比。
* 比分界值小,则 mark++ 后与之互换。
* 结束循环后,将首元素分界值与当前mark互换。
* 返回 mark 下标为分界值下标。
*/
private int singlePointer(int[] arr, int startIndex, int endIndex) {
int pivot = arr[startIndex];
int markPoint = startIndex;
for (int i = startIndex + 1; i <= endIndex; i++) {
// 如果比分界值小,则 mark++ 后互换。
if (arr[i] < pivot) {
markPoint++;
int temp = arr[markPoint];
arr[markPoint] = arr[i];
arr[i] = temp;
}
}
// 将首元素分界值与当前mark互换
arr[startIndex] = arr[markPoint];
arr[markPoint] = pivot;
return markPoint;
}
三、选择排序(Selection Sort)
1、原理
选择排序是一种简单的原地比较排序算法,其核心思想是每次从未排序区间中选择最小(或最大)元素,将其放到已排序区间的末尾。具体步骤如下:
- 初始化:整个数组均为未排序区间。
- 查找最小值:遍历未排序区间,找到最小值并记录其索引。
- 交换位置:将最小值与未排序区间的第一个元素交换。
- 区间缩减:未排序区间长度减 1,已排序区间长度增 1。
- 重复:重复上述步骤,直到所有元素有序。
不稳定性示例:
数组 [5, 8, 5, 2]
排序时,第一个 5
会与 2
交换,导致两个 5
的相对顺序改变。
2、性能分析
- 时间复杂度:始终为 O(n²)(无论数据是否有序)。
- 比较次数:固定为 n(n-1)/2。
- 交换次数:O(n)(每次外层循环交换一次)。
- 空间复杂度:O(1)(原地排序)。
- 稳定性:不稳定(交换可能破坏相同元素的顺序)。
3、适用场景
适用于数据规模较小,且对稳定性要求不高的场景。例如,对一些小规模的无序数据进行简单排序,选择排序的简单性使其易于实现和理解。但对于大规模数据,由于其时间复杂度较高,效率较低,不建议使用。
4、代码示例
Python 版本:
def selection_sort(arr):
n = len(arr)
for i in range(n - 1): # 外层循环 n-1 次
min_index = i
for j in range(i + 1, n):
if arr[j] < arr[min_index]:
min_index = j
arr[i], arr[min_index] = arr[min_index], arr[i] # 交换
return arr
golang版本:
func selectionSort(arr []int) {
n := len(arr)
for i := 0; i < n-1; i++ { // 外层循环 n-1 次
minIndex := i
for j := i + 1; j < n; j++ {
if arr[j] < arr[minIndex] {
minIndex = j
}
}
arr[i], arr[minIndex] = arr[minIndex], arr[i] // 交换
}
}
php 版本:
function selectionSort(&$arr) {
$n = count($arr);
for ($i = 0; $i < $n - 1; $i++) { // 外层循环 n-1 次
$minIndex = $i;
for ($j = $i + 1; $j < $n; $j++) {
if ($arr[$j] < $arr[$minIndex]) {
$minIndex = $j;
}
}
// 交换元素
$temp = $arr[$i];
$arr[$i] = $arr[$minIndex];
$arr[$minIndex] = $temp;
}
}
java 版本:
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) { // 外层循环 n-1 次
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 交换元素
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
核心逻辑:
- 双重循环:外层控制已排序区间末尾,内层寻找最小值。
- 原地交换:通过索引直接修改原数组,无额外空间开销。
- 统一优化:外层循环执行 n-1 次(第 n 个元素无需处理)。
四. 插入排序(Insertion Sort)
1、 原理
插入排序是一种稳定且原地(in-place)的排序算法,核心思想是将未排序元素逐个插入已排序部分的正确位置。具体步骤如下:
- 初始化:将数组第一个元素视为已排序区间。
- 遍历未排序区间:从第二个元素开始,依次与已排序区间的元素从后向前比较。
- 插入操作:若当前元素 < 已排序元素,则将其后移一位,直到找到插入位置。
- 重复:直到所有元素插入完成。
示例:排序 [5, 2, 4, 6, 1]
- 第1步:
[5]
→[2,5]
- 第2步:
[2,5]
→[2,4,5]
- 第3步:
[2,4,5]
→[2,4,5,6]
- 第4步:
[2,4,5,6]
→[1,2,4,5,6]
3、 性能分析
- 时间复杂度:
- 最好:
O(n)
(数组已有序时,只需比较不移动)。 - 平均和最坏:
O(n²)
(需要双重循环比较和移动)。
- 最好:
- 空间复杂度:
O(1)
(原地排序,无需额外空间)。 - 稳定性:稳定(相等元素插入时不改变原有顺序)。
对比选择排序:
- 插入排序在数据部分有序时更高效(比较次数减少)。
- 选择排序无论数据是否有序,始终需要
O(n²)
次比较。
4、 适用场景
- 小规模数据:时间复杂度为
O(n²)
,适合数据量较小(如n < 1000
)。 - 数据接近有序:例如日志按时间近乎有序插入,性能接近
O(n)
。 - 流式数据实时处理:逐个接收新数据并插入有序区间。
- 稳定排序需求:需保持相同元素的原始顺序。
不适用场景:
- 大规模乱序数据(如
n > 10,000
)。 - 对内存敏感但数据量大的场景(优先选择
O(n log n)
算法)。
5、代码实现
核心逻辑
- 外层循环:遍历未排序元素(从第二个元素开始)。
- 内层循环:从后向前比较,移动元素腾出插入位置。
- 插入操作:将当前元素插入到正确位置,保持有序性。
python:
def insertion_sort(arr):
for i in range(1, len(arr)):
current = arr[i]
j = i - 1
while j >= 0 and current < arr[j]:
arr[j + 1] = arr[j] # 后移元素
j -= 1
arr[j + 1] = current # 插入正确位置
return arr
golang:
func insertionSort(arr []int) {
for i := 1; i < len(arr); i++ {
current := arr[i]
j := i - 1
for j >= 0 && current < arr[j] {
arr[j+1] = arr[j]
j--
}
arr[j+1] = current
}
}
php:
function insertionSort(&$arr) {
$n = count($arr);
for ($i = 1; $i < $n; $i++) {
$current = $arr[$i];
$j = $i - 1;
while ($j >= 0 && $current < $arr[$j]) {
$arr[$j + 1] = $arr[$j];
$j--;
}
$arr[$j + 1] = $current;
}
}
java:
public static void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int current = arr[i];
int j = i - 1;
while (j >= 0 && current < arr[j]) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = current;
}
}
对比选择排序 vs 插入排序
特性 | 选择排序 | 插入排序 |
---|---|---|
时间复杂度 | 始终 | 最好 |
稳定性 | 不稳定 | 稳定 |
交换次数 |
|
|
适用场景 | 小数据且需减少交换次数 | 小数据或部分有序数据 |
总结:
- 若数据已接近有序,优先选择插入排序。
- 若需减少交换次数(如元素体积大),优先选择选择排序。
五、 归并排序(Merge Sort)
1、 原理
归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的稳定排序算法,核心思想是:
- 分解(Divide):将数组递归分成两半,直到子数组长度为 1(天然有序)。
- 合并(Merge):将两个有序子数组合并为一个有序数组,重复此过程直到整体有序。
关键步骤示意图:
原始数组: [3, 7, 2, 5, 1]
分解 → [3,7,2] 和 [5,1] → 继续分解直到单元素
合并 → [2,3,7] 和 [1,5] → 最终合并为 [1,2,3,5,7]
2、 性能分析
- 时间复杂度:
- 最好、最坏、平均均为 O(n log n)。
- 分解:每次将数组分为两半(递归深度 log₂n)。
- 合并:每层合并总操作 O(n)。
- 空间复杂度:O(n)(合并时需要额外临时数组)。
- 稳定性:稳定(合并时保留相等元素的原始顺序)。
3、 适用场景
- 大规模数据排序:O(n log n) 时间复杂度优于 O(n²) 算法。
- 稳定性需求:需要保留相同元素的原始顺序(如多条件排序)。
- 链表排序:归并排序是链表排序的最佳选择之一(无需随机访问)。
- 外部排序:适用于数据量超出内存的场景(如大文件分块排序)。
不适用场景:
- 内存严格受限(需 O(n) 额外空间)。
- 小规模数据(简单排序算法更高效)。
4、代码实现
Python:
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
merged = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]: # 保证稳定性
merged.append(left[i])
i += 1
else:
merged.append(right[j])
j += 1
merged.extend(left[i:])
merged.extend(right[j:])
return merged
golang:
func mergeSort(arr []int) []int {
if len(arr) <= 1 {
return arr
}
mid := len(arr) / 2
left := mergeSort(arr[:mid])
right := mergeSort(arr[mid:])
return merge(left, right)
}
func merge(left, right []int) []int {
merged := make([]int, 0, len(left)+len(right))
i, j := 0, 0
for i < len(left) && j < len(right) {
if left[i] <= right[j] { // 稳定性关键
merged = append(merged, left[i])
i++
} else {
merged = append(merged, right[j])
j++
}
}
merged = append(merged, left[i:]...)
merged = append(merged, right[j:]...)
return merged
}
php:
function mergeSort($arr) {
if (count($arr) <= 1) {
return $arr;
}
$mid = intdiv(count($arr), 2);
$left = mergeSort(array_slice($arr, 0, $mid));
$right = mergeSort(array_slice($arr, $mid));
return merge($left, $right);
}
function merge($left, $right) {
$merged = [];
$i = $j = 0;
while ($i < count($left) && $j < count($right)) {
if ($left[$i] <= $right[$j]) { // 保持稳定
array_push($merged, $left[$i]);
$i++;
} else {
array_push($merged, $right[$j]);
$j++;
}
}
return array_merge($merged, array_slice($left, $i), array_slice($right, $j));
}
java:
public static void mergeSort(int[] arr) {
if (arr.length <= 1) return;
int mid = arr.length / 2;
int[] left = Arrays.copyOfRange(arr, 0, mid);
int[] right = Arrays.copyOfRange(arr, mid, arr.length);
mergeSort(left);
mergeSort(right);
merge(arr, left, right);
}
private static void merge(int[] arr, int[] left, int[] right) {
int i = 0, j = 0, k = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) { // 稳定排序
arr[k++] = left[i++];
} else {
arr[k++] = right[j++];
}
}
while (i < left.length) arr[k++] = left[i++];
while (j < right.length) arr[k++] = right[j++];
}
5、归并排序VS 快排
核心原理:
维度 | 归并排序 (Merge Sort) | 快速排序 (Quick Sort) |
---|---|---|
分治策略 | 强制均分:将数组等分为左右两半 | 动态划分:通过**基准(pivot)**将数组分为“小等于区”和“大于区”,划分位置不固定 |
核心操作 | 合并有序子数组:需要额外空间合并排序后的子数组 | 原地分区:通过交换元素直接划分区域,无需合并 |
递归顺序 | 先递归再处理(后序遍历) | 先处理再递归(先序遍历) |
性能对比:
维度 | 归并排序 | 快速排序 |
---|---|---|
时间复杂度 | 始终 O(n log n)(稳定性能) | 平均 O(n log n),最坏 O(n²)(但可通过优化避免) |
空间复杂度 | O(n)(合并时需要额外数组) | O(log n)(递归栈空间,原地排序) |
稳定性 | ✅ 稳定(合并时保持相等元素顺序) | ❌ 不稳定(分区交换可能打乱顺序) |
缓存友好性 | ❌ 较差(频繁内存跳转访问) | ✅ 较好(分区操作局部连续访问) |
最坏触发条件 | 无(任何情况下保持 O(n log n)) | 基准选择不当导致极度不平衡分区(如已有序数组选首元素为基准) |
整体总结对比:
特性 | 冒泡排序 | 快速排序 | 选择排序 | 归并排序 | 插入排序 |
---|---|---|---|---|---|
时间复杂度 | O(n²) | O(n log n) | O(n²) | O(n log n) | O(n²) |
空间复杂度 | O(1) | O(log n) | O(1) | O(n) | O(1) |
稳定性 | ✔️ | ❌ | ❌ | ✔️ | ✔️ |
最佳用例 | 小数据 | 大规模数据 | 简单实现 | 大数据需稳定 | 小或有序数据 |