一篇文章让你彻底了解Java算法「十大经典排序算法」

news2024/11/19 1:39:51

✍️作者简介:码农小北(专注于Android、Web、TCP/IP等技术方向)
🐳博客主页: 开源中国、稀土掘金、51cto博客、博客园、知乎、简书、慕课网、CSDN
🔔如果文章对您有一定的帮助请👉关注✨、点赞👍、收藏📂、评论💬。
🔥如需转载请参考【转载须知】

文章目录

  • 简介
    • 算法分类
    • 十四种算法简介
    • 算法复杂度
  • 算法演示
    • 冒泡排序 (Bubble Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
    • 选择排序 (Selection Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
    • 插入排序 (Insertion Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
    • 希尔排序 (Shell Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
    • 归并排序 (Merge Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
    • 快速排序 (Quick Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
    • 堆排序 (Heap Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
    • 计数排序 (Counting Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
    • 桶排序 (Bucket Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
    • 基数排序 (Radix Sort)
      • 简介
      • 算法步骤
      • 图解算法
      • 代码示例
      • 算法分析
      • 算法源码总结需要的可以直接下载

简介

排序算法是计算机科学中至关重要的一部分,它通过一系列操作将一组记录按照某个或某些关键字的大小进行递增或递减排列。在数据处理领域,排序算法的选择直接影响着程序的效率和性能。为了得到符合实际需求的优秀算法,我们需要经过深入的推理和分析,考虑到数据的各种限制和规范。因此,深入探讨十大经典排序算法的原理及实现。通过这个过程,我希望为大家提供更加准确和全面的排序算法知识,助力在实际编程中更灵活地选择适合场景的排序方法,提升程序的效率和性能。

排序算法根据其基本思想可以分为比较类排序和非比较类排序。比较类排序通过比较元素的大小来决定排序顺序,而非比较类排序则不依赖元素间的比较。不同排序算法在处理不同规模和类型的数据时表现出不同的优势。接下来,我们将详细介绍这十大经典排序算法。

算法分类

比较类排序

在比较类排序中,算法通过比较元素之间的大小关系来确定它们的相对次序。这类算法主要关注元素之间的比较操作,以决定它们在排序后的顺序。

非比较类排序

非比较类排序则不直接通过比较元素的大小关系来确定它们的顺序,而是利用其他方法完成排序。

区别和应用场景

  • 比较类排序更适用于元素之间有大小关系的情况,主要通过比较操作来实现排序,适用于各种数据类型。

  • 非比较类排序更适用于特定范围或结构的数据,利用其他方法实现排序,适用于整数、字符串等类型。
    在这里插入图片描述

十四种算法简介

序号简称英文简介
1二分查找法Binary Search二分查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
2冒泡排序算法Bubble Sort重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序错误就把它们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
3插入排序算法Insertion Sort通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应的位置并插入。
4快速排序算法Quick Sort对冒泡算法的一种改进。是指通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。整个排序过程可以递归进行,以此达到整个数据变成有序序列。
5希尔排序算法Shell’s Sort希尔排序是插入排序的一种又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法
6归并排序算法Merge Sort归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
7桶排序算法Bucket Sort桶排序也叫箱排序,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到 O(n log n) 下限的影响。
8基数排序算法Radix Sort基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
9剪枝算法Pruning Algorithms在搜索算法中优化中,剪枝,就是通过某种判断,避免一些不必要的遍历过程,形象的说,就是剪去了搜索树中的某些“枝条”,故称剪枝。应用剪枝优化的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。
10回溯算法Backtracking回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
11最短路径算法Shortest Path从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径叫做最短路径。解决最短路的问题有以下算法,Dijkstra 算法,Bellman-Ford 算法,Floyd 算法和 SPFA算法等。
12最大子数组算法Maximum Subarray最大子数组问题是要在一个数组中找到一个连续的子数组,使得该子数组的元素之和最大。该问题有多种解法,其中一种常见的解法是使用分治法。
13最长公共子序算法Longest Common Subsequence最长公共子序列是指在两个序列中均存在的、且相对顺序相同的元素组成的子序列。解决最长公共子序列的问题通常使用动态
14最小生成树算法Minimum Spanning Tree最小生成树是一个无向连通图中生成树中边权值之和最小的生成树。解决最小生成树的问题有多种算法,其中常见的包括 Prim 算法和 Kruskal 算法,它们通过选择图中的边来构建最小生成树。这些算法在网络设计、城市规划等领域有广泛应用。

算法复杂度

在实际应用中,不同排序算法的性能表现受到数据规模、数据分布等多方面因素的影响。我们将通过具体的示例和图表,展示这些排序算法在时间复杂度和空间复杂度上的对比,以便读者更好地选择适合自己场景的排序算法。

算法复杂度通常通过时间复杂度和空间复杂度来表示。以下是常见排序算法的时间复杂度和空间复杂度的表格:

排序算法平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
冒泡排序 (Bubble Sort)O(n^2)O(n^2)O(n)O(1)稳定
选择排序 (Selection Sort)O(n^2)O(n^2)O(n^2)O(1)不稳定
插入排序 (Insertion Sort)O(n^2)O(n^2)O(n)O(1)稳定
希尔排序 (Shell Sort)O(n log^2 n)O(n log^2 n)O(n log n)O(1)不稳定
快速排序 (Quick Sort)O(n log n)O(n^2)O(n log n)O(log n)不稳定
归并排序 (Merge Sort)O(n log n)O(n log n)O(n log n)O(n)稳定
堆排序 (Heap Sort)O(n log n)O(n log n)O(n log n)O(1)不稳定
计数排序 (Counting Sort)O(n + k)O(n + k)O(n + k)O(k)稳定
桶排序 (Bucket Sort)O(n^2)O(n^2)O(n + k)O(n + k)稳定
基数排序 (Radix Sort)O(nk)O(nk)O(nk)O(n + k)稳定

注解:

  • 时间复杂度通常用大O表示法表示,表示算法运行时间相对于输入大小的增长率。
  • 空间复杂度表示算法在运行过程中相对于输入大小所需的额外空间。
  • "稳定"表示如果有两个相等的元素在排序前后的相对顺序保持不变。
  • "不稳定"表示相等的元素在排序前后的相对顺序可能改变。

以上表格中的复杂度是对于典型情况而言的。实际应用中,算法性能还可能受到具体实现、硬件条件等因素的影响。

算法演示

冒泡排序 (Bubble Sort)

简介

冒泡排序是一种简单直观的排序算法,它多次遍历待排序的序列,每次遍历都比较相邻的两个元素,如果顺序不正确就交换它们。通过多次遍历,使得最大的元素逐渐移动到最后,实现整个序列的排序。冒泡排序是一种简单但效率较低的排序算法,通常不适用于大规模数据集。

算法步骤

  1. 从序列的第一个元素开始,对相邻的两个元素进行比较。
  2. 如果顺序不正确,交换这两个元素的位置。
  3. 遍历完整个序列后,最大的元素会沉到序列的末尾。
  4. 重复步骤1-3,每次遍历都将未排序部分的最大元素沉到序列的末尾。
  5. 重复以上步骤,直到整个序列有序。

图解算法

在这里插入图片描述

代码示例

    /**
     * 冒泡排序
     *
     * @param arr 待排序数组
     */
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        // 外层循环控制比较的轮数
        for (int i = 0; i < n - 1; i++) {
            // 内层循环控制每一轮比较的次数
            for (int j = 0; j < n - i - 1; j++) {
                // 如果当前元素大于下一个元素,则交换它们的位置
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }

算法分析

  • 稳定性: 冒泡排序是一种稳定的排序算法,相等元素的相对位置在排序前后不会改变。
  • 时间复杂度: 最佳:O(n), 最差:O(n^2), 平均:O(n^2)
  • 空间复杂度: O(1)
  • 排序方式: 冒泡排序是一种原地排序算法。

选择排序 (Selection Sort)

简介

选择排序是一种简单直观的排序算法,它将待排序的序列分为已排序部分和未排序部分,每次从未排序部分选择最小(或最大)的元素,放到已排序部分的末尾。通过多次选择最小(或最大)元素,完成整个序列的排序。虽然选择排序不如一些高级排序算法的性能优越,但由于其简单性,在某些场景下仍然是一个有效的选择。

算法步骤

  1. 从未排序部分选择最小(或最大)的元素。
  2. 将选中的元素与未排序部分的第一个元素交换位置,使其成为已排序部分的末尾。
  3. 更新分界线,将已排序部分扩大一个元素,未排序部分缩小一个元素。
  4. 重复步骤1-3,直到未排序部分为空。
  5. 完成排序。

图解算法

在这里插入图片描述

代码示例

/**
     * 选择排序
     *
     * @param arr 待排序数组
     */
    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;
        }
    }

算法分析

  • 稳定性: 选择排序是一种不稳定的排序算法,因为相等元素可能在排序过程中改变相对位置。
  • 时间复杂度: 最佳:O(n^2), 最差:O(n^2), 平均:O(n^2)
  • 空间复杂度: O(1)
  • 排序方式: 选择排序是一种原地排序算法。

插入排序 (Insertion Sort)

简介

插入排序是一种简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应的位置并插入。插入排序是稳定的排序算法,适用于小型数据集。

算法步骤

  1. 从第一个元素开始,该元素可以认为已经被排序。
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描。
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置。
  4. 重复步骤3,直到找到已排序的元素小于或等于新元素的位置。
  5. 将新元素插入到该位置后。
  6. 重复步骤2~5,直到整个数组排序完成。

图解算法

在这里插入图片描述

代码示例

public class InsertionSort {
    public static void main(String[] args) {
        int[] array = {12, 11, 13, 5, 6};

        System.out.println("原始数组:");
        printArray(array);

        // 应用插入排序
        insertionSort(array);

        System.out.println("\n排序后的数组:");
        printArray(array);
    }

    /**
     * 插入排序
     *
     * @param arr 待排序数组
     */
    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;

            // 将大于 key 的元素都向后移动
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j = j - 1;
            }
            
            // 插入 key 到正确的位置
            arr[j + 1] = key;
        }
    }

    /**
     * 打印数组元素
     *
     * @param arr 待打印数组
     */
    public static void printArray(int[] arr) {
        for (int value : arr) {
            System.out.print(value + " ");
        }
        System.out.println();
    }
}

算法分析

  • 稳定性: 稳定
  • 时间复杂度: 最佳:O(n),最差:O(n2),平均:O(n2)
  • 空间复杂度: O(1)

希尔排序 (Shell Sort)

简介

希尔排序是一种插入排序的改进版本,它通过比较一定间隔(增量)内的元素,并逐步减小这个间隔,最终完成整个序列的排序。希尔排序的核心思想是将相隔较远的元素进行比较和交换,以得到局部较为有序的序列,最后再进行插入排序。希尔排序在大规模数据集上比插入排序更加高效。

算法步骤

  1. 选择一个增量序列(一般是按照 Knuth 提出的序列:1, 4, 13, 40, …)。
  2. 根据增量序列,将序列分割成若干子序列,对每个子序列进行插入排序。
  3. 逐步减小增量,重复步骤2,直到增量为1,此时进行最后一次插入排序。
  4. 完成排序。

图解算法

在这里插入图片描述

代码示例

/**
     * 希尔排序
     *
     * @param arr 待排序数组
     */
    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;
                // 在当前步长范围内,进行插入排序
                for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                    arr[j] = arr[j - gap];
                }
                arr[j] = temp;
            }
        }
    }

算法分析

  • 稳定性: 希尔排序是一种不稳定的排序算法,相等元素可能在排序过程中改变相对位置。
  • 时间复杂度: 最佳:O(n log n), 最差:O(n^2), 平均:取决于增量序列的选择
  • 空间复杂度: O(1)
  • 排序方式: 希尔排序是一种原地排序算法。

归并排序 (Merge Sort)

简介

归并排序是一种基于分治思想的排序算法,它将待排序的序列分成两个子序列,对每个子序列进行递归排序,然后将已排序的子序列合并成一个有序序列。归并排序的关键在于合并操作,它保证了每个子序列在合并时都是有序的。归并排序具有稳定性和高效性,在大规模数据集上表现出色。

算法步骤

  1. 将待排序序列分成两个子序列,分别进行递归排序。
  2. 合并已排序的子序列,得到新的有序序列。
  3. 重复步骤1-2,直到整个序列有序。

图解算法

在这里插入图片描述

代码示例

/**
     * 归并排序
     *
     * @param arr   待排序数组
     * @param left  左边界索引
     * @param right 右边界索引
     */
    public static void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            // 找到中间索引
            int mid = left + (right - left) / 2;
            // 对左半部分进行归并排序
            mergeSort(arr, left, mid);
            // 对右半部分进行归并排序
            mergeSort(arr, mid + 1, right);
            // 合并左右两部分
            merge(arr, left, mid, right);
        }
    }

    /**
     * 合并两个有序数组
     *
     * @param arr   待合并数组
     * @param left  左边界索引
     * @param mid   中间索引
     * @param right 右边界索引
     */
    public static void merge(int[] arr, int left, int mid, int right) {
        int n1 = mid - left + 1;
        int n2 = right - mid;

        // 创建临时数组
        int[] leftArray = new int[n1];
        int[] rightArray = new int[n2];

        // 将数据复制到临时数组
        System.arraycopy(arr, left, leftArray, 0, n1);
        System.arraycopy(arr, mid + 1, rightArray, 0, n2);

        // 合并临时数组
        int i = 0, j = 0, k = left;
        while (i < n1 && j < n2) {
            if (leftArray[i] <= rightArray[j]) {
                arr[k++] = leftArray[i++];
            } else {
                arr[k++] = rightArray[j++];
            }
        }

        // 将左半部分剩余元素复制到原数组
        while (i < n1) {
            arr[k++] = leftArray[i++];
        }

        // 将右半部分剩余元素复制到原数组
        while (j < n2) {
            arr[k++] = rightArray[j++];
        }
    }

算法分析

  • 稳定性: 归并排序是一种稳定的排序算法,相等元素的相对位置在排序前后不会改变。
  • 时间复杂度: 最佳:O(n log n), 最差:O(n log n), 平均:O(n log n)
  • 空间复杂度: O(n)
  • 排序方式: 归并排序是一种非原地排序算法。

快速排序 (Quick Sort)

简介

快速排序是一种基于分治思想的排序算法,它选择一个元素作为基准值,将序列分为两个子序列,小于基准值的元素放在基准值的左边,大于基准值的元素放在右边。然后对左右子序列分别进行递归排序。快速排序是一种高效的排序算法,在大规模数据集上表现出色。

算法步骤

  1. 选择一个基准值。
  2. 将序列分为两个子序列,小于基准值的元素放在左边,大于基准值的元素放在右边。
  3. 对左右子序列分别进行递归排序。
  4. 完成排序。

图解算法

在这里插入图片描述

代码示例

/**
 * 快速排序
 * @param arr 待排序数组
 * @param low 左边界
 * @param high 右边界
 */
public static void quickSort(int[] arr, int low, int high) {
    if (low < high) {
        // 划分数组,获取基准值的位置
        int pivotIndex = partition(arr, low, high);

        // 对左子数组进行递归排序
        quickSort(arr, low, pivotIndex - 1);

        // 对右子数组进行递归排序
        quickSort(arr, pivotIndex + 1, high);
    }
}

/**
 * 划分数组,获取基准值的位置
 * @param arr 待排序数组
 * @param low 左边界
 * @param high 右边界
 * @return 基准值的位置
 */
public static int partition(int[] arr, int low, int high) {
    // 选择最右边的元素作为基准值
    int pivot = arr[high];

    // i 表示小于基准值的元素的最右位置
    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;
}

/**
 * 交换数组中两个元素的位置
 * @param arr 待排序数组
 * @param i 第一个元素的索引
 * @param j 第二个元素的索引
 */
public static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

算法分析

  • 稳定性: 快速排序是一种不稳定的排序算法,相等元素可能在排序过程中改变相对位置。
  • 时间复杂度: 最佳:O(n log n), 最差:O(n^2), 平均:O(n log n)
  • 空间复杂度: 最佳:O(log n), 最差:O(n)
  • 排序方式: 快速排序是一种原地排序算法。

堆排序 (Heap Sort)

简介

堆排序是一种基于二叉堆数据结构的排序算法。它将待排序的序列构建成一个二叉堆,然后反复将堆顶元素与堆尾元素交换,将最大(或最小)元素放在已排序部分的末尾。通过反复调整堆,直到整个序列有序。堆排序具有高效的性能,并且是一种原地排序算法。

算法步骤

  1. 构建一个最大堆(对于升序排序)或最小堆(对于降序排序)。
  2. 将堆顶元素与堆尾元素交换,将最大(或最小)元素放在已排序部分的末尾。
  3. 调整堆,使得剩余元素仍然构成一个最大堆(或最小堆)。
  4. 重复步骤2-3,直到整个序列有序。

图解算法

在这里插入图片描述

代码示例

 /**
     * 堆排序
     *
     * @param arr 待排序数组
     */
    public static void heapSort(int[] arr) {
        int n = arr.length;

        // 构建最大堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 依次取出最大堆的根节点,放到数组末尾,重新调整堆结构
        for (int i = n - 1; i > 0; i--) {
            // 将堆顶元素与当前未排序部分的最后一个元素交换
            swap(arr, 0, i);

            // 调整堆,重新构建最大堆
            heapify(arr, i, 0);
        }
    }

    /**
     * 调整堆,使其成为最大堆
     *
     * @param arr 待调整数组
     * @param n   堆的大小
     * @param i   当前节点索引
     */
    public static void heapify(int[] arr, int n, int i) {
        int largest = i;
        int left = 2 * i + 1;
        int right = 2 * i + 2;

        // 找到左右子节点中值最大的节点索引
        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }

        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }

        // 如果最大值的索引不是当前节点索引,交换节点值,然后递归调整下层堆结构
        if (largest != i) {
            swap(arr, i, largest);
            heapify(arr, n, largest);
        }
    }

    /**
     * 交换数组中两个元素的位置
     *
     * @param arr 待交换数组
     * @param i   第一个元素的索引
     * @param j   第二个元素的索引
     */
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

算法分析

  • 稳定性: 堆排序是一种不稳定的排序算法,相等元素可能在排序过程中改变相对位置。
  • 时间复杂度: 最佳:O(n log n), 最差:O(n log n), 平均:O(n log n)
  • 空间复杂度: O(1)
  • 排序方式: 堆排序是一种原地排序算法。

计数排序 (Counting Sort)

简介

计数排序是一种非比较性的排序算法,适用于具有一定范围的整数元素。它通过统计每个元素的出现次数,然后根据统计信息将元素排列成有序序列。计数排序的核心思想是将每个元素的出现次数记录在额外的数组中,然后根据统计信息构建有序序列。

算法步骤

  1. 统计每个元素的出现次数,得到计数数组。
  2. 根据计数数组,构建有序序列。

图解算法

在这里插入图片描述

代码示例

 /**
     * 计数排序
     *
     * @param arr 待排序数组
     */
    public static void countingSort(int[] arr) {
        int n = arr.length;
        // 找到数组中的最大值,确定计数数组的大小
        int max = arr[0];
        for (int value : arr) {
            if (value > max) {
                max = value;
            }
        }
        // 创建计数数组,并初始化为0
        int[] count = new int[max + 1];
        for (int i = 0; i <= max; i++) {
            count[i] = 0;
        }
        // 统计每个元素的出现次数
        for (int value : arr) {
            count[value]++;
        }
        // 根据计数数组,重构原数组
        int index = 0;
        for (int i = 0; i <= max; i++) {
            while (count[i] > 0) {
                arr[index++] = i;
                count[i]--;
            }
        }
    }

算法分析

  • 稳定性: 计数排序是一种稳定的排序算法,相等元素的相对位置在排序前后不会改变。
  • 时间复杂度: 最佳:O(n + k), 最差:O(n + k), 平均:O(n + k)
  • 空间复杂度: O(k),其中 k 为计数数组的大小。
  • 排序方式: 计数排序是一种非原地排序算法。

桶排序 (Bucket Sort)

简介

桶排序是一种非比较性的排序算法,它将待排序元素分散到不同的桶中,然后分别对每个桶中的元素进行排序,最后将各个桶中的元素合并得到有序序列。桶排序适用于元素分布均匀的情况,可以有效提高排序的速度。

算法步骤

  1. 将待排序元素分散到不同的桶中。
  2. 对每个桶中的元素进行排序(可以使用其他排序算法或递归桶排序)。
  3. 将各个桶中的元素合并得到有序序列。

图解算法

在这里插入图片描述

代码示例

/**
     * 桶排序
     *
     * @param arr 待排序数组
     */
    public static void bucketSort(double[] arr) {
        int n = arr.length;

        // 创建桶
        ArrayList<Double>[] buckets = new ArrayList[n];

        for (int i = 0; i < n; i++) {
            buckets[i] = new ArrayList<>();
        }

        // 将元素分配到对应的桶中
        for (double value : arr) {
            int bucketIndex = (int) (value * n);
            buckets[bucketIndex].add(value);
        }

        // 对每个桶内的元素进行排序
        for (ArrayList<Double> bucket : buckets) {
            Collections.sort(bucket);
        }

        // 将桶中的元素合并到原数组
        int index = 0;
        for (ArrayList<Double> bucket : buckets) {
            for (double value : bucket) {
                arr[index++] = value;
            }
        }
    }

算法分析

  • 稳定性: 桶排序是一种稳定的排序算法,相等元素的相对位置在排序前后不会改变。
  • 时间复杂度: 最佳:O(n + k), 最差:O(n^2), 平均:O(n + n^2/k + k),其中 k 为桶的数量。
  • 空间复杂度: O(n + k),其中 n 为待排序元素的数量,k 为桶的数量。
  • 排序方式: 桶排序是一种非原地排序算法。

基数排序 (Radix Sort)

简介

基数排序是一种非比较性的排序算法,它根据元素的位数进行排序。基数排序的核心思想是将待排序元素按照低位到高位的顺序依次进行排序,每次排序使用稳定的排序算法。基数排序适用于元素的位数相同的情况,例如整数或字符串。

算法步骤

  1. 将待排序元素按照最低位开始,依次排序。
  2. 重复步骤1,直到排序到最高位。
  3. 合并得到有序序列。

图解算法

在这里插入图片描述

代码示例

import java.util.ArrayList;

/**
 * 基数排序
 * @param arr 待排序数组
 * @return 排序后的数组
 */
public static int[] radixSort(int[] arr) {
    // 找到数组中的最大值,确定位数
    int max = getMax(arr);

    // 进行位数的排序
    for (int exp = 1; max / exp > 0; exp *= 10) {
        countingSortByDigit(arr, exp);
    }

    return arr;
}

/**
 * 获取数组中的最大值
 * @param arr 待排序数组
 * @return 数组中的最大值
 */
private static int getMax(int[] arr) {
    int max = arr[0];
    for (int num : arr) {
        if (num > max) {
            max = num;
        }
    }
    return max;
}

/**
 * 根据位数进行计数排序
 * @param arr 待排序数组
 * @param exp 当前位数,例如个位、十位、百位...
 */
private static void countingSortByDigit(int[] arr, int exp) {
    int n = arr.length;
    int[] output = new int[n];
    int[] count = new int[10];

    // 统计每个数字的出现次数
    for (int i = 0; i < n; i++) {
        count[(arr[i] / exp) % 10]++;
    }

    // 计算累加次数
    for (int i = 1; i < 10; i++) {
        count[i] += count[i - 1];
    }

    // 构建输出数组
    for (int i = n - 1; i >= 0; i--) {
        output[count[(arr[i] / exp) % 10] - 1] = arr[i];
        count[(arr[i] / exp) % 10]--;
    }

    // 将输出数组拷贝回原数组
    System.arraycopy(output, 0, arr, 0, n);
}

算法分析

  • 稳定性: 基数排序是一种稳定的排序算法,相等元素的相对位置在排序前后不会改变。
  • 时间复杂度: 最佳:O(d * (n + k)), 最差:O(d * (n + k)), 平均:O(d * (n + k)),其中 d 为元素的位数,k 为基数。
  • 空间复杂度: O(n + k),其中 n 为待排序元素的数量,k 为基数。
  • 排序方式: 基数排序是一种非原地排序算法。

算法源码总结需要的可以直接下载

如果有积分的可以支持一下
点击下载:Java经典十大算法源码

没有积分的可以在百度网盘下载
链接: https://pan.baidu.com/s/15seMR8RCZswMrhprKs0tMA 提取码: viii

♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠

分享不易,原创不易!如果以上链接对您有用,可以请作者喝杯水!您的支持是我持续分享的动力,感谢!!!

在这里插入图片描述
无论是哪个阶段,坚持努力都是成功的关键。不要停下脚步,继续前行,即使前路崎岖,也请保持乐观和勇气。相信自己的能力,你所追求的目标定会在不久的将来实现。加油!

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

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

相关文章

Delayed 延时任务

延时任务与定时任务的区别 延时任务&#xff0c;可以理解为定时任务的一种&#xff0c;但是他们是有区别的。 延时任务&#xff1a;将程序代码延时执行&#xff0c;执行完毕&#xff0c;即为结束。 定时任务&#xff1a;周期性执行任务。代码执行完毕后&#xff0c;并不意味着…

2023年中国AI基础设施行业发展趋势分析:AI基础设施将保持高速增长[图]

从产品形态来看&#xff0c;AI基础设施可划分为AI基础硬件和基础软件两大类。而在AI生态系统中&#xff0c;通用型和定制型AI基础设施的相互依赖性促进了广泛的AI技术应用&#xff0c;也为各行业的持续发展提供了关键支持。 AI基础设施分类 资料来源&#xff1a;共研产业咨询&…

windows11系统如何设置锁屏壁纸

1. 在开始页面里面找到设置 2. 在设置里面找到个性化 3. 按照红色圈出部分操作 个性化锁屏界面 选择 图片 浏览照片 选择一张你觉得好看的图片作为锁屏壁纸 注&#xff1a;如果需要在锁屏后的登录页面显示壁纸 请勾选第三个红圈部分

Java基本数据类型与引用类型的区别

基本数据类型存放在栈中&#xff0c;引用数据类型其具体内容存放在堆中栈中存放的是其具体内容所在内存的地址。通过变量地址可以找到变量的具体内容&#xff0c;就像通过房间号可以找到房间一样。 public class Main{public static void main(String[] args){//基本数据类型in…

Active Directory 和域名系统(DNS)的相互关系

什么是域名系统&#xff08;DNS&#xff09; 域名系统&#xff08;DNS&#xff09;&#xff0c;从一般意义上讲是一种将主机名或域名解析为相应IP地址的手段。 在 AD 的中&#xff0c;DNS 服务维护 DNS 域和子域的工作命名空间&#xff0c;这些域和子域主要有助于查找过程&am…

最新绿豆APP源码苹果CMS影视插件版本/原生JAVA源码+反编译开源+免授权

源码简介&#xff1a; 最新绿豆APP源码苹果CMS影视插件版本&#xff0c;它是原生JAVA源码反编译开源免授权&#xff0c;绿豆影视对接苹果CMS&#xff0c;它可以支持多功能自定义DIY页面布局。 1、新版绿豆视频APP视频6.1插件版反编译指南及教程 2、后端插件开源&#xff0c;可…

archery修改为不能自提自审核上线SQL

目录 背景修改代码效果参考 背景 我和同事都可以提交上线SQL&#xff0c;但是不能自己提交的SQL自己去审核通过。目前的情况是可以自提自审。 修改代码 找到/opt/archery/sql/utils/workflow_audit.py文件 ...省略...# 判断用户当前是否是可审核staticmethoddef can_revie…

windows如何查看自己的ip地址

windows如何查看自己的ip地址 1.打开控制面板 2.进入网络和internet 3.进入网络共享中心 4.点击以太网进入网络详情页&#xff0c;或邮件已连接的网络&#xff0c;点击属性 5.查看ipv4地址就是当前机器ip

prometheus基本介绍 prometheus和zabbix的区别 grafana可视化工具

一、 promethues概念prometheus介绍promethues的特点prometheus的工作原理prometheus的架构 二、promethues和zabbix的区别三、prometheus安装部署prometheus下载安装prometheus启动浏览器访问查看暴露指标将Prometheus配置为systemd管理 配置node_exporter监控项node_promethe…

深入探讨软件测试技术:方法、工具与最佳实践

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 引言 软件测试是软件开发生命周期中至关重要的…

微信小程序会议OA-登录获取手机号流程登录-小程序导入微信小程序SDK(从微信小程序和会议OA登录获取手机号到登录小程序导入微信小程序SDK)

目录 获取用户昵称头像和昵称 wx.getUserProfile bindgetuserinfo 登录过程 登录-小程序 wx.checkSession wx.login wx.request 后台 准备数据表 反向生成工具生成 准备封装前端传过来的数据 小程序服器配置 导入微信小程序SDK application.yml WxProperties …

对OpenAI CEO奥特曼突然被解雇事件的一些分析

今天也来凑个热闹&#xff0c;说说OpenAI的事。本来不想写的&#xff0c;但是看到自媒体又开始胡说八道&#xff0c;所以根据我自己得到的消息和理解说一说我的看法&#xff0c;这篇文章要是有个小姐姐解说录成视频&#xff0c;那肯定火了&#xff0c;但是我现在没资源&#xf…

OpenAI政变背后是科学家创始人的悲歌

OpenAI政变背后是科学家创始人的悲歌 去年11月突然推出ChatGPT震惊世界的OpenAI&#xff0c;在整整一年后以闪电解职CEO再次震惊世界。 有不少人以为这拿的是乔布斯的剧本&#xff0c;错了&#xff0c;这其实是天才科学家奋力一击的故事。 OpenAI的灵魂人物不是CEO Sam Al…

Vue3入门(与Vue2进行对比)

1. Vue2 选项式 API vs Vue3 组合式API 特点&#xff1a; 代码量变少分散式维护变成集中式维护 2. Vue3的优势 使用create-vue搭建Vue3项目 1. 认识create-vue create-vue是Vue官方新的脚手架工具&#xff0c;底层切换到了 vite &#xff08;下一代前端工具链&#xff09;&…

2023APMCM亚太杯/小美赛数学建模竞赛优秀论文模板分享

一、模板介绍 二、注意事项 将论文划分小节时&#xff0c;应避免在小节中出现大段的文字叙述&#xff0c;这样的叙述会妨碍评委在浏览论文时掌握论文的要点。重要的句子&#xff0c;包括首次定义的概念&#xff0c;用黑体书写。 重要的数学公式应另起新行单独列出。建模所用的…

使用activiti部署提示不是 ‘NCName‘ 的有效值

排查发现是整个流程图的&#xff0c;流程名称没有填写 修改之后就可以了

Redis篇---第十二篇

系列文章目录 文章目录 系列文章目录前言一、Memcache与Redis的区别都有哪些?二、单线程的redis为什么这么快三、redis的数据类型,以及每种数据类型的使用场景前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇…

大数据基础设施搭建 - Kafka(with ZooKeeper)

文章目录 一、简介二、单机部署2.1 上传压缩包2.2 解压压缩包2.3 修改配置文件&#xff08;1&#xff09;配置zookeeper地址&#xff08;2&#xff09;修改kafka运行日志(数据)存储路径 2.4 配置环境变量2.5 启动/关闭2.6 测试&#xff08;1&#xff09;查看当前服务器中的所有…

Wireshark的数据包它来啦!

通过Wireshark工具&#xff0c;可以轻松的看到网卡的数据信息。通过Wireshark显示的数据包内容信息&#xff0c;通常分七栏&#xff0c;介绍一下&#xff1a; 1No.&#xff1a; 数据包编号。 2.Time Time显示时间&#xff0c;以1号数据包发生开始计时。 3.Source Source显示内容…

零基础想系统地学习金融学、量化投资、数据分析、python,需要哪些课程、书籍?有哪些证书可以考?

曾经我也是零基础小白&#xff0c;题主想走的路&#xff0c;我已经走过啦&#xff5e;作为一名CFA持证人和管理因子投资的量化策略的投资组合经理&#xff0c;我把这些年积累的干货跟大家分享。 量化投资是金融学的一部分&#xff0c;量化投资&#xff08;跟量化交易的概念有部…