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

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

本文主要是对排序算法的巩固和学习,以及对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.十大经典排序算法(动图演示)

相关推荐
小龙报6 分钟前
《算法通关指南数据结构和算法篇(2)--- 链表专题》
c语言·数据结构·c++·算法·链表·学习方法·visual studio
shark_chili15 分钟前
浅谈Java并发编程中断的哲学
后端
艾莉丝努力练剑27 分钟前
【优选算法必刷100题】第031~32题(前缀和算法):连续数组、矩阵区域和
大数据·人工智能·线性代数·算法·矩阵·二维前缀和
醉颜凉28 分钟前
环形房屋如何 “安全劫舍”?动态规划解题逻辑与技巧
c语言·算法·动态规划
大雨淅淅31 分钟前
一文搞懂动态规划:从入门到精通
算法·动态规划
不去幼儿园34 分钟前
【启发式算法】灰狼优化算法(Grey Wolf Optimizer, GWO)详细介绍(Python)
人工智能·python·算法·机器学习·启发式算法
培风图南以星河揽胜34 分钟前
Java实习模拟面试|离散数学|概率论|金融英语|数据库实战|职业规划|期末冲刺|今日本科计科要闻速递:技术分享与学习指南
java·面试·概率论
随意起个昵称35 分钟前
【二分】洛谷P2920,P2985做题小记
c++·算法
没书读了41 分钟前
计算机组成原理-考前记忆清单
线性代数·算法
Billow_lamb1 小时前
Spring Boot2.x.x 全局错误处理
java·spring boot·后端