热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

9个元素换6次达到排序序列_7天时间,我整理并实现了这9种常见的排序算法

排序算法回顾我们前面已经介绍了3种最常见的排序算法:java实现冒泡排序讲解QuickSort快速排序到底快在哪里?SelectionSort选择排序算
407dc3c36843431389ed08ec9b6f1b59

排序算法

回顾

我们前面已经介绍了 3 种最常见的排序算法:

java 实现冒泡排序讲解

QuickSort 快速排序到底快在哪里?

SelectionSort 选择排序算法详解(java 实现)

然而天下排序千千万,今天老马就和大家一起把最常见的几种都学习一遍。

堆排序

堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。

堆是一个近似完全二叉树的结构,并同时满足堆的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。

基础知识 JCIP-11-二叉堆

最大堆

若以升序排序说明,把数组转换成最大堆(Max-Heap Heap),这是一种满足最大堆性质(Max-Heap Property)的二叉树:对于除了根之外的每个节点i, A[parent(i)] ≥ A[i]。

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

重复从最大堆取出数值最大的结点(把根结点和最后一个结点交换,把交换后的最后一个结点移出堆),并让残余的堆维持最大堆性质。

a65e212f1d2d4411b1cb9889e9b50f7b

输入图片说明

同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子:

d1b886832633441bac0ba7a5ce9ac22d

输入图片说明

堆节点的访问

通常堆是通过一维数组来实现的。

在数组起始位置为0的情形中:

则父节点和子节点的位置关系如下:

(01) 索引为i的左孩子的索引是 (2*i+1);

(02) 索引为i的左孩子的索引是 (2*i+2);

(03) 索引为i的父结点的索引是 floor((i-1)/2);

ec8715640d244c5096e44eca315015bb

堆节点的访问

堆的操作

在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。

堆中定义以下几种操作:

最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点

创建最大堆(Build Max Heap):将堆中的所有数据重新排序

堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算

堆排序算法图解

这个图解来自 图解排序算法(三)之堆排序,画的非常漂亮。

基本思想

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。

将其与末尾元素进行交换,此时末尾就为最大值。

然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

步骤

步骤一 构造初始堆

将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

a. 假设给定无序序列结构如下

15c8c23d0d444e0cae6600890b186c92

输入图片说明

b. 此时我们从最后一个非叶子结点开始(叶子结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

76574d21456d474babc3b428d3ed4ab4

输入图片说明

c. 找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

1f6194f5e90d4aeca319bc6f0e947092

输入图片说明

d. 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

d3dced3cfb9b42a089a3f5c861fd9d54

输入图片说明

此时,我们就将一个无序的序列构造成了一个大顶堆。

步骤二 调整堆

将堆顶元素与末尾元素进行交换,使末尾元素最大。

然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

a. 将堆顶元素9和末尾元素4进行交换

ac3ac6f7b459434ab000b18923d7fd3e

输入图片说明

b. 重新调整结构,使其继续满足堆定义

ed20ca8420484a6ba7f73f2155f43036

输入图片说明

c. 再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.

9f6f538110934e91aa9194f04b792153

输入图片说明

后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

4f28902f95b44b9eb1a0c9e5b6990cba

输入图片说明

简单总结

再简单总结下堆排序的基本思路:

a. 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

b. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

c. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

java 实现

说明

为了和前面的逻辑保持一致,我们暂时依然使用 list 去实现这个堆排序。

实现

package com.github.houbb.sort.core.api;import com.github.houbb.log.integration.core.Log;import com.github.houbb.log.integration.core.LogFactory;import com.github.houbb.sort.core.util.InnerSortUtil;import java.util.List;/** * 堆排序 * * &#64;author binbin.hou * &#64;since 0.0.4 */public class HeapSort extends AbstractSort {    private static final Log log &#61; LogFactory.getLog(HeapSort.class);    &#64;Override    &#64;SuppressWarnings("all")    protected void doSort(List> original) {        final int maxIndex &#61; original.size() - 1;        /*         *  第一步&#xff1a;将数组堆化         *  beginIndex &#61; 第一个非叶子节点。         *  从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。         *  叶子节点可以看作已符合堆要求的节点&#xff0c;根节点就是它自己且自己以下值为最大。         */        int beginIndex &#61; original.size() / 2 - 1;        for (int i &#61; beginIndex; i >&#61; 0; i--) {            maxHeapify(original, i, maxIndex);        }        /*         * 第二步&#xff1a;对堆化数据排序         * 每次都是移出最顶层的根节点A[0]&#xff0c;与最尾部节点位置调换&#xff0c;同时遍历长度 - 1。         * 然后从新整理被换到根节点的末尾元素&#xff0c;使其符合堆的特性。         * 直至未排序的堆长度为 0。         */        for (int i &#61; maxIndex; i > 0; i--) {            InnerSortUtil.swap(original, 0, i);            maxHeapify(original, 0, i - 1);        }    }    /**     * 调整索引为 index 处的数据&#xff0c;使其符合堆的特性。     *     * &#64;param list  列表     * &#64;param index 需要堆化处理的数据的索引     * &#64;param len   未排序的堆(数组)的长度     * &#64;since 0.0.4     */    &#64;SuppressWarnings("all")    private void maxHeapify(final List list, int index, int len) {        int li &#61; (index * 2) &#43; 1; // 左子节点索引        int ri &#61; li &#43; 1;           // 右子节点索引        int cMax &#61; li;             // 子节点值最大索引&#xff0c;默认左子节点。        // 左子节点索引超出计算范围&#xff0c;直接返回。        if (li > len) {            return;        }        // 先判断左右子节点&#xff0c;哪个较大。        if (ri <&#61; len && InnerSortUtil.gt(list, ri, li)) {            cMax &#61; ri;        }        if (InnerSortUtil.gt(list, cMax, index)) {            InnerSortUtil.swap(list, cMax, index);      // 如果父节点被子节点调换&#xff0c;            maxHeapify(list, cMax, len);  // 则需要继续判断换下后的父节点是否符合堆的特性。        }    }}

插入排序

插入排序(英语&#xff1a;Insertion Sort)是一种简单直观的排序算法。

它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。

插入排序在实现上&#xff0c;通常采用in-place排序(即只需用到 O(1) 的额外空间的排序)&#xff0c;因而在从后向前扫描过程中&#xff0c;需要反复把已排序元素逐步向后挪位&#xff0c;为最新元素提供插入空间。

算法步骤

一般来说&#xff0c;插入排序都采用in-place在数组上实现。具体算法描述如下&#xff1a;

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

排序

java 实现

java 实现

package com.github.houbb.sort.core.api;import com.github.houbb.heaven.annotation.ThreadSafe;import com.github.houbb.log.integration.core.Log;import com.github.houbb.log.integration.core.LogFactory;import com.github.houbb.sort.core.util.InnerSortUtil;import java.util.List;/** * 冒泡排序 * &#64;author binbin.hou * &#64;since 0.0.5 */&#64;ThreadSafepublic class InsertSort extends AbstractSort {    private static final Log log &#61; LogFactory.getLog(InsertSort.class);    &#64;Override    &#64;SuppressWarnings("all")    public void doSort(List original) {        for(int i &#61; 1; i &#61; 0 && InnerSortUtil.gt(original, j, current)) {                // 元素向后移动一位                original.set(j&#43;1, original.get(j));                j--;            }            // 将元素插入到对应的位置            original.set(j&#43;1, current);        }    }}

希尔排序(Shellsort)

也称递减增量排序算法&#xff0c;是插入排序的一种更高效的改进版本。

希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的&#xff1a;

  1. 插入排序在对几乎已经排好序的数据操作时&#xff0c;效率高&#xff0c;即可以达到线性排序的效率
  2. 但插入排序一般来说是低效的&#xff0c;因为插入排序每次只能将数据移动一位

算法实现

希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。

这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序&#xff0c;算法的最后一步就是普通的插入排序&#xff0c;但是到了这步&#xff0c;需排序的数据几乎是已排好的了(此时插入排序较快)。

假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n^2)的排序(冒泡排序或插入排序)&#xff0c;可能会进行n次的比较和交换才能将该数据移至正确位置。

而希尔排序会用较大的步长移动数据&#xff0c;所以小数据只需进行少数比较和交换即可到正确位置

一个更好理解的希尔排序实现&#xff1a;将数组列在一个表中并对列排序(用插入排序)。重复这过程&#xff0c;不过每次用更长的列来进行。最后整个表就只有一列了。

将数组转换至表是为了更好地理解这算法&#xff0c;算法本身仅仅对原数组进行排序(通过增加索引的步长&#xff0c;例如是用i &#43;&#61; step_size而不是i&#43;&#43;)。

例子

例如&#xff0c;假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ]&#xff0c;如果我们以步长为5开始进行排序&#xff0c;我们可以通过将这列表放在有5列的表中来更好地描述算法&#xff0c;这样他们就应该看起来是这样&#xff1a;

13 14 94 33 8225 59 94 65 2345 27 73 25 3910

然后我们对每列进行排序&#xff1a;

10 14 73 25 2313 27 94 33 3925 59 94 65 8245

将上述四行数字&#xff0c;依序接在一起时我们得到&#xff1a;[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。

这时10已经移至正确位置了&#xff0c;然后再以3为步长进行排序&#xff1a;

10 14 7325 23 1327 94 3339 25 5994 65 8245

排序之后变为&#xff1a;

10 14 1325 23 3327 25 5939 65 7345 94 8294

最后以1步长进行排序(此时就是简单的插入排序了)。

步长序列如何选择&#xff1f;

Donald Shell 最初建议步长选择为 n/2 并且对步长取半直到步长达到1。

虽然这样取可以比 O(n^2) 类的算法(插入排序)更好&#xff0c;但这样仍然有减少平均时间和最差时间的余地。

已知的最好步长序列是由 Sedgewick 提出的(1, 5, 19, 41, 109,…)&#xff0c;

另一个在大数组中表现优异的步长序列是(斐波那契数列除去0和1将剩余的数以黄金分割比的两倍的幂进行运算得到的数列)&#xff1a;

(1, 9, 34, 182, 836, 4025, 19001, 90358, 428481, 2034035, 9651787, 45806244, 217378076, 1031612713,…)

java 代码实现

实现

这里为了简单&#xff0c;我们步长直接选择列表长度的一半&#xff0c;并且依次折半。

import com.github.houbb.log.integration.core.Log;import com.github.houbb.log.integration.core.LogFactory;import com.github.houbb.sort.core.util.InnerSortUtil;import java.util.List;/** * 希尔排序 * * &#64;author binbin.hou * &#64;since 0.0.6 */public class ShellSort extends AbstractSort {    private static final Log log &#61; LogFactory.getLog(ShellSort.class);    &#64;Override    &#64;SuppressWarnings("all")    protected void doSort(List> original) {        // 步长从大到小        for(int step &#61; original.size()/2; step > 0; step /&#61; 2) {            // 从第 step 个元素&#xff0c;逐个执行插入排序            for(int i &#61; step; i &#61; 0) && InnerSortUtil.lt(original, j, j-step)) {                    // 执行交换                    InnerSortUtil.swap(original, j, j-step);                    if(log.isDebugEnabled()) {                        log.debug("step: {}, j: {}, j-step: {}, list: {}",                                step, j, j-step, original);                    }                    // 更新步长                    j -&#61; step;                }            }        }    }}

整体实现也不难&#xff0c;大家可以回顾下 插入排序

这里为了便于大家理解&#xff0c;我们特意添加了日志。

归并排序(英语&#xff1a;Merge sort&#xff0c;或mergesort)

是创建在归并操作上的一种有效的排序算法&#xff0c;效率为 O(nlogn)(大O符号)。1945年由约翰·冯·诺伊曼首次提出。

该算法是采用分治法(Divide and Conquer)的一个非常典型的应用&#xff0c;且各层分治递归可以同时进行。

概述

采用分治法:

分割&#xff1a;递归地把当前序列平均分割成两半。

集成&#xff1a;在保持元素顺序的同时将上一步得到的子序列集成到一起(归并)。

java 实现递归法

递归法(Top-down)
  1. 申请空间&#xff0c;使其大小为两个已经排序序列之和&#xff0c;该空间用来存放合并后的序列
  2. 设定两个指针&#xff0c;最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素&#xff0c;选择相对小的元素放入到合并空间&#xff0c;并移动指针到下一位置
  4. 重复步骤3直到某一指针到达序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾

java 实现

实际上代码实现也不难&#xff0c;不过递归多多少少让人看起来不太习惯。

我们后面会结合测试日志&#xff0c;再进行讲解。

package com.github.houbb.sort.core.api;import com.github.houbb.log.integration.core.Log;import com.github.houbb.log.integration.core.LogFactory;import java.util.ArrayList;import java.util.List;/** * 归并排序-递归实现 * * &#64;author binbin.hou * &#64;since 0.0.7 */public class MergeRecursiveSort extends AbstractSort {    private static final Log log &#61; LogFactory.getLog(MergeRecursiveSort.class);    &#64;Override    &#64;SuppressWarnings("all")    protected void doSort(List> original) {        // 存放归并的结果        // 直接将数组填满&#xff0c;避免 set 出现越界        List> resultList &#61; new ArrayList<>(original);        sortRecursive(original, resultList, 0, original.size()-1);    }    /**     * 递归排序     * &#64;param originalList 原始列表     * &#64;param resultList 存放结果的列表     * &#64;param startIx 开始     * &#64;param endIx 结果     * &#64;since 0.0.7     */    &#64;SuppressWarnings("all")    private void sortRecursive(List originalList,                               List resultList,                               int startIx,                               int endIx) {        // 循环结束        if(startIx >&#61; endIx) {            return;        }        // 找到中间位置&#xff0c;将列表一分为二        int midIx &#61; (startIx&#43;endIx) / 2;        int leftStart &#61; startIx;        int leftEnd &#61; midIx;        int rightStart &#61; midIx&#43;1;        int rightEnd &#61; endIx;        if(log.isDebugEnabled()) {            log.debug("拆分&#xff1a;ls: {}, le: {}, rs: {}, re: {}",                    leftStart, leftEnd, rightStart, rightEnd);        }        // 递归调用        sortRecursive(originalList, resultList, leftStart, leftEnd);        sortRecursive(originalList, resultList, rightStart, rightEnd);        if(log.isDebugEnabled()) {            log.debug("操作&#xff1a;ls: {}, le: {}, rs: {}, re: {}",                    leftStart, leftEnd, rightStart, rightEnd);        }        // 这里需要通过 k 记录一下开始的位置        int k &#61; startIx;        while (leftStart <&#61; leftEnd && rightStart <&#61; rightEnd) {            //相对小的元素放入到合并空间&#xff0c;并移动指针到下一位置            Comparable left &#61; (Comparable) originalList.get(leftStart);            Comparable right &#61; (Comparable) originalList.get(rightStart);            // 左边较小&#xff0c;则放入合并空间            if(left.compareTo(right) 

java 迭代实现

相信很多小伙伴都知道迭代可以使得代码变得简洁&#xff0c;但是会让调试和理解变得复杂。

我们来一起学习一下迭代的实现方式。

迭代法(Bottom-up)

原理如下(假设序列共有 n 个元素)&#xff1a;

  1. 将序列每相邻两个数字进行归并操作&#xff0c;形成 ceil(n/2) 个序列&#xff0c;排序后每个序列包含两/一个元素
  2. 若此时序列数不是1个则将上述序列再次归并&#xff0c;形成 ceil(n/4) 个序列&#xff0c;每个序列包含四/三个元素
  3. 重复步骤2&#xff0c;直到所有元素排序完毕&#xff0c;即序列数为1

迭代实现

相对递归&#xff0c;这个代码就要显得复杂很多。

不过这种迭代的方式性能更好&#xff0c;实现如下。

package com.github.houbb.sort.core.api;import com.github.houbb.log.integration.core.Log;import com.github.houbb.log.integration.core.LogFactory;import com.github.houbb.sort.core.util.InnerSortUtil;import java.util.ArrayList;import java.util.List;/** * 归并排序-迭代实现 * * &#64;author binbin.hou * &#64;since 0.0.7 */public class MergeSort extends AbstractSort {    private static final Log log &#61; LogFactory.getLog(MergeSort.class);    &#64;Override    protected void doSort(List> original) {        // 存放归并的结果        // 直接将数组填满&#xff0c;避免 set 出现越界        List> resultList &#61; new ArrayList<>(original);        //起始&#xff0c;子序列长度为1。对长度为1的序列进行两两合并        int k &#61; 1;        final int length &#61; original.size();        while (k (len-2*k&#43;1),所以根本没有进入while循环        while (i 

counting sort 计数排序

计数排序(Counting sort)是一种稳定的线性时间排序算法。

该算法于1954年由 Harold H. Seward 提出。

通过计数将时间复杂度降到了 O(N)。

基础版

算法步骤
  1. 找出原数组中元素值最大的&#xff0c;记为max。
  2. 创建一个新数组count&#xff0c;其长度是max加1&#xff0c;其元素默认值都为0。
  3. 遍历原数组中的元素&#xff0c;以原数组中的元素作为count数组的索引&#xff0c;以原数组中的元素出现次数作为count数组的元素值
  4. 创建结果数组result&#xff0c;起始索引index。
  5. 遍历count数组&#xff0c;找出其中元素值大于0的元素&#xff0c;将其对应的索引作为元素值填充到result数组中去&#xff0c;每处理一次&#xff0c;count中的该元素值减1&#xff0c;直到该元素值不大于0&#xff0c;依次处理count中剩下的元素。
  6. 返回结果数组 result。

java 实现

package com.github.houbb.sort.core.api;import com.github.houbb.heaven.annotation.ThreadSafe;import com.github.houbb.log.integration.core.Log;import com.github.houbb.log.integration.core.LogFactory;import com.github.houbb.sort.core.util.InnerSortUtil;import java.util.ArrayList;import java.util.Arrays;import java.util.List;/** * 计数排序 * * &#64;author binbin.hou * &#64;since 0.0.8 */&#64;ThreadSafepublic class CountingSortBasic extends AbstractSort {    private static final Log log &#61; LogFactory.getLog(CountingSortBasic.class);    &#64;Override    &#64;SuppressWarnings("all")    public void doSort(List original) {        //1. 获取最大的元素        int max &#61; Integer.MIN_VALUE;        for (Object object : original) {            Integer integer &#61; (Integer) object;            max &#61; Math.max(max, integer);        }        //2. 构建 count 列表        int[] counts &#61; new int[max &#43; 1];        //3.遍历原数组中的元素&#xff0c;以原数组中的元素作为count数组的索引&#xff0c;以原数组中的元素出现次数作为count数组的元素值。        for (Object object : original) {            Integer integer &#61; (Integer) object;            counts[integer]&#43;&#43;;        }        //4. 结果构建        int index &#61; 0;        // 遍历计数数组&#xff0c;将计数数组的索引填充到结果数组中        for (int i &#61; 0; i  0) {                // i 实际上就是元素的值                // 从左到右遍历&#xff0c;元素自然也就排序好了。                // 相同的元素会出现多次&#xff0c;所以才需要循环。                original.set(index&#43;&#43;, i);                counts[i]--;                if(log.isDebugEnabled()) {                    log.debug("结果数组&#xff1a;{}", original);                }            }        }    }}

改良版

空间浪费

实际上我们创建一个比最大元素还要大1的数组&#xff0c;只是为了放下所有的元素而已。

但是它有一个缺陷&#xff0c;那就是存在空间浪费的问题。

比如一组数据{101,109,102,110}&#xff0c;其中最大值为110&#xff0c;按照基础版的思路&#xff0c;我们需要创建一个长度为111的计数数组&#xff0c;但是我们可以发现&#xff0c;它前面的[0,100]的空间完全浪费了&#xff0c;那怎样优化呢&#xff1f;

将数组长度定为 max-min&#43;1&#xff0c;即不仅要找出最大值&#xff0c;还要找出最小值&#xff0c;根据两者的差来确定计数数组的长度。

改良版本实现

import com.github.houbb.heaven.annotation.ThreadSafe;import com.github.houbb.log.integration.core.Log;import com.github.houbb.log.integration.core.LogFactory;import java.util.Arrays;import java.util.List;/** * 计数排序 * * &#64;author binbin.hou * &#64;since 0.0.8 */&#64;ThreadSafepublic class CountingSort extends AbstractSort {    private static final Log log &#61; LogFactory.getLog(CountingSort.class);    &#64;Override    &#64;SuppressWarnings("all")    public void doSort(List original) {        //1. 获取最大、最小的元素        int max &#61; Integer.MIN_VALUE;        int min &#61; Integer.MAX_VALUE;        for (Object object : original) {            Integer integer &#61; (Integer) object;            max &#61; Math.max(max, integer);            min &#61; Math.min(min, integer);        }        //2. 构建 count 列表        int[] counts &#61; new int[max-min &#43; 1];        //3.遍历原数组中的元素&#xff0c;以原数组中的元素作为count数组的索引&#xff0c;以原数组中的元素出现次数作为count数组的元素值。        for (Object object : original) {            Integer integer &#61; (Integer) object;            // 元素要减去最小值&#xff0c;再作为新索引            counts[integer-min]&#43;&#43;;        }        if(log.isDebugEnabled()) {            log.debug("counts.length: {}", counts.length);        }        //4. 结果构建        int index &#61; 0;        // 遍历计数数组&#xff0c;将计数数组的索引填充到结果数组中        for (int i &#61; 0; i  0) {                // i 实际上就是元素的值                // 从左到右遍历&#xff0c;元素自然也就排序好了。                // 相同的元素会出现多次&#xff0c;所以才需要循环。                // 这里将减去的最小值统一加上                original.set(index&#43;&#43;, i&#43;min);                counts[i]--;                if(log.isDebugEnabled()) {                    log.debug("结果数组&#xff1a;{}", original);                }            }        }    }}

自己的思考

算法的本质

这个算法的本质是什么呢&#xff1f;

个人理解只需要保证两点&#xff1a;

(1)每一个元素&#xff0c;都有自己的一个元素位置

(2)相同的元素&#xff0c;次数会增加。

算法的巧妙之处在于直接利用数值本身所谓索引&#xff0c;直接跳过了排序比较&#xff1b;利用技数&#xff0c;解决了重复数值的问题。

算法的不足

这个算法的巧妙之处&#xff0c;同时也是对应的限制&#xff1a;那就是只能直接比较数字。如果是字符串呢&#xff1f;

一点想法

我最初的想法就是可以使用类似于 HashMap 的数据结构。这样可以解决元素过滤&#xff0c;次数统计的问题。

但是无法解决排序问题。

当然了&#xff0c;如果使用 TreeMap 就太赖皮了&#xff0c;因为本身就是利用了树进行排序。

TreeMap 版本

我们这里使用 TreeMap 主要有下面的目的&#xff1a;

(1)让排序不局限于数字。

(2)大幅度降低内存的浪费&#xff0c;不多一个元素&#xff0c;也不少一个元素。

思想实际上依然是技术排序的思想。

package com.github.houbb.sort.core.api;import com.github.houbb.heaven.annotation.ThreadSafe;import com.github.houbb.log.integration.core.Log;import com.github.houbb.log.integration.core.LogFactory;import java.util.List;import java.util.Map;import java.util.TreeMap;/** * 计数排序-TreeMap * * &#64;author binbin.hou * &#64;since 0.0.8 */&#64;ThreadSafepublic class CountingSortTreeMap extends AbstractSort {    private static final Log log &#61; LogFactory.getLog(CountingSortTreeMap.class);    &#64;Override    &#64;SuppressWarnings("all")    public void doSort(List original) {        TreeMap countMap &#61; new TreeMap<>();        // 初始化次数        for (Object object : original) {            Comparable comparable &#61; (Comparable) object;            Integer count &#61; countMap.get(comparable);            if(count &#61;&#61; null) {                count &#61; 0;            }            count&#43;&#43;;            countMap.put(comparable, count);        }        //4. 结果构建        int index &#61; 0;        // 遍历计数数组&#xff0c;将计数数组的索引填充到结果数组中        for (Map.Entry entry : countMap.entrySet()) {            int count &#61; entry.getValue();            Comparable key &#61; entry.getKey();            while (count > 0) {                // i 实际上就是元素的值                // 从左到右遍历&#xff0c;元素自然也就排序好了。                // 相同的元素会出现多次&#xff0c;所以才需要循环。                original.set(index&#43;&#43;, key);                count--;            }        }    }}

桶排序(Bucket sort)

或所谓的箱排序&#xff0c;是一个排序算法&#xff0c;工作的原理是将数组分到有限数量的桶里。

每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。

桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候&#xff0c;桶排序使用线性时间 O(n)。

桶排序是计数排序的扩展版本&#xff0c;计数排序可以看成每个桶只存储相同元素&#xff0c;而桶排序每个桶存储一定范围的元素&#xff0c;通过映射函数&#xff0c;将待排序数组中的元素映射到各个对应的桶中&#xff0c;对每个桶中的元素进行排序&#xff0c;最后将非空桶中的元素逐个放入原序列中。

桶排序需要尽量保证元素分散均匀&#xff0c;否则当所有数据集中在同一个桶中时&#xff0c;桶排序失效。

算法流程

桶排序以下列程序进行&#xff1a;

  1. 设置一个定量的数组当作空桶子。
  2. 寻访序列&#xff0c;并且把项目一个一个放到对应的桶子去。
  3. 对每个不是空的桶子进行排序。
  4. 从不是空的桶子里把项目再放回原来的序列中。
53e1543399594dc3be336c9303482b1a

算法

java 实现

实现

package com.github.houbb.sort.core.api;import com.github.houbb.heaven.annotation.ThreadSafe;import com.github.houbb.log.integration.core.Log;import com.github.houbb.log.integration.core.LogFactory;import java.util.ArrayList;import java.util.Collections;import java.util.List;/** * 桶排序 * * &#64;author binbin.hou * &#64;since 0.0.9 */&#64;ThreadSafepublic class BucketSort extends AbstractSort {    private static final Log log &#61; LogFactory.getLog(BucketSort.class);    &#64;Override    &#64;SuppressWarnings("all")    public void doSort(List original) {        final int step &#61; 10;        // 计算最小值        int min &#61; Integer.MAX_VALUE;        int max &#61; Integer.MIN_VALUE;        for(Object object : original) {            Integer integer &#61; (Integer) object;            min &#61; Math.min(min, integer);            max &#61; Math.max(max, integer);        }        //2. 计算桶的个数        int bucketNum &#61; (max-min) / step &#43; 1;;        //2.1 初始化临时列表        List> tempList &#61; new ArrayList<>(bucketNum);        for(int i &#61; 0; i ());        }        //3. 将元素放入桶中        // 这里有一个限制&#xff1a;要求元素必须一个左边的桶元素&#xff0c;要小于右边的桶。        // 这就限制了只能是数字类的比较&#xff0c;不然没有范围的概念        for(Object o : original) {            Integer integer &#61; (Integer) o;            int index &#61; (integer-min) / step;            tempList.get(index).add(integer);        }        // 4. 针对单个桶进行排序        // 可以选择任意你喜欢的算法        for(int i &#61; 0; i  integers &#61; tempList.get(i);            for(Integer val : integers) {                original.set(index&#43;&#43;, val);            }        }    }}

开源地址

为了便于大家学习&#xff0c;上面的排序已经开源&#xff0c;开源地址&#xff1a;

https://github.com/houbb/sort

欢迎大家 fork/star&#xff0c;鼓励一下作者~~

小结

希望本文对你有帮助&#xff0c;如果有其他想法的话&#xff0c;也可以评论区和大家分享哦。

各位极客的点赞收藏转发&#xff0c;是老马持续写作的最大动力&#xff01;

135b7338307f4206a78a7707edeaac1e



推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
author-avatar
俊廷淑芳淑幸
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有