算法技巧-各种排序算法(一)

排序算法是计算机科学中常见的算法类型,用于将一组数据按照特定的顺序进行排列。

本文主要是对排序算法的巩固和学习,以及对JDK中相关排序源码解读

排序算法分类

可以飞分为比较类排序和非比较类排序

  • 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  • 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

算法复杂度

排序 时间复杂度(平均) 时间复杂度(最好) 时间复杂度(最坏) 空间复杂度 稳定与否
插入排序 O(N2) O(N) O(N2) O(1) 稳定
希尔排序 O(NlogN) O(N) O(N2) O(1) 不稳定
选择排序 O(N2) O(N2) O(N2) O(1) 不稳定
堆排序 O(NlogN) O(NlogN) O(NlogN) O(1) 不稳定
冒泡排序 O(N2) O(N2) O(N) O(1) 稳定
快速排序 O(NlogN) O(NlogN) O(N2) O(logN) 不稳定
归并排序 O(NlogN) O(NlogN) O(NlogN) O(N) 稳定
计数排序 O(N+K) O(N+K) O(N+K) O(N+K) 稳定
桶排序 O(N+K) O(N2) O(N) O(N+K) 稳定
基数排序 O(N*K) O(N*K) O(N*K) O(N+K) 稳定

稳定与不稳定解释:

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。

经典排序算法

插入排序(Insertion Sort)

插入排序(Insertion Sort)是一种简单直观的排序算法,它的思想是将待排序的元素逐个插入到已排序序列中的适当位置,从而构建有序序列。插入排序的步骤如下:

  1. 初始时,将第一个元素视为已排序序列,将其作为参考点。
  2. 从第二个元素开始,将当前元素与已排序序列中的元素进行比较,找到插入的位置。
  3. 将当前元素插入到合适的位置,同时将已排序序列中的元素向后移动。
  4. 重复步骤2和3,直到所有元素都被插入到正确的位置,形成有序序列。
java 复制代码
public static void insertionSort(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; i++) {
            int key = arr[i];
            int j = i - 1;
            
            // 将比当前元素大的元素向后移动
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            
            // 插入当前元素到合适的位置
            arr[j + 1] = key;
        }
    }

动图演示:

希尔排序(Shell Sort)

希尔排序(Shell Sort),也称为缩小增量排序,是插入排序的一种改进版算法。它通过将待排序的元素分组,每组进行插入排序,逐渐缩小增量(间隔),最终完成排序。希尔排序的步骤如下:

  1. 选择一个增量序列(间隔序列),通常选择初始增量为数组长度的一半,然后逐步缩小增量直至为1。
  2. 按照增量将数组分成多个子序列,对每个子序列进行插入排序。
  3. 逐步减小增量,重复步骤2,直到增量为1时,进行最后一次插入排序。
java 复制代码
public static void shellSort(int[] arr) {
        int n = arr.length;
        
        // 初始增量为数组长度的一半,逐步缩小增量
        for (int gap = n / 2; gap > 0; gap /= 2) {
            // 对每个子序列进行插入排序
            for (int i = gap; i < n; i++) {
                int temp = arr[i];
                int j = i;
                
                // 对当前子序列进行插入排序
                while (j >= gap && arr[j - gap] > temp) {
                    arr[j] = arr[j - gap];
                    j -= gap;
                }
                
                arr[j] = temp;
            }
        }

动图演示如下:

选择排序(Selection Sort)

选择排序(Selection Sort)是一种简单直观的排序算法,它的思想是每次从未排序的元素中选择最小(或最大)的元素,将其放置在已排序序列的末尾(或开头),从而逐步构建有序序列。选择排序的步骤如下:

  1. 初始时,将整个数组视为未排序序列。
  2. 在未排序序列中,找到最小(或最大)的元素,将其与未排序序列的第一个元素进行交换。
  3. 将未排序序列的起始位置向后移动一个位置,即将已排序序列的末尾扩展一个元素。
  4. 重复步骤2和3,直到未排序序列为空,即所有元素都已排序。
java 复制代码
public static void selectionSort(int[] arr) {
        int n = arr.length;
        
        for (int i = 0; i < n - 1; i++) {
            int minIndex = i;
            
            // 找到未排序序列中的最小元素的索引
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            
            // 将最小元素与未排序序列的第一个元素进行交换
            int temp = arr[minIndex];
            arr[minIndex] = arr[i];
            arr[i] = temp;
        }
    }

动图演示如下:

堆排序

堆排序(Heap Sort)是一种高效的排序算法,它利用堆这种数据结构进行排序。堆是一个完全二叉树,具有以下性质:对于每个节点i,其父节点的值总是大于等于(或小于等于)其子节点的值。堆排序的步骤如下:

  1. 构建最大堆(或最小堆):将待排序的数组看作是一个完全二叉树,通过自下而上的方式,从最后一个非叶子节点开始,对每个节点进行堆化操作,使得每个节点满足堆的性质。
  2. 交换堆顶元素与最后一个元素:将堆顶元素与堆中最后一个元素进行交换,即将最大(或最小)元素放置在已排序部分的末尾。
  3. 重新调整堆:对交换后的堆顶元素进行堆化操作,使得堆顶元素重新满足堆的性质。
  4. 重复步骤2和3,直到堆中只剩下一个元素,即完成排序。
java 复制代码
public static void heapSort(int[] arr) {
        int n = arr.length;
        
        // 构建最大堆
        buildMaxHeap(arr);
        
        // 交换堆顶元素与最后一个元素,并重新调整堆
        for (int i = n - 1; i > 0; i--) {
            swap(arr, 0, i);
            heapify(arr, 0, i);
        }
    }
    
    // 构建最大堆
    private static void buildMaxHeap(int[] arr) {
        int n = arr.length;
        
        // 从最后一个非叶子节点开始,自下而上进行堆化操作
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, i, n);
        }
    }
    
    // 堆化操作
    private static void heapify(int[] arr, int root, int size) {
        int largest = root;
        int left = 2 * root + 1;
        int right = 2 * root + 2;
        
        // 找出根节点、左子节点和右子节点中的最大值
        if (left < size && arr[left] > arr[largest]) {
            largest = left;
        }
        
        if (right < size && arr[right] > arr[largest]) {
            largest = right;
        }
        
        // 如果最大值不是根节点,则交换并继续进行堆化操作
        if (largest != root) {
            swap(arr, root, largest);
            heapify(arr, largest, size);
        }
    }
    
    // 交换数组中两个元素的位置
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

动图演示如下:

未完待续

参考

1.常用的排序算法和时间复杂度

2.10种排序算法的对比分析

3.十大经典排序算法(动图演示)

相关推荐
是小胡嘛12 分钟前
数据结构之旅:红黑树如何驱动 Set 和 Map
数据结构·算法
m0_7482550215 分钟前
前端常用算法集合
前端·算法
呆呆的猫38 分钟前
【LeetCode】227、基本计算器 II
算法·leetcode·职场和发展
Tisfy40 分钟前
LeetCode 1705.吃苹果的最大数目:贪心(优先队列) - 清晰题解
算法·leetcode·优先队列·贪心·
余额不足121381 小时前
C语言基础十六:枚举、c语言中文件的读写操作
linux·c语言·算法
测试老哥1 小时前
外包干了两年,技术退步明显。。。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
追逐时光者2 小时前
免费、简单、直观的数据库设计工具和 SQL 生成器
后端·mysql
初晴~2 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581362 小时前
InnoDB 的页分裂和页合并
数据库·后端