经典排序算法合集(上)

一、冒泡排序(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. 初始化:整个数组均为未排序区间。
  2. 查找最小值:遍历未排序区间,找到最小值并记录其索引。
  3. 交换位置:将最小值与未排序区间的第一个元素交换。
  4. 区间缩减:未排序区间长度减 1,已排序区间长度增 1。
  5. 重复:重复上述步骤,直到所有元素有序。

不稳定性示例
数组 [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)的排序算法,核心思想是将未排序元素逐个插入已排序部分的正确位置。具体步骤如下:

  1. 初始化:将数组第一个元素视为已排序区间。
  2. 遍历未排序区间:从第二个元素开始,依次与已排序区间的元素从后向前比较。
  3. 插入操作:若当前元素 < 已排序元素,则将其后移一位,直到找到插入位置。
  4. 重复:直到所有元素插入完成。

示例:排序 [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、 适用场景

  1. 小规模数据:时间复杂度为 O(n²),适合数据量较小(如 n < 1000)。
  2. 数据接近有序:例如日志按时间近乎有序插入,性能接近 O(n)
  3. 流式数据实时处理:逐个接收新数据并插入有序区间。
  4. 稳定排序需求:需保持相同元素的原始顺序。

不适用场景

  • 大规模乱序数据(如 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 插入排序

特性

选择排序

插入排序

时间复杂度

始终 O(n²)

最好 O(n),最坏 O(n²)

稳定性

不稳定

稳定

交换次数

O(n)(每次循环交换一次)

O(n²)(可能频繁移动元素)

适用场景

小数据且需减少交换次数

小数据或部分有序数据

总结

  • 若数据已接近有序,优先选择插入排序
  • 若需减少交换次数(如元素体积大),优先选择选择排序

五、 归并排序(Merge Sort)

1、 原理

归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)稳定排序算法,核心思想是:

  1. 分解(Divide):将数组递归分成两半,直到子数组长度为 1(天然有序)。
  2. 合并(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、 适用场景

  1. 大规模数据排序:O(n log n) 时间复杂度优于 O(n²) 算法。
  2. 稳定性需求:需要保留相同元素的原始顺序(如多条件排序)。
  3. 链表排序:归并排序是链表排序的最佳选择之一(无需随机访问)。
  4. 外部排序:适用于数据量超出内存的场景(如大文件分块排序)。

不适用场景

  • 内存严格受限(需 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)

稳定性

✔️

✔️

✔️

最佳用例

小数据

大规模数据

简单实现

大数据需稳定

小或有序数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值