详解七大排序

news2025/4/6 10:33:35

目录

一.直接插入排序

(1)基本思想

(2)算法步骤

(3)代码实现

(4)算法特性

(5)算法优化

(6)示例演示

二.希尔排序

(1)基本思想

(2)算法步骤

(3)代码实现

(4)算法特性

 (5)算法优化

(6)实例演示

 三.选择排序

(1)基本思想

(2)算法步骤

(3)代码实现

1)每次交换一个元素(最小元素):

2)每次交换两个元素(一个最大元素,一个最小元素):

(4)算法特性

(5)算法优化

(6)实例演示

 四.堆排序

(1)基本思想

(2)算法步骤

(3)代码实现

 (4)算法特性

(5)算法优化

(6)实例演示

1)建堆过程:

 2)排序阶段

五.冒泡排序

(1)基本思想

(2)算法步骤

(3)代码实现

(4)算法特性

 (5)算法优化

1. 提前终止(已实现)

2. 鸡尾酒排序(双向冒泡)

3. 记录最后交换位置

 (6)实例演示

六.快速排序

(1)基本思想

(2)算法步骤

(3)代码实现

 (4)算法特性

1. 高效的平均性能

2. 内存效率高

3. 实现灵活

最坏情况性能差

2. 不稳定排序

3. 递归实现的风险

(5)算法优化

1. 三数取中法选择基准值

2. 尾递归优化

3. 插入排序混合优化

 (6)实例演示

示例1:基础快速排序流程

示例2:三数取中法优化

示例3:三路快排处理重复元素

七.归并排序

(1)基本思想

(2)算法步骤

(3)代码实现

(4)算法特性

(5)算法优化

1. 小规模数据使用插入排序

2. 减少不必要的数组复制

3. 提前终止合并过程

(6)实例演示

分解阶段

合并阶段

八.常见排序表

 在数据结构中,排序是组织和处理数据的核心操作之一。它不仅是数据组织的核心手段,更是提升计算效率、优化资源利用的基础。其应用贯穿于数据库、操作系统、机器学习、实时系统等领域,是算法设计与系统优化的基石。

选择适合的排序算法需综合考虑数据规模、内存限制、稳定性需求及硬件特性。

这篇文章详细解析了七大排序的思想、步骤及其特点。

一.直接插入排序

(1)基本思想

直接插入排序是一种简单的排序算法,其基本思想是:将待排序的元素逐个插入到已排序序列的适当位置,直到所有元素都插入完毕。

(2)算法步骤

  1. 将第一个元素视为已排序序列

  2. 取出下一个元素,在已排序序列中从后向前扫描

  3. 如果已排序元素大于新元素,将该元素移到下一位置

  4. 重复步骤3,直到找到已排序元素小于或等于新元素的位置

  5. 将新元素插入到该位置

  6. 重复步骤2~5,直到所有元素都排序完成

(3)代码实现

 for (int i = 1; i < array.length; i++) {
            int temp = array[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (array[j] > temp) {
       /*array[j+1]就是temp,但是1个i对应很多个j,所以i不是等于j+1;要用j+1来表示temp*/
                    array[j + 1] = array[j];
                } else {
                    //不用改变元素位置,把取出来的temp再放回去
              // array[j+1]=temp;
                    break;
                }
            }
           /*出了j的循环,证明j这时已经小于0了,也就是-1把取出来的temp再放回去j+1的位置,也就是下标0*/
            array[j + 1] = temp;
        }

(4)算法特性

特性说明
时间复杂度最好O(n),最坏和平均O(n²)
空间复杂度O(1)(原地排序)
稳定性稳定排序
适用场景小规模数据或基本有序数据
优点

(1)实现简单直观;

(2)稳定排序,不会改变相同元素的相对顺序;

(3)原地排序,不需要额外的存储空间(空间复杂度O(1));

(4)对小规模或部分有序数据高效;

缺点

(1)时间复杂度高:平均O(n²);

(2)对逆序数据表现最差;

(3)数据移动频繁;

(5)算法优化

  1. 二分查找优化:在已排序部分使用二分查找确定插入位置(减少比较次数)

  2. 希尔排序:是插入排序的改进版,通过分组插入提高效率

(6)示例演示

以数组 [5, 2, 4, 6, 1, 3] 为例:

初始:  [5, 2, 4, 6, 1, 3]
第1轮: [2, 5, 4, 6, 1, 3] (插入2)
第2轮: [2, 4, 5, 6, 1, 3] (插入4)
第3轮: [2, 4, 5, 6, 1, 3] (插入6)
第4轮: [1, 2, 4, 5, 6, 3] (插入1)
第5轮: [1, 2, 3, 4, 5, 6] (插入3)

直接插入排序虽然简单,但在处理小规模或部分有序数据时效率较高,且实现简单,常被用作其他高级排序算法的子过程。

二.希尔排序

(1)基本思想

希尔排序(Shell Sort)是插入排序的一种高效改进版本,由Donald Shell于1959年提出。其核心思想是通过分组插入排序逐步减少间隔(增量),最终实现整个数组的有序化。

希尔排序的思想是通过分组来减少增量,提前进行多次小范围排序,使得数据有序化,最后当gap=1(最后一次排序)时,实现高效率的直接插入排序。

(2)算法步骤

  1. 选择增量序列:确定一个递减的增量序列(例如初始增量为数组长度的一半,后续逐步减半)。

  2. 分组插入排序:对每个增量间隔形成的子序列进行直接插入排序

  3. 缩小增量:重复步骤2,直到增量为1,此时整个数组作为一整个序列进行最后一次插入排序(希尔排序的最后一次排序就是直接插入排序)。

(3)代码实现

 public static void shellSort(int[] array) {
        int gap = array.length / 2;
/*随着gap的递减,分的组数也越来越少,直接组数为1,gap=1,这时进行直接插入排序*/
        while (gap > 0) {
            shell(array, gap);
            gap = gap / 2;
        }
    }

    //希尔排序内排序
    public static void shell(int[] array, int gap) {
        for (int i = gap; i < array.length; i++) {
            int temp = array[i];
            int j = i - gap;
            for (; j >= 0; j = j - gap) {
                if (array[j] > temp) {
                    array[j + gap] = array[j];
                } else {
                    break;
                }
            }
            //此时j为负数,但不是-1
            array[j + gap] = temp;
        }
    }

希尔排序是优化版的直接插入排序,旨在提升直接插入排序的效率。

并且希尔排序的最后一次排序就是直接插入排序,希尔排序之所以效率高是因为希尔排序通过前面几次小范围排序使得数组逐渐有序化(直接插入排序在数据有序化的时候效率高)。

(4)算法特性

特性说明
时间复杂度最坏O(n²),平均 O(n log n)(当希尔增量(n/2递减)时)
空间复杂度O(1)
稳定性不稳定排序(相同元素可能在排序时改变相对顺序)
适用场景中小规模数据排序(特别是内存受限环境)
优点

原地排序,不需要额外的存储空间;

比直接插入排序效率更高,更适合中等规模数据;

灵活性强,可以通过选择不同增量序列来优化性能。

缺点

不稳定排序,相同元素可能在排序时改变相对顺序;

增量依赖,性能受增量序列的选择影响较大;

理论复杂,最佳增量序列至今尚无统一结论。

希尔排序的时间复杂度分析是算法理论中的一个经典难题,其复杂度高度依赖增量序列的选择目前无法精确计算所有情况下的时间复杂度。 

 (5)算法优化

Sedgewick增量优化:

使用Robert Sedgewick提出的增量序列实现的希尔排序改进版本。这种增量序列通过数学优化显著提升了希尔排序的性能,是目前已知综合表现最优的增量序列之一

public static void shellSortOptimized(int[] arr) {
    int n = arr.length;
    // Sedgewick增量序列(部分)
    int[] gaps = {1073, 281, 77, 23, 8, 1}; 
    
    for (int gap : gaps) {
        if (gap >= n) continue;
        for (int i = gap; i < n; i++) {
            int temp = arr[i];
            int j;
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
}

(6)实例演示

算法演示(以希尔增量为例)
初始数组:[12, 34, 54, 2, 3, 8, 15, 29]

增量 = 4:

分组:[12,3], [34,8], [54,15], [2,29]

排序后:[3,8,15,2,12,34,54,29]

增量 = 2:

分组:[3,15,12,54], [8,2,34,29]

排序后:[3,2,12,8,15,29,54,34]

增量 = 1:

对整个数组插入排序,最终有序。

希尔排序的时间复杂度并非固定值,而是由增量序列决定。早期资料中的 O(n log n) 是对特定场景的简化描述,现代研究更倾向于具体分析增量序列的影响。实际开发中,选择优化增量序列可显著提升性能,使其在小规模数据排序中具备竞争力。

 三.选择排序

(1)基本思想

选择排序是一种简单直观的排序算法,其核心思想是每次从未排序序列中选出最小(或最大)元素,将其放到已排序序列的末尾,直到所有元素排序完成。

(2)算法步骤

  1. 初始化:整个数组视为未排序序列

  2. 寻找最小值:遍历未排序序列,找到最小元素的位置

  3. 交换位置:将最小元素与未排序序列的第一个元素交换

  4. 缩小范围:将已找到的最小元素归入已排序序列

  5. 重复操作:重复步骤2~4,直到所有元素有序

(3)代码实现

1)每次交换一个元素(最小元素):

public static void selectSort(int[] array) {
        for (int i = 0; i < array.length; i++) {
            int minIndex = i;
            for (int j = i + 1; j < array.length; j++) {
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            //这时j已经遍历完数组,交换存储的最小值下标元素和array[i]
            swap(array, minIndex, i);
        }
    }

    //定义方法:交换数组中两个下标对应的元素
    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;

    }

2)每次交换两个元素(一个最大元素,一个最小元素):

 public static void selectSort2(int[] array) {
        int left = 0;
        int right = array.length - 1;
        while (left < right) {
            int minIndex = left;
            int maxIndex = left;
            for (int i = left + 1; i <= right; i++) {
                if (array[i] > array[maxIndex]) {
                    maxIndex = i;
                } else if (array[i] < array[minIndex]) {
                    minIndex = i;
                }
            }
            //走完i循环,交换元素
            swap(array, minIndex, left);
            if (maxIndex == left) {
                swap(array, left, right);
            } else {
                swap(array, maxIndex, right);
            }
            left++;
            right--;
        }
    }

(4)算法特性

特性说明
时间复杂度固定 O(n²)(无论数据是否有序)
空间复杂度O(1)(原地排序)
稳定性通常不稳定(可特殊实现为稳定)
交换次数最多 n-1 次交换
比较次数固定 n(n−1)22n(n−1)​ 次
优点
  1. 简单直观:逻辑清晰,易于理解和实现

  2. 交换次数少:每轮仅需一次交换(相比冒泡排序更高效)

  3. 内存友好:原地排序,无需额外空间

  4. 适用小数据:数据量较小时实际性能尚可

缺点
  1. 效率低下:时间复杂度始终为 O(n²),不适合大规模数据

  2. 无适应性:无论数据初始状态如何,比较次数固定

  3. 不稳定排序:默认实现会改变相同元素的相对顺序

(5)算法优化

  1. 双向选择排序(鸡尾酒选择排序)

    • 同时寻找最小值和最大值,减少循环次数

    • 优化后比较次数减少约50%

  2. 堆排序优化

    • 使用堆结构优化选择过程,时间复杂度降为 O(n log n)

    • 实际上堆排序就是选择排序的高级变种

(6)实例演示

初始状态: [5, 2, 4, 6, 1, 3]
第1轮:找到最小值1 → 交换位置 → [1, 2, 4, 6, 5, 3]
第2轮:找到次小值2 → 无需交换 → [1, 2, 4, 6, 5, 3]
第3轮:找到最小值3 → 交换位置 → [1, 2, 3, 6, 5, 4]
第4轮:找到最小值4 → 交换位置 → [1, 2, 3, 4, 5, 6]
第5轮:数组已有序,排序完成

 四.堆排序

(1)基本思想

堆排序是一种基于完全二叉树结构的高效排序算法,利用堆的性质进行排序。其核心思想是:

  1. 构建最大堆(或最小堆),将无序数组转换为堆结构

  2. 逐步取出堆顶元素(最大值或最小值),与堆末尾元素交换并调整堆

  3. 重复调整直到堆大小为1,完成排序

(2)算法步骤

  1. 构建最大堆
    从最后一个非叶子节点开始,自底向上调整堆结构

  2. 堆排序阶段

    • 交换堆顶与末尾元素(最大值归位)

    • 缩小堆范围并重新调整堆

    • 重复直到堆大小为1

(3)代码实现

    public static void heapSort(int[] array) {
        createHeap(array);
        for (int end = array.length - 1; end >= 0; end--) {
            //交换堆顶元素和最后一个元素
            swap(array, 0, end);
            //调整剩余元素为大根堆
            siftDown(array, 0, end);
        }

    }

    //定义方法:创建1个大根堆
    public static void createHeap(int[] array) {
        //确定最后1棵子树的位置
        for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
            //siftDown是将1棵子树调整为大根堆,所以要使parent--,调整所有的子树至大根堆
            siftDown(array, parent, array.length);
        }
    }

    /*定义方法:向下调整;将1个堆调整为大根堆在数组array中向下调整到length位置*/
    public static void siftDown(int[] array, int parent, int length) {

        int child = 2 * parent + 1;
        while (child < length) {
            //拿到左右孩子最大值
            if (child < array.length - 1 && array[child] >= array[child + 1]) {
                child++;
            }
            //如果孩子最大值大于父亲节点,交换两个节点的值
            if (array[child] > array[parent]) {
                swap(array, child, parent);
                //继续向下调整
                parent = child;
                child = 2 * parent + 1;

            } else {
                break;
            }
        }

    }

 (4)算法特性

特性说明
时间复杂度O(n log n)
空间复杂度O(1)(原地排序)
稳定性不稳定
优点
  1. 时间复杂度稳定:始终保证O(n log n)的性能

  2. 内存高效:原地排序,无需额外存储空间

  3. 适合大数据:处理海量数据时不会出现快速排序的最坏情况

  4. 优先级队列基础:堆结构的重要应用场景

缺点
  1. 不稳定排序:相同值元素的相对位置可能改变

  2. 缓存不友好:跳跃式访问内存,可能影响实际性能

  3. 常数项较大:实际运行速度通常略慢于快速排序

(5)算法优化

  1. 非递归实现
    将调整方法改为迭代实现,避免递归调用开销

  2. 多叉堆优化
    使用三叉堆或四叉堆(适用于现代CPU缓存特性)

  3. 并行建堆
    对大规模数据可采用多线程并行调整子树

(6)实例演示

(以数组[12, 11, 13, 5, 6, 7]为例)

1)建堆过程:

初始数组: [12, 11, 13, 5, 6, 7]
转换为完全二叉树:
        12
       /  \
      11   13
     / \   /
    5  6 7

调整非叶子节点(索引2→1→0):
最终最大堆: [13, 11, 12, 5, 6, 7]
对应二叉树:
        13
       /  \
      11   12
     / \   /
    5  6 7

 2)排序阶段

第1次交换:13↔7 → [7, 11, 12, 5, 6, 13]
调整堆:   [12, 11, 7, 5, 6, 13]

第2次交换:12↔6 → [6, 11, 7, 5, 12, 13]
调整堆:   [11, 6, 7, 5, 12, 13]

...(重复过程)...
最终结果: [5, 6, 7, 11, 12, 13]

五.冒泡排序

(1)基本思想

冒泡排序是一种简单的交换排序算法,其核心思想是通过相邻元素的比较和交换,将较大的元素逐步“冒泡”到数组末尾。每一轮遍历都会确定一个当前未排序部分的最大值。

(2)算法步骤

  1. 外层循环:控制排序轮数(共需 n-1 轮,n 为数组长度)

  2. 内层循环:遍历未排序部分,比较相邻元素

  3. 元素交换:若当前元素 > 后一个元素,则交换位置

  4. 优化判断:若某轮无交换发生,提前终止排序

(3)代码实现

public class BubbleSort {
    public static void bubbleSort(int[] arr) {
        if (arr == null || arr.length <= 1) return;

        int n = arr.length;
        boolean swapped; // 优化标志
        
        for (int i = 0; i < n - 1; i++) {
            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[] arr = {64, 34, 25, 12, 22, 11};
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr)); // 输出 [11, 12, 22, 25, 34, 64]
    }
}

(4)算法特性

特性说明
时间复杂度最好O(n),最坏和平均O(n²)
空间复杂度O(1)(原地排序)
稳定性稳定排序
交换次数最多 n(n−1)22n(n−1)​ 次
优点
  1. 实现简单:代码逻辑直观,适合教学和小规模数据

  2. 稳定性:相等元素不会交换,保持原有顺序

  3. 空间效率:无需额外内存空间

  4. 适应性:对部分有序数据效率较高(通过优化标志)

缺点
  1. 效率低下:大规模数据排序速度显著下降

  2. 冗余比较:即便数据已有序仍需多轮遍历(未优化版本)

 (5)算法优化

1. 提前终止(已实现)

  • 通过swapped标志检测是否发生交换

  • 最佳情况时间复杂度优化至O(n)(完全有序数组)

2. 鸡尾酒排序(双向冒泡)

  • 交替进行正向和反向遍历

  • 对包含大量无序小元素的数组更高效

鸡尾酒排序代码实例: 

public static void cocktailSort(int[] arr) {
    int left = 0;
    int right = arr.length - 1;
    boolean swapped;
    
    while (left < right) {
        // 正向遍历
        swapped = false;
        for (int i = left; i < right; i++) {
            if (arr[i] > arr[i+1]) {
                swap(arr, i, i+1);
                swapped = true;
            }
        }
        if (!swapped) break;
        right--;
        
        // 反向遍历
        swapped = false;
        for (int i = right; i > left; i--) {
            if (arr[i] < arr[i-1]) {
                swap(arr, i, i-1);
                swapped = true;
            }
        }
        if (!swapped) break;
        left++;
    }
}

3. 记录最后交换位置

  • 记录每轮最后一次交换的位置,减少无效比较

int lastSwapIndex = 0;
int sortBorder = arr.length - 1;

for (int i = 0; i < arr.length - 1; i++) {
    boolean swapped = false;
    for (int j = 0; j < sortBorder; j++) {
        if (arr[j] > arr[j+1]) {
            swap(arr, j, j+1);
            swapped = true;
            lastSwapIndex = j;
        }
    }
    sortBorder = lastSwapIndex;
    if (!swapped) break;
}

 (6)实例演示

(以数组[5, 2, 4, 6, 1, 3]为例)

初始状态: [5, 2, 4, 6, 1, 3]

第1轮遍历:
2 5 4 6 1 3 → 2 4 5 6 1 3 → 2 4 5 1 6 3 → 2 4 5 1 3 6
确定最大值6归位

第2轮遍历:
2 4 5 1 3 → 2 4 1 5 3 → 2 4 1 3 5
确定次大值5归位

第3轮遍历:
2 4 1 3 → 2 1 4 3 → 2 1 3 4
确定4归位

第4轮遍历:
2 1 3 → 1 2 3
确定3归位(优化:此时已有序,提前结束)

六.快速排序

(1)基本思想

快速排序是一种高效的分治算法,核心思想是通过基准值(pivot)划分数组,将小于基准值的元素放在左侧,大于基准值的元素放在右侧,然后递归处理左右子数组,直到整个数组有序。

(2)算法步骤

  1. 选择基准值(Pivot):从数组中选取一个元素作为基准

  2. 分区操作(Partition):重新排列数组,使小于基准值的元素在左,大于基准值的在右

  3. 递归排序:对左右两个子数组递归执行上述步骤

(3)代码实现

public class QuickSort {
    public static void quickSort(int[] arr) {
        if (arr == null || arr.length <= 1) return;
        sort(arr, 0, arr.length - 1);
    }

    private static void sort(int[] arr, int low, int high) {
        if (low < high) {
            int pivotIndex = partition(arr, low, high);
            sort(arr, low, pivotIndex - 1);  // 递归处理左子数组
            sort(arr, pivotIndex + 1, high); // 递归处理右子数组
        }
    }

    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];  // 选择最后一个元素为基准值
        int i = low - 1;        // 指向比基准值小的最后一个元素

        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, high); // 将基准值放到正确位置
        return i + 1;
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        quickSort(arr);
        System.out.println(Arrays.toString(arr)); // 输出 [1, 5, 7, 8, 9, 10]
    }
}

 (4)算法特性

特性说明
平均时间复杂度O(n log n)
最坏时间复杂度O(n²)(可通过优化避免)
空间复杂度O(log n)(递归调用栈)
稳定性不稳定
最佳适用场景大规模随机数据
优点

1. 高效的平均性能

  • 时间复杂度:平均情况O(n log n),实际应用中效率通常高于其他O(n log n)算法(如归并排序)

  • 比较次数少:相比归并排序,快速排序的比较次数通常更少

2. 内存效率高

  • 原地排序:只需O(log n)的栈空间(递归调用),不需要额外存储空间

  • 缓存友好:顺序访问内存,能有效利用CPU缓存

3. 实现灵活

  • 可通过多种方式选择基准值(pivot)

  • 可优化为三路快排处理重复元素

  • 可与非递归实现结合

缺点

最坏情况性能差

  • 最坏时间复杂度:O(n²)(当输入已排序且基准选择不当时)

  • 解决方案:随机化基准选择或三数取中法

2. 不稳定排序

  • 相同元素的相对位置可能改变

  • 示例:排序[3①, 2, 3②]可能得到[2, 3②, 3①]

3. 递归实现的风险

  • 深度递归可能导致栈溢出

  • 解决方案:尾递归优化或改为迭代实现

(5)算法优化

1. 三数取中法选择基准值

避免最坏情况(已排序数组导致O(n²))

private static int selectPivot(int[] arr, int low, int high) {
    int mid = low + (high - low)/2;
    // 比较low/mid/high三个位置的元素
    if (arr[low] > arr[mid]) swap(arr, low, mid);
    if (arr[low] > arr[high]) swap(arr, low, high);
    if (arr[mid] > arr[high]) swap(arr, mid, high);
    return mid; // 返回中间值索引
}

2. 尾递归优化

减少递归调用栈深度

private static void sortOptimized(int[] arr, int low, int high) {
    while (low < high) {
        int pivotIndex = partition(arr, low, high);
        if (pivotIndex - low < high - pivotIndex) {
            sortOptimized(arr, low, pivotIndex - 1);
            low = pivotIndex + 1;
        } else {
            sortOptimized(arr, pivotIndex + 1, high);
            high = pivotIndex - 1;
        }
    }
}

3. 插入排序混合优化

对小规模子数组(如长度≤15)改用插入排序

private static final int INSERTION_THRESHOLD = 15;

private static void sort(int[] arr, int low, int high) {
    if (high - low <= INSERTION_THRESHOLD) {
        insertionSort(arr, low, high);
        return;
    }
    // ...原快速排序逻辑
}

 (6)实例演示

示例1:基础快速排序流程

输入数组[10, 80, 30, 90, 40, 50, 70]

步骤演示

  1. 选择基准值:70(末尾元素)

  2. 分区过程:

    • 10 < 70 → 保留

    • 80 > 70 → 跳过

    • 30 < 70 → 与80交换 → [10, 30, 80, 90, 40, 50, 70]

    • 90 > 70 → 跳过

    • 40 < 70 → 与80交换 → [10, 30, 40, 90, 80, 50, 70]

    • 50 < 70 → 与90交换 → [10, 30, 40, 50, 80, 90, 70]

  3. 最终交换基准值 → [10, 30, 40, 50, 70, 90, 80]

  4. 递归处理左右子数组

示例2:三数取中法优化

输入数组[1, 2, 3, 4, 5, 6, 7](已排序数组)

优化步骤

  1. 选择low=1, mid=4, high=7

  2. 三数排序后取中值:4

  3. 分区后得到平衡划分,避免O(n²)最坏情况

示例3:三路快排处理重复元素

输入数组[3, 1, 3, 2, 3, 3, 4]

分区过程

初始:lt=0, gt=6, i=1, pivot=3
[3, 1, 3, 2, 3, 3, 4]

步骤1:arr[1]=1 < 3 → 交换lt(0)和i(1)
[1, 3, 3, 2, 3, 3, 4] (lt=1, i=2)

步骤2:arr[2]=3 == 3 → i++
(lt=1, i=3)

步骤3:arr[3]=2 < 3 → 交换lt(1)和i(3)
[1, 2, 3, 3, 3, 3, 4] (lt=2, i=4)

...最终得到:
<3的部分:[1, 2]
=3的部分:[3, 3, 3, 3]
>3的部分:[4]

七.归并排序

(1)基本思想

归并排序(Merge Sort)是一种经典的分治算法,由约翰・冯・诺伊曼在 1945 年提出。它的基本思想是将一个大问题分解为多个小问题,分别解决这些小问题,最后将小问题的解合并起来得到大问题的解。

归并排序采用分治策略,具体分为两个阶段:

  • 分解(Divide):将待排序的数组从中间分成两个子数组,然后递归地对这两个子数组继续进行分解,直到每个子数组只有一个元素(因为单个元素的数组本身就是有序的)。
  • 合并(Merge):将两个有序的子数组合并成一个有序的数组。合并过程中,比较两个子数组的元素,将较小的元素依次放入新的数组中,直到两个子数组的所有元素都被放入新数组。

(2)算法步骤

  1. 分解阶段
    • 找到数组的中间位置,将数组分成左右两部分。
    • 递归地对左右两部分进行分解,直到每个子数组只有一个元素。
  2. 合并阶段
    • 创建一个临时数组,用于存放合并后的结果。
    • 比较左右两个子数组的元素,将较小的元素依次放入临时数组中。
    • 将临时数组中的元素复制回原数组。

(3)代码实现

 private static void mergechild(int[] array, int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = (left + right) / 2;
        mergechild(array, left, mid);
        mergechild(array, mid + 1, right);
        merge(array,left,mid,right);

    }
//定义方法:合并两个子数组
    private static void merge(int[] array, int left, int mid, int right) {
        int[] tempArray = new int[right - left + 1];
        int k = 0;//临时数组tempArray的下标
        int start1 = left;
        int start2 = mid + 1;
        int end1 = mid;
        int end2 = right;
        //两个子数组中都有数据
        while (start1 <= end1 && start2 <= end2) {
            if (array[start1] > array[start2]) {
                tempArray[k] = array[start2];
                k++;
                start2++;
            } else if (array[start1] <= array[start2]) {
                tempArray[k] = array[start1];
                k++;
                start1++;
            }
        }
        //1个子数组中没有数据了,跳出while循环
        while (start1 <= end1) {
            tempArray[k] = array[start1];
            k++;
            start1++;
        }
        while (start2 <= end2) {
            tempArray[k] = array[start2];
            k++;
            start2++;
        }
        //将tempArray中的元素复制回原数组
        for (int i = 0; i < tempArray.length; i++) {
        array[i+left]=tempArray[i];
        }
    }

(4)算法特性

特性说明
平均时间复杂度 O(nlogn)
空间复杂度 O(n)
稳定性稳定
最佳适用场景处理大规模数据
优点
  • 稳定性:归并排序是一种稳定的排序算法,即相等元素的相对顺序在排序前后不会改变。
  • 时间复杂度稳定:无论输入数据的分布如何,归并排序的时间复杂度都是 O(nlogn)。
缺点
  • 空间复杂度较高:需要额外的 O(n) 空间来存储临时数组。
  • 常数因子较大:由于需要频繁地进行数组的复制和合并操作,归并排序的常数因子相对较大,在处理小规模数据时效率不如一些简单的排序算法(如插入排序)。

(5)算法优化

1. 小规模数据使用插入排序

对于小规模的数据,插入排序的常数时间开销相对较小,性能可能比归并排序更好。因此当子数组规模较小时,可以采用插入排序来处理,减少递归调用带来的开销。

2. 减少不必要的数组复制

在归并过程中,频繁地创建和复制临时数组会带来一定的性能开销。可以通过交替使用原数组和临时数组来减少这种开销。

3. 提前终止合并过程

在合并两个有序子数组时,如果发现其中一个子数组的所有元素都已经小于另一个子数组的所有元素,就可以提前终止合并过程。

(6)实例演示

假设我们有一个待排序的数组 [38, 27, 43, 3, 9, 82, 10]

分解阶段
  • 首先将原数组从中间分成两部分:[38, 27, 43, 3] 和 [9, 82, 10]
  • 对这两个子数组继续分解,[38, 27, 43, 3] 分成 [38, 27] 和 [43, 3][9, 82, 10] 分成 [9, 82] 和 [10]
  • 继续分解,[38, 27] 分成 [38] 和 [27][43, 3] 分成 [43] 和 [3][9, 82] 分成 [9] 和 [82]。此时所有子数组都只有一个元素,分解结束。
合并阶段
  • 合并 [38] 和 [27] 得到 [27, 38];合并 [43] 和 [3] 得到 [3, 43];合并 [9] 和 [82] 得到 [9, 82]
  • 合并 [27, 38] 和 [3, 43] 得到 [3, 27, 38, 43][9, 82] 和 [10] 合并得到 [9, 10, 82]
  • 最后合并 [3, 27, 38, 43] 和 [9, 10, 82] 得到最终的有序数组 [3, 9, 10, 27, 38, 43, 82]

八.常见排序表

排序算法时间复杂度(最好)时间复杂度(平均)时间复杂度(最坏)空间复杂度稳定性备注
冒泡排序O(n)O(n²)O(n²)O(1)稳定简单但效率低
选择排序O(n²)O(n²)O(n²)O(1)不稳定交换次数最少
插入排序O(n)O(n²)O(n²)O(1)稳定对小规模数据高效
快速排序O(n log n)O(n log n)O(n²)O(log n)不稳定实际应用中最快(优化后)
归并排序O(n log n)O(n log n)O(n log n)O(n)稳定稳定且适合外部排序
堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定适合大规模数据
希尔排序O(n log n)O(n log² n)O(n²)O(1)不稳定插入排序的改进版

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2329157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python爬虫:小程序逆向实战教程

根据我之前发表的文章&#xff0c;我们进行延伸实战https://blog.csdn.net/weixin_64809364/article/details/146981598?spm1001.2014.3001.5501 1. 想要爬取什么小程序&#xff0c;我们进行搜索 2. 找到我们vx小程序的文件地址&#xff0c;我们就可以进行破解 破解步骤强看…

day 8 TIM定时器

一、STM32 定时器概述 1. 定时器的概述定时器的基本功能&#xff0c;但是 STM32 的定时器除了具有定时功能之外&#xff0c;也具有定时器中断功能&#xff0c;还具有输入捕获&#xff08;检测外部信号&#xff09;以及输出比较功能&#xff08;输出不同的脉冲&#xff09;&…

全星 研发项目管理APQP 软件:驱动汽车及制造业研发升级的数字化引擎

全星 APQP 软件&#xff1a;驱动汽车及制造业研发升级的数字化引擎 在汽车及制造业竞争白热化的当下&#xff0c;如何高效推进研发项目&#xff0c;同时确保严格合规&#xff0c;成为企业亟待解决的难题。 全星研发项目管理 APQP 软件系统&#xff0c;凭借卓越的功能与显著优势…

【VUE】RuoYi-Vue3项目结构的分析

【VUE】RuoYi-Vue3项目结构的分析 1. 项目地址2. RuoYi-Vue3项目结构2.1 整体结构2.2 package.json2.2.1 &#x1f9fe; 基本信息2.2.2 &#x1f527; 脚本命令&#xff08;scripts&#xff09;2.2.3 &#x1f30d; 仓库信息2.2.4 &#x1f4e6; 项目依赖&#xff08;dependenc…

智能体和RPA都需要程序思维,如何使用影刀的变量?

欢迎来到涛涛聊AI&#xff0c; 不管AI还是RPA&#xff0c;都需要用到编程思想才能完成批量工作。今天研究了下影刀的变量。 变量类型 根据变量值选择相应的类型&#xff0c;可选择任意一种影刀所支持的数据类型 变量值 指定变量中保存的值&#xff0c;会根据不同的类型设置…

论文笔记(七十五)Auto-Encoding Variational Bayes

Auto-Encoding Variational Bayes 文章概括摘要1 引言2 方法2.1 问题场景2.2 变分下界2.3 SGVB估计器与AEVB算法2.4 重参数化技巧 3 示例&#xff1a;变分自编码器&#xff08;Variational Auto-Encoder&#xff09;4 相关工作5 实验6 结论7 未来工作 文章概括 引用&#xff1…

Sentinel[超详细讲解]-7 -之 -熔断降级[异常比例阈值]

&#x1f4d6; 主要讲解熔断降级之 --- 异常比例阈值 &#x1f680; 1️⃣ 背景 Sentinel 以流量作为切入点&#xff0c;提供了很多的丰富的功能&#xff0c;例如&#x1f917;&#xff1a; 流量控制&#xff0c;熔断降级等&#xff0c;它能够有效的适用各个复杂的业务场景&am…

C++11观察者模式示例

该示例代码采用C11标准&#xff0c;解决以下问题&#xff1a; 消除了类继承的强耦合方式&#xff1b;通知接口使用可变参数模板&#xff0c;支持任意参数&#xff1b; 示例代码 .h文件如下&#xff1a; #include <functional> #include <string> #include <…

win10 笔记本电脑安装 pytorch+cuda+gpu 大模型开发环境过程记录

win10 笔记本电脑安装 pytorchcudagpu 大模型开发环境过程记录 文章部分内容参考 deepseek。 以下使用命令行工具 mingw64。 安装 Anaconda 安装位置&#xff1a; /c/DEVPACK/Anaconda3 然后安装 Python 3.10.16 &#xff08;略&#xff09; $ conda create -n pytorch_…

Layout Inspector平替跨平台布局分析器のAppium Inspector

引言 因为我有一个api为26的设备&#xff0c;因为 Layout Inspector 无法在 API 26 以下设备上使用&#xff0c;并且现在AS的 Hierarchy Viewer 和Android Device Monitor 均已经在SDK中剔除&#xff0c;故想再搜一个pc版的布局查看器&#xff0c;发现Appium Inspector学习成本…

常见NLP指标PPL,F1,Rouge-L,Accuracy (CLS),Accuracy (EM)总结

常见NLP指标PPL&#xff0c;F1&#xff0c;Rouge-L总结 1.PPL 2.F1 3.Rouge-L 4.Accuracy (CLS) 5.Accuracy (EM)

Redis数据结构之ZSet

目录 1.概述2.常见操作2.1 ZADD2.2 ZRANGE2.3 ZREVRANGE2.4 ZRANGEBYSCORE2.5 ZSCORE2.6 ZCARD2.6 ZREM2.7 ZINCRBY2.8 ZCOUNT2.9 ZMPOP2.10 ZRANK2.11 ZREVRANK 3.总结 1.概述 ZSet和Set一样也是String类型元素的集合&#xff0c;且不允许重复的成员&#xff0c;不同的是ZSet…

磁盘分析工具合集:告别C盘焦虑!

今天李师傅带大家盘点五款硬盘空间分析利器&#xff0c;帮你精准定位那些"吃空间"的元凶&#xff0c;让C盘告别臃肿烦恼&#xff01; 一、WizTree 这款NTFS磁盘的"透视眼"堪称效率典范。它通过直接读取硬盘主文件表(MFT)实现秒级扫描&#xff0c;1TB机械…

20250405在荣品的PRO-RK3566开发板使用Rockchip原厂的buildroot系统来适配gmac1

【暂时还没有解决让PRO-RK3566的eth0/gmac1开机就启动】 PRO-RK3566作为iperf服务器&#xff1a; rootrk3566-buildroot:/# ifconfig rootrk3566-buildroot:/# ifconfig -a rootrk3566-buildroot:/# ifconfig eth0 up rootrk3566-buildroot:/# ifconfig rootrk3566-buildroot:/…

每日一题(小白)模拟娱乐篇14

直接理解题意&#xff0c;一分钟扩散一次&#xff0c;那么2020分钟也就是需要循环2020次&#xff0c;然后加入扩散后的条件&#xff0c;每一个次扩散使方格子的总量1&#xff08;只要有一个点扩散就无需看其他的点&#xff09;&#xff0c;若干次循环过后总数之和即所有黑色格子…

使用 Python 爬取并打印双色球近期 5 场开奖数据

使用 Python 爬取并打印双色球近期 5 场开奖数据 前期准备安装所需库 完整代码代码解析 1. 导入必要的库2. 定义函数 get_recent_five_ssq 3. 设置请求的 URL 和 Headers 4. 发送请求并处理响应5. 解析 HTML 内容6. 提取并打印数据7. 错误处理 首先看下运行的效果图&#xff1a…

再见VS Code!Google IDE 正颠覆传统开发体验

云端开发的革命&#xff1a;Google Project IDX 如何颠覆传统开发体验 在软件开发领域&#xff0c;Google 最新推出的 Project IDX 绝非仅仅是另一个“基于浏览器的 VS Code”——它是一次真正的范式转变。与 VS Code、Cursor 等传统工具不同&#xff0c;IDX 是一个完全云原生的…

AI+自动化测试:如何让测试编写效率提升10倍?

文章目录 摘要传统自动化测试的痛点编写测试用例太费时间测试覆盖率难以保证UI 测试维护成本高 AI 如何优化自动化测试&#xff1f;AI 生成单元测试&#xff1a;减少重复工作&#xff0c;提高覆盖率传统方法 VS AI 方法 使用 AI 生成 Python 单元测试自动补全边界情况传统方法 …

01-STM32(介绍、工具准备、新建工程)p1-4

文章目录 工具准备和介绍硬件设备stm32简介和arm简介stm32简介STM32命名规则STM32选型STM32F103C8T6最小系统板引脚定义STM32启动配置STM32最小系统电路ARM简介 软件安装注册器件支持包安装ST-LINK驱动安装USB转串口驱动 新建工程创建stm32工程STM32工程编译和下载型号分类及缩…

Win10定时任务计划无法显示要执行的EXE任务程序界面,问题解决办法

用C#开发的一款WINFORM程序&#xff0c;在电脑测试一切顺利&#xff0c;运行结果正确。但用电脑的定时任务执行时&#xff0c;程序界面不显示&#xff0c;重启电脑、各种试都不行&#xff0c;最终问题解决。 解决办法&#xff1a; 要选“只在用户登陆时运行”&#xff0c;才能执…