【尚硅谷】Java数据结构与算法笔记07 - 排序算法

news2024/11/19 14:27:38

文章目录

  • 一、排序算法简介
  • 二、排序的分类
  • 三、冒泡排序
    • 3.1 基本介绍
    • 3.2 算法图解
    • 3.3 代码实现
  • 四、选择排序
    • 4.1 基本介绍
    • 4.2 思路分析
    • 4.3 算法图解
    • 4.4 代码实现
  • 五、插入排序
    • 5.1 基本介绍
    • 5.2 思路分析
    • 5.3 算法图解
    • 5.4 代码实现
  • 六、希尔排序
    • 6.1 简单插入排序存在的问题
    • 6.2 基本介绍
    • 6.3 思路分析
    • 6.4 算法图解
    • 6.5 代码实现
  • 七、快速排序
    • 7.1 基本介绍
    • 7.2 算法图解
    • 7.3 代码实现
  • 八、归并排序
    • 8.1 基本介绍
    • 8.2 算法图解
    • 8.3 代码实现
  • 九、基数排序
    • 9.1 基本介绍
    • 9.2 思路分析
    • 9.3 算法图解
    • 9.4 代码实现
    • 9.5 基数排序说明
  • 十、堆排序
    • 10.1 基本介绍
    • 10.2 思路分析
    • 10.3 算法图解
    • 10.4 代码实现
  • 十一、计数排序
    • 11.1 基本介绍
    • 11.2 思路分析
    • 11.3 算法图解
    • 11.4 代码实现
  • 十二、桶排序
    • 12.1 基本介绍
    • 12.2 思路分析
    • 12.3 算法图解
    • 12.4 代码实现
  • 十三、常用排序算法总结和对比
    • 13.1 一张排序算法的对比图
    • 13.2 相关术语解释


一、排序算法简介

所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。


二、排序的分类

  1. 内部排序:
    指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。
  2. 外部排序法:
    数据量过大, 无法全部加载到内存中, 需要借助外部存储(文件等)进行排序。
  3. 常见的排序算法分类(见下图):

在这里插入图片描述


三、冒泡排序

3.1 基本介绍

冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较 相邻元素的值, 若发现逆序则交换, 使值较大的元素逐渐从前移向后部, 就象水底下的气泡一样逐渐向上冒。

优化

因为排序的过程中, 各元素不断接近自己的位置, 如果一趟比较下来没有进行过交换, 就说明序列有序, 因此要在排序过程中设置一个标志 flag 判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化, 可以在冒泡排序写好后, 在进行)

3.2 算法图解

在这里插入图片描述

小结上面的图解过程:

  • 一共进行 数组的大小-1 次 大的循环
  • 每一趟排序的次数在逐渐的减少
  • 如果我们发现在某趟排序中, 没有发生一次交换, 可以提前结束冒泡排序。这个就是优化

3.3 代码实现

【排序算法】Java实现9大排序算法及其速度对比(附详细代码)

上面链接中包含了冒泡排序的基础代码。但是没有进行优化。下面给出的是带有优化的冒泡排序代码:

    /**
     * @param nums 要排序的数组
     * @return 返回排序后的数组
     * @Description 冒泡排序
     */
    public static int[] bubbleSort(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            boolean isChange = false;
            for (int j = 0; j < nums.length - 1 - i; j++) {
                if (nums[j] > nums[j + 1]) {
                    //交换位置
                    int temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                    isChange = true;
                }
            }
            if (!isChange) {
                break;
            }
        }
        return nums;
    }

四、选择排序

4.1 基本介绍

选择式排序也属于内部排序法, 是从欲排序的数据中, 按指定的规则选出某一元素, 再依规定交换位置后达到 排序的目的。

4.2 思路分析

选择排序 (select sorting) 也是一种简单的排序方法。它的基本思想是:第一次从 arr[0] arr[n-1]中选取最小值, 与 arr ⁡ [ 0 ] \operatorname{arr}[0] arr[0] 交换, 第二次从 arr ⁡ [ 1 ] ∼ arr ⁡ [ n − 1 ] \operatorname{arr}[1] \sim \operatorname{arr}[\mathrm{n}-1] arr[1]arr[n1] 中选取最小值, 与 arr ⁡ [ 1 ] \operatorname{arr}[1] arr[1] 交换, 第三次从 arr ⁡ [ 2 ] arr ⁡ [ n − 1 ] \operatorname{arr}[2] \operatorname{arr}[\mathrm{n}-1] arr[2]arr[n1] 中选取最小值, 与 arr ⁡ [ 2 ] \operatorname{arr}[2] arr[2] 交换, ⋯ \cdots , 第 i \mathrm{i} i 次从 arr ⁡ [ i − 1 ] arr ⁡ [ n − 1 ] \operatorname{arr}[\mathrm{i}-1] \operatorname{arr}[\mathrm{n}-1] arr[i1]arr[n1] 中选取最小值, 与 arr ⁡ [ i − 1 ] \operatorname{arr}[\mathrm{i}-1] arr[i1] 交换, ⋯ \cdots , 第 n − 1 \mathrm{n}-1 n1 次从 arr ⁡ [ n − 2 ] arr ⁡ [ n − 1 ] \operatorname{arr}[\mathrm{n}-2] \operatorname{arr}[\mathrm{n}-1] arr[n2]arr[n1] 中选取最小值, 与 arr ⁡ [ n − 2 ] \operatorname{arr}[\mathrm{n}-2] arr[n2] 交换, 总共通过 n − 1 \mathrm{n}-1 n1 次, 得到一个按排序码从小到大排列的有序序列。

4.3 算法图解

在这里插入图片描述

4.4 代码实现

    /**
     * @param nums 要排序的数组
     * @return 返回排序后的数组
     * @Description 选择排序
     */
    public static int[] selectSort(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            int min = nums[i];
            int minIndex = i;
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] < min) {
                    min = nums[j];
                    minIndex = j;
                }
            }
            // 把找到的最小值和当前i位置替换
            int temp = nums[i];
            nums[i] = nums[minIndex];
            nums[minIndex] = temp;
        }
        return nums;
    }

五、插入排序

5.1 基本介绍

插入式排序属于内部排序法, 是对于要排序的元素以插入的方式找寻该元素的适当位置, 以达到排序的目的。

5.2 思路分析

插入排序(Insertion Sorting)的基本思想是:把 n \mathbf{n} n 个待排序的元素看成为一个有序表和一个无序表, 开始时有 序表中只包含一个元素, 无序表中包含有 n − 1 \mathbf{n - 1} n1 个元素, 排序过程中每次从无序表中取出第一个元素, 把它的排 序码依次与有序表元素的排序码进行比较, 将它插入到有序表中的适当位置, 使之成为新的有序表。

5.3 算法图解

在这里插入图片描述

5.4 代码实现

    public static int[] insertSort(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            // 把当前遍历的数字存起来
            int temp = nums[i];
            // 遍历当前数字前面所有的数字
            int j;
            for (j = i - 1; j >= 0 && nums[j] > temp; j--) {
                // 把前一个数字赋给后一个数字
                nums[j + 1] = nums[j];
            }
            // 把临时变量赋给不满足条件的后一个元素
            nums[j + 1] = temp;
        }
        return nums;
    }

六、希尔排序

6.1 简单插入排序存在的问题

我们看简单的插入排序可能存在的问题。

数组 arr ⁡ = { 2 , 3 , 4 , 5 , 6 , 1 } \operatorname{arr}=\{2,3,4,5,6,1\} arr={2,3,4,5,6,1} 这时需要插入的数 1 (最小), 这样的过程是:
{ 2 , 3 , 4 , 5 , 6 , 6 } { 2 , 3 , 4 , 5 , 5 , 6 } { 2 , 3 , 4 , 4 , 5 , 6 } { 2 , 3 , 3 , 4 , 5 , 6 } { 2 , 2 , 3 , 4 , 5 , 6 } { 1 , 2 , 3 , 4 , 5 , 6 } \begin{aligned} & \{2,3,4,5,6,6\} \\ & \{2,3,4,5,5,6\} \\ & \{2,3,4,4,5,6\} \\ & \{2,3,3,4,5,6\} \\ & \{2,2,3,4,5,6\} \\ & \{1,2,3,4,5,6\} \end{aligned} {2,3,4,5,6,6}{2,3,4,5,5,6}{2,3,4,4,5,6}{2,3,3,4,5,6}{2,2,3,4,5,6}{1,2,3,4,5,6}
结论: 当需要揷入的数是较小的数时, 后移的次数明显增多, 对效率有影响。

6.2 基本介绍

希尔排序是希尔 (Donald Shell) 于 1959 年提出的一种排序算法。希尔排序也是一种插入排序, 它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序

6.3 思路分析

希尔排序是把记录按下标的一定增量分组, 对每组使用直接插入排序算法排序; 随着增量逐渐减少, 每组包含 的关键词越来越多, 当增量减至 1 时, 整个文件恰被分成一组, 算法便终止

6.4 算法图解

在这里插入图片描述
在这里插入图片描述

6.5 代码实现

    public static int[] shellSort(int[] nums) {
        // 遍历所有步长
        for (int d = nums.length / 2; d > 0; d /= 2) {
            // 遍历所有元素
            for (int i = 0; i < nums.length; i++) {
                // 遍历本组中所有元素
                for (int j = i - d; j >= 0; j -= d) {
                    // 如果当前元素大于加上步长后的那个元素
                    if (nums[j] > nums[j + d]) {
                        int temp = nums[j];
                        nums[j] = nums[j + d];
                        nums[j + d] = temp;
                    }
                }
            }
        }
        return nums;
    }

七、快速排序

7.1 基本介绍

快速排序 (Quicksort) 是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两 部分, 其中一部分的所有数据都比另外一部分的所有数据都要小, 然后再按此方法对这两部分数据分别进行快速排序, 整个排序过程可以递归进行, 以此达到整个数据变成有序序列

7.2 算法图解

在这里插入图片描述

在这里插入图片描述

7.3 代码实现

    /**
     * @param nums  被排序的数组
     * @param start 排序开始位置
     * @param end   排序结束位置
     * @return 返回排序后的数组
     */
    public static int[] quickSort(int[] nums, int start, int end) {
        if (start < end) {
            // 低位置
            int low = start;
            // 高位置
            int high = end;
            // 取开始位置元素作为标准数
            int standard = nums[start];
            while (low < high) {
                // 如果右边的数组比标准数大
                while (nums[high] >= standard && low < high) {
                    high--;
                }
                // 使用右边的数字替换左边的数字
                nums[low] = nums[high];
                // 如果左边的数组比标准数小
                while (nums[low] <= standard && low < high) {
                    low++;
                }
                // 使用左边的数字替换右边的数字
                nums[high] = nums[low];
            }
            // 把标准数赋回给低位置或者高位置(此时低位置和高位置已经重合)
            nums[low] = standard;
            // 处理标准数左边的数字
            nums = quickSort(nums, start, low);
            // 处理标准数右边的数字
            nums = quickSort(nums, low + 1, end);
        }
        return nums;
    }

八、归并排序

8.1 基本介绍

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法, 该算法采用经典的分治(divide-and-conquer) 策略(分治法将问题分(divide)成一些小的问题然后递归求解, 而治(conquer)的阶段则将分的阶段得到的各答案"修 补"在一起, 即分而治之)。

8.2 算法图解

在这里插入图片描述
再来看看治阶段, 我们需要将两个已经有序的子序列合并成一个有序序列, 比如上图中的最后一次合并, 要将 [ 4 , 5 , 7 , 8 ] [4,5,7,8] [4,5,7,8] [ 1 , 2 , 3 , 6 ] [1,2,3,6] [1,2,3,6] 两个已经有序的子序列, 合并为最终序列 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] [1,2,3,4,5,6,7,8] [1,2,3,4,5,6,7,8], 来看下实现步骤
在这里插入图片描述

8.3 代码实现

	public static int[] mergeSort(int[] nums, int low, int high) {
        int mid = (low + high) / 2;
        if (low < high) {
            mergeSort(nums, low, mid);
            mergeSort(nums, mid + 1, high);
            merge(nums, low, mid, high);
        }

        return nums;
    }

    public static void merge(int[] nums, int low, int mid, int high) {
        // 临时数组
        int[] temp = new int[high - low + 1];
        // 记录左边的数组的最左位置
        int i = low;
        // 记录右边数组的最左位置
        int j = mid + 1;
        // 记录放入temp数组中的位置,从0开始放,每次放 就++
        int index = 0;
        while (i <= mid && j <= high) {
            if (nums[i] < nums[j]) {
                temp[index++] = nums[i++];
            } else {
                temp[index++] = nums[j++];
            }
        }
        // 处理多余的数组
        while (i <= mid) {
            temp[index++] = nums[i++];
        }
        while (j <= high) {
            temp[index++] = nums[j++];
        }
        // 将临时数组中的值赋给nums数组中对应位置
        for (int k = 0; k < temp.length; k++) {
            nums[k + low] = temp[k];
        }
    }

九、基数排序

注意:基数排序不支持负数,实数排序效率不高

9.1 基本介绍

  • 基数排序(radix sort)属于 “分配式排序” (distribution sort), 又称 “桶子法” (bucket sort)或 bin sort, 顾 名思义, 它是通过键值的各个位的值, 将要排序的元素分配至某些 “桶” 中, 达到排序的作用
  • 基数排序法是属于稳定性的排序, 基数排序法的是效率高的稳定性排序法
  • 基数排序(Radix Sort)是桶排序的扩展
  • 基数排序是 1887 年赫尔曼 - 何乐礼发明的。它是这样实现的: 将整数按位数切割成不同的数字, 然后按每个位数分别比较。

9.2 思路分析

将所有待比较数值统一为同样的数位长度, 数位较短的数前面补零。然后, 从最低位开始, 依次进行一次排序。 这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

这样说明, 比较难理解, 下面我们看一个图文解释, 理解基数排序的步骤

9.3 算法图解

 将数组  { 53 , 3 , 542 , 748 , 14 , 214 }  使用基数排序, 进行升序排序  \text { 将数组 }\{53,3,542,748,14,214\} \text { 使用基数排序, 进行升序排序 }  将数组 {53,3,542,748,14,214} 使用基数排序进行升序排序 

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

9.4 代码实现

	public static void radixSort(int[] arr) {
        // 找出最大数
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        // 计算最大数是几位数
        int maxLength = (max + "").length();
        // 定义一个二维数组,表示10个桶,每个桶就是一个一维数组
        // 说明:
        // 二维数组包含10个一维数组,
        // 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
        // 名明确,基数排序是使用空间换时间的经典算法
        int[][] bucket = new int[10][arr.length];
        // 为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
        // 可以这里理解
        // 比如:bucketElementCounts[0],记录的就是bucket[0]桶的放入的数据个数
        int[] bucketElementCounts = new int[10];
        // 这里我们使用循环将代码处理
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            // 针对每个元素的对应位进行排序处理,第一次是个位,第二次是十位,第三次是百位
            for (int j = 0; j < arr.length; j++) {
                // 取出每个元素的对应位的值
                int digitOfElement = arr[j] / n % 10;
                // 放入到对应的桶中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            // 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
            int index = 0;
            // 遍历每一桶,并将桶中的数据放入到原数组
            for (int k = 0; k < bucketElementCounts.length; k++) {
                // 如果桶中有数据,我们才放入到原数组
                if (bucketElementCounts[k] != 0) {
                    // 循环该桶即第k个桶(即第k个一维数组),放入
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        // 取出元素放入到arr
                        arr[index++] = bucket[k][l];
                    }
                }
                // 第i+1轮处理后,需要将每个bucketElementCounts[k] = 0
                bucketElementCounts[k] = 0;
            }
        }
    }

9.5 基数排序说明

  • 基数排序是对传统桶排序的扩展, 速度很快.
  • 基数排序是经典的空间换时间的方式, 占用内存很大, 当对海量数据排序时, 容易造成 OutOfMemoryError 。
  • 基数排序是稳定的。[注:假定在待排序的记录序列中, 存在多个具有相同的关键字的记录, 若经过排序, 这些 记录的相对次序保持不变, 即在原序列中, r [ i ] = r [ j ] r[i]=r[j] r[i]=r[j], 且 r[i]在 r[j]之前, 而在排序后的序列中, r [ i ] r[i] r[i] 仍在 r[j]之前, 则称这种排序算法是稳定的; 否则称为不稳定的]
  • 有负数的数组, 我们不用基数排序来进行排序, 如果要支持负数, 参考: https://code.i-harness.com/zh-CN/q/e98fa9

十、堆排序

参考链接:【算法】排序算法之堆排序

10.1 基本介绍

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  • 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  • 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 O ( n log ⁡ n ) Ο(n \log n) O(nlogn)

10.2 思路分析

① 先将初始的R[0…n-1]建立成最大堆,此时是无序堆,而堆顶是最大元素。
② 再将堆顶R[0]和无序区的最后一个记录R[n-1]交换,由此得到新的无序区R[0…n-2]和有序区R[n-1],且满足R[0…n-2].keys ≤ R[n-1].key
③ 由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1…n-1]调整为堆。然后再次将R[1…n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1…n-2]和有序区R[n-1…n],且仍满足关系R[1…n-2].keys≤R[n-1…n].keys,同样要将R[1…n-2]调整为堆。
④ 直到无序区只有一个元素为止。

10.3 算法图解

在这里插入图片描述
堆排序算法的演示。首先,将元素进行重排,以匹配堆的条件。图中排序过程之前简单的绘出了堆树的结构。

在这里插入图片描述

10.4 代码实现

	public static int[] heapSort(int[] nums) {
        int start = (nums.length - 1) / 2;
        // 调整为大顶堆
        for (int i = start; i >= 0; i--) {
            maxHeap(nums, nums.length, i);
        }
        //
        for (int i = nums.length - 1; i >= 0; i--) {
            int temp = nums[0];
            nums[0] = nums[i];
            nums[i] = temp;
            maxHeap(nums, i, 0);
        }
        return nums;
    }

    // 转大顶堆的方法
    public static void maxHeap(int[] nums, int size, int index) {
        // 当前节点
        int self = index;
        // 左子节点
        int left = 2 * index + 1;
        // 和左子节点进行对比,选出最大的节点放到自身位置
        if (left < size && nums[self] < nums[left]) {
            int temp = nums[self];
            nums[self] = nums[left];
            nums[left] = temp;
            maxHeap(nums, size, left);
        }
        // 右子节点
        int right = 2 * index + 2;
        // 和右子节点进行对比,选出最大的节点放到自身位置
        if (right < size && nums[self] < nums[right]) {
            int temp = nums[self];
            nums[self] = nums[right];
            nums[right] = temp;
            maxHeap(nums, size, right);
        }
    }

十一、计数排序

11.1 基本介绍

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

  1. 计数排序的特征
    当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

通俗地理解,例如有 10 个年龄不同的人,统计出有 8 个人的年龄比 A 小,那 A 的年龄就排在第 9 位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去 1 的原因。

注意:计数排序只适用于非负整数的排序

11.2 思路分析

算法的步骤如下:

(1)找出待排序的数组中最大和最小的元素
(2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
(3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
(4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

11.3 算法图解

在这里插入图片描述

11.4 代码实现

	public static void countSort(int[] array) {
        // 1.得到数列的最大值
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > max) {
                max = array[i];
            }
        }
        // 2.根据数列最大值确定统计数组的长度
        int[] countArray = new int[max + 1];
        // 3.遍历数列,填充统计数组
        for (int k : array) {
            countArray[k]++;
        }
        // 4.遍历统计数组,输出结果
        int index = 0;
        for (int i = 0; i < countArray.length; i++) {
            for (int j = 0; j < countArray[i]; j++) {
                array[index++] = i;
            }
        }
    }

十二、桶排序

12.1 基本介绍

桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后依次把各个桶中的记录列出来记得到有序序列。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到O(n log n)下限的影响。

12.2 思路分析

桶排序的思想近乎彻底的分治思想。

桶排序假设待排序的一组数均匀独立的分布在一个范围中,并将这一范围划分成几个子范围(桶)。

然后基于某种映射函数f ,将待排序列的关键字 k 映射到第i个桶中 (即桶数组B 的下标i) ,那么该关键字k 就作为 B[i]中的元素 (每个桶B[i]都是一组大小为N/M 的序列 )。

接着将各个桶中的数据有序的合并起来 : 对每个桶B[i] 中的所有元素进行比较排序 (可以使用快排)。然后依次枚举输出 B[0]….B[M] 中的全部内容即是一个有序序列。

补充: 映射函数一般是 f = array[i] / k; k^2 = n; n是所有元素个数
为了使桶排序更加高效,我们需要做到这两点:

1、在额外空间充足的情况下,尽量增大桶的数量;
2、使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中;
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

实现逻辑

  • 设置一个定量的数组当作空桶子。
  • 寻访序列,并且把项目一个一个放到对应的桶子去。
  • 对每个不是空的桶子进行排序。
  • 从不是空的桶子里把项目再放回原来的序列中。

12.3 算法图解

在这里插入图片描述

12.4 代码实现

	public static void bucketSort(int[] arr) {
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for (int k : arr) {
            max = Math.max(max, k);
            min = Math.min(min, k);
        }
        // 桶数
        int bucketNum = (max - min) / arr.length + 1;
        ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
        for (int i = 0; i < bucketNum; i++) {
            bucketArr.add(new ArrayList<>());
        }
        // 将每个元素放入桶
        for (int j : arr) {
            int num = (j - min) / (arr.length);
            bucketArr.get(num).add(j);
        }
        // 对每个桶进行排序
        for (ArrayList<Integer> integerArrayList : bucketArr) {
            Collections.sort(integerArrayList);
        }
        int index = 0;
        // 将桶中的数据依次取出
        for (ArrayList<Integer> integers : bucketArr) {
            for (Integer integer : integers) {
                arr[index++] = integer;
            }
        }
    }

十三、常用排序算法总结和对比

13.1 一张排序算法的对比图

在这里插入图片描述

13.2 相关术语解释

  • 稳定: 如果 a a a 原本在 b b b 前面, 而 a = b a=b a=b, 排序之后 a a a 仍然在 b b b 的前面;
  • 不稳定:如果 a a a 原本在 b b b 的前面, 而 a = b a=b a=b, 排序之后 a a a 可能会出现在 b b b 的后面;
  • 内排序: 所有排序操作都在内存中完成;
  • 外排序: 由于数据太大, 因此把数据放在磁盘中, 而排序通过磁盘和内存的数据传输才能进行;
  • 时间复杂度:一个算法执行所耗费的时间。
  • 空间复杂度: 运行完一个程序所需内存的大小。
  • n \mathrm{n} n : 数据规模
  • k \mathrm{k} k : “桶” 的个数
  • In-place: 不占用额外内存
  • Out-place: 占用额外内存

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

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

相关文章

知识点查漏补缺

目录谷粒商城知识点补充前言1 java8新特性之lambda表达式1.1 为什么使用1.2 从匿名类到Lambda的转换1.3 语法总结2 Stream API2.1 概述2.2 创建2.2.1 通过集合2.2.2 通过数组2.2.3 通过Stream的of()2.2.4 创建无限流2.3 中间操作2.3.1 筛选与切片1)、filter2)、limit3)、skip(n…

【数据结构】—— Java实现队列和循环队列

队列与循环队列一、队列1.概念2.队列的使用3.队列的模拟实现二、循环队列1.基本概念2.代码实现一、队列 1.概念 队列&#xff1a;是允许在一端进行插入操作&#xff0c;而在另一端进行删除操作的线性表。 队列是一种先进先出的&#xff08;First In First Out&#xff09;的…

商场楼层分布图用什么软件做,高效、便捷的商场二三维地图绘制平台

现在的很多大型购物商场占地面积较大&#xff0c;商品种类繁多&#xff0c;如果没有商场导航&#xff0c;会导致新顾客难以找到想要的店铺和商品&#xff0c;那么大型购物商场导航怎么实现呢&#xff1f;电子地图作为大家最喜闻乐见的高效应用形式&#xff0c;在商业应用中&…

C生万物 | 函数的讲解与剖析【内附众多案例详解】

&#x1f451;作者主页&#xff1a;Fire_Cloud_1 &#x1f3e0;学习社区&#xff1a;烈火神盾 &#x1f517;专栏链接&#xff1a;万物之源——C 一起来学习函数吧&#xff01;一、函数是什么&#xff1f;二、C语言中函数的分类1、库函数2、自定义函数【⭐⭐⭐】三、函数的参数…

【Linux学习】vim指令集(一)

Linux的最高境界 1、背景 vim是一款多模式的文本编辑器&#xff0c;兼容所有的vi语法&#xff0c;其有多种操作模式&#xff0c;每种模式可以互相切换。vim的安装指令如下所示&#xff1a; yum install -y vim2、vim编辑器的相关指令 常用的vim模式有命令模式、插入模式、底…

Vue项目大概目录介绍

后端,自己学习做个记录.实话是看不懂全靠抄. 这是一个刚建好的Vue项目 node_modules:整个项目用到的依赖文件 public--->favicon.ico:Vue的图标 public--->index.html:可以理解为首页入口,模板页,开发的时候用不到,Vue是个单页面应用,开发也用不到 src:好比项目的源码…

智云通CRM:阻挡客户拜访的三个因素,你中了几个?

拜访客户为什么会迟迟不行动&#xff0c;一般有三个因素&#xff0c;它们分别是“没有主动性”“证明自己的价值需要时间”“我很不专业&#xff0c;需要学习”&#xff0c;因为这三个因素都在诉说一件事——“我需要准备”。智云通CRM统计常见的情形如下。 “我刚刚从事这个…

nginx反向代理、负载均衡、动静结合

目录前言nginx是什么&#xff1f;一.启动异常1.80端口被占用① 关掉占用端口② 修改 nginx.conf 文件2.启动nginx并测试是否启动成功① 启动② 测试 电脑ip 你自己在nginx.conf设置的端口二.应用场景之负载均衡三.应用场景之反向代理1.正向代理和反向代理的区别2.配置反向代理…

Linux常用命令——wget命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) wget Linux系统下载文件工具 补充说明 wget命令用来从指定的URL下载文件。wget非常稳定&#xff0c;它在带宽很窄的情况下和不稳定网络中有很强的适应性&#xff0c;如果是由于网络的原因下载失败&#xff0c;…

一款ARPG游戏是如何搭建云真机系统的

随着业内对“工业化”认知的不断提升&#xff0c;越来越多的UWA用户通过UWA Pipeline的各项功能&#xff0c;为项目研发提供了极大的助力。其中的自动化测试与GOT Online性能测评的结合&#xff0c;帮助项目组在每个“测试-反馈”周期内&#xff0c;在大批量真机设备上实现项目…

Linux——信号知识归纳(下)

目录 一.进程状态 二.信号捕捉时机与流程 三.sigaction函数 四.SIGCHLD信号 一.进程状态 linux将进程的状态分为用户态&#xff08;user mode&#xff09;和内核态&#xff08;kernel mode&#xff09;。 内核态时CPU执行代码不受任何限制&#xff0c;而用户态会做代码安…

MRP的库存供应天数(StckDS)和收货供应天数(RDS)

我们在MD07里面&#xff0c;可以看到三个天数&#xff1a; 1、库存可供应天数(StckDS) 2、第一次日供应量收货(RDS 1) 3、第二接货日期的收货(RDS 2) 而且信号灯的显示就是按照这三个天数来进行设置的 我们以15000047这个物料为例&#xff0c;来看下这三个数是怎么计算的 …

Java集合全解【完整版】

文章目录01 初识集合> 集合是什么&#xff1f;为什么要用集合&#xff1f;> 集合的框架体系02 Collection接口> Collection接口常用方法> Collection接口遍历元素&#xff1a;使用Iterator&#xff08;迭代器&#xff09;> Collection接口遍历元素&#xff1a;增…

那个叫郭不戳的博主去哪了--回归篇

文章目录那个叫郭不戳的博主去哪了--回归篇所以你究竟去哪了&#xff1f;有没有好的面试经验啊工作方面怎么样啊卷不卷总结那个叫郭不戳的博主去哪了–回归篇 消失了三个月&#xff0c;今天开始正式回归。一大早打开CSDN看到好多消息&#xff0c;首先谢谢大家对我的认可。接下来…

基于微信小程序云开发的医院体检预约小程序源码,医院体检预约小程序源码,实现体检预约管理、体检预约凭证、预约数据查看导出 版权申诉

功能介绍 对于医院体检科室和体检机构而言&#xff0c;每天的体检人数分布也不太均衡&#xff0c;若很多人集中在 某些天体检&#xff0c;会出现「医生强度大、体检人排队久」的问题。采用体检预约小程序进行体检预约数管理&#xff0c;体检人可预约到更广的日期范围、更精准的…

Apache Spark 机器学习 管道 3

Apache Spark的机器学习管道提供一个统一的、高级的APIs集合&#xff0c;该APIs集合是以数据框架&#xff08;Datagrams&#xff09;为基础&#xff0c;帮助开发人员创建或者优化一个用于实际环境的机器学习的管道。 管道&#xff08;Pipeline&#xff09;的基本概念 Spark机…

测试之Bug与用例【创建Bug、Bug级别、Bug生命周期、测试用例的万能公式、设计测试用例具体方法】

文章目录1. 如何创建Bug2. Bug的级别3. Bug的生命周期4. 面试题&#xff1a;跟开发产生争执怎么办5. 设计测试用例的万能公式使用万能公式对水杯设计测试用例6. 设计测试用例的具体方法6.1 等价类6.2 边界类6.3 判定表6.4 正交法(allparis)6.5 场景设计法1. 如何创建Bug 提 Bu…

关于USB的事儿

FTDI公司的产品值得研究&#xff0c;包含很多USB转接芯片【串口、SPI、IIC、JTAG】。USB通信芯片本身也是需要编程的&#xff0c;上位机和USB芯片通信是需要驱动的&#xff0c;才能实现对应的识别。上位机(VB、C#、C等一系列面向对象程序)调用dll文件&#xff0c;里面很多API函…

集群安全Security

一、多节点 1、配置文件中配置 xpack.security.enabled: true xpack.security.transport.ssl.enabled: true xpack.security.transport.ssl.verification_mode: certificate xpack.security.transport.ssl.client_authentication: required xpack.security.transport.ssl.ke…

Java中最常用的循环--for循环

文章目录0 写在前面1 格式2 举例说明三种循环之间的区别写在最后0 写在前面 Java for循环用于多次迭代程序的一部分&#xff0c;或者多次执行同一个代码块。如果迭代次数是固定的&#xff0c;建议使用for循环。 说实在的&#xff0c;for循环就是while循环的变种。只不过限定条…