十大排序——11.十大排序的比较汇总及Java中自带的排序算法

news2024/11/24 20:41:26

这篇文章对排序算法进行一个汇总比较!

目录

0.十大排序汇总

0.1概述

0.2比较和非比较的区别

0.3基本术语

0.4排序算法的复杂度及稳定性

1.冒泡排序

算法简介

动图演示

代码演示

应用场景

算法分析

2.快速排序

算法简介

动图演示

代码演示

应用场景

算法分析

3.插入排序

算法简介

动图演示

代码演示

应用场景

算法分析

4.选择排序

算法简介

动图演示

代码演示

应用场景

算法分析

5.归并排序

算法简介

图片演示

代码演示

应用场景

算法分析

6.希尔排序

算法简介

图片演示

代码演示

应用场景

算法分析

7.堆排序

算法简介

图片演示

代码演示

应用场景

算法分析

8.计数排序

算法分析

动图演示

代码演示

应用场景

算法分析

9.基数排序

算法分析

动图演示

代码演示

应用场景

算法分析

10.桶排序

算法分析

动图演示

代码演示

应用场景

算法分析

11.Java自带的排序算法


0.十大排序汇总

0.1概述

一图以蔽之:

0.2比较和非比较的区别

常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置。


冒泡排序之类的排序中,问题规模为 n,又因为需要比较 n 次,所以平均时间复杂度为O(n²)。

归并排序、快速排序之类的排序中,问题规模通过分治法消减为 logN 次,所以时间复杂度平均O(nlogn)。


比较排序的优势是:适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。


计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组 arr,计算 arr 之前有多少个元素,则唯一确定了 arr 在排序后数组中的位置。


非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n)。


非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。

0.3基本术语

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能出现在b的后面;
内排序:所有的排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度:一个算法执行所消耗的时间;
空间复杂度:运行完一个程序所需内存的大小。

0.4排序算法的复杂度及稳定性

1.冒泡排序

算法简介

1. 从左向右依次对比相邻元素,将较大值交换到右边;
2. 每一轮循环可将最大值交换到最左边
3. 重复1.2两个步骤,直至完成整个数组。

动图演示

代码演示

package Sorts;

import java.util.Arrays;
//冒泡排序
public class MaoPaoSort {
    public static void main(String[] args) {
        int[] arr = new int []{5,9,2,7,3,1,10};
        maoPaoSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void maoPaoSort(int[]arr){
        for (int i = 0; i <arr.length ; i++) {
            for (int j = 0; j <arr.length-1 ; j++) {
                if (arr[j] > arr[j+1]){//这是比较相邻的元素
                    int temp = arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                }
            }
        }
    }
}

应用场景

一般在学习的时候作为理解排序原理的时候使用,在实际应用中不会使用

算法分析

最佳情况:T ( n ) = O ( n )

最差情况:T ( n ) = O ( n^2 )

平均情况:T ( n ) = O ( n^2 )

2.快速排序

算法简介

  1. 选择一个元素赋值给中间元素temp,默认选择左边第一个元素,这样左边第一个元素的位置就空出来了;
  2. 先从最右边的元素开始依次跟temp比较大小,大于等于temp元素的原地不动,遇到小于temp元素的则终止循环,把该元素赋值到左侧空出来的位置,同时左侧索引值自增,这是该元素原来的位置就空出;
  3. 然后左侧元素开始依次跟temp比较大小,小于等于temp元素的原地不动,遇到大雨temp元素的则终止循环,把该元素赋值到右侧空出来的位置,同时右侧索引值自减;
  4. 依次循环2,3步,直至左侧索引等于右侧索引,则完成一轮循环,把哨兵赋值到该索引的位置。
  5. 在分别递归地对 temp 左右两侧的子数组进行1234步,直至递归子数组只有一个元素则排序完成

动图演示

代码演示

package Sorts;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
//快排
public class QuickSort {
    public static void main(String[] args) {
        int[] array = {16,13,7,15,28,11,9,32,22,19};
        int[] array1 = {4,2,1,3,2,4};
        System.out.println("排序前:"+ Arrays.toString(array1));
        sort(array1);
        System.out.println("排序后:"+ Arrays.toString(array1));

    }
    /**
     * 交换操作
     * */
    private static void swap(int[] array,int i,int j){
        int t = array[i];
        array[i] = array[j];
        array[j] = t;
    }
    /**
     * 排序的函数
     * */
    private static void sort(int[] array) {
        quick(array,0,array.length-1);
    }
    /**
     * 递归的函数
     * */
    private static void quick(int[] array, int left, int right) {
        //结束递归的条件,如果我们左边的索引大于或等于右边的索引了(最多只能等于,不可能大于),那说明就只有一个元素的了,那就不用递归了
        if (left>=right){
            return;
        }
        int p = partition4(array,left,right); //p代表基准点元素的索引
        quick(array,left,p-1); // 对基准点左边的区进行递归操作
        quick(array,p+1,right); // 对基准点右边的区进行递归操作
    }
    /**
     * 分区并进行比较然后排序的函数(这部分是核心代码)
     * 单边快排
     * */
    private static int partition1(int[] array, int left, int right) {
        int pv = array[right]; //基准点的值
        int i = left;
        int j = left;
        while (j<right){ //当j小于右边届的时候,j就要+1
            if (array[j]<pv){ //j找到比基准点小的值
                if (i!=j){
                    swap(array,i,j);
                }
                /**
                 * 这里多说一点
                 * i与j都是从left开始的,初始指向是一致的,i找大的,j找小的
                 * 进入这个判断,就是说明j找到小的了
                 * 没进入这个判断,就是说明j没有找到小的,也就是说此时j指向的值大于等于基准值
                 * 因为i与j的指向在初始时是一致的
                 * 所以没进入这个判断时,i指向的值就比基准值大了
                 * 所以i就找到了,不用+1了
                 * 但是j没有找到
                 * 所以j要+1
                 * 这就是i++在里面;j++在外面的原因
                 * 如果进入这个判断,即j找到小的了,此时i在哪?
                 * i在j前面或者和i在同一个位置,看代码就能想清楚
                 * 有没有可能j找到小的了,然后不走了,i没找到继续走,然后在j后面找到大的了?
                 * 这种情况就没必要交换了,并且这种情况进不来这个判断
                 * */
                i++;
            }
            j++;
        }
        swap(array,i,right); //交换基准点与i的位置,此时i记录的就是基准点的位置
        return i;
    }
    /**
     * 双边快排
     * */
    private static int partition2(int[] array, int left, int right) {
        int pv = array[left]; //基准点的值
        int i = left; //游标i,从最左边开始,找大的
        int j = right; //游标j,从最右边开始,找小的

        while (i < j){ //i<j的时候进行循环,一旦i=j或i>j,就要退出循环
            while (i < j && array[j] > pv){//找比基准点小的值,没找到j就--,一旦找到,就退出循环
                j--;
            }
//            while (i<j && array[i]<pv)
            while (i<j && array[i]<=pv){//找比基准点大的值,没找到i就++,一旦找到,就退出循环
                i++;
            }
            swap(array,i,j);//交换
        }
        swap(array,left,i);
        return i;
    }
    /**
     * 定义随机的基准点
     * */
    private static int partition3(int[] array, int left, int right) {
        int idx = ThreadLocalRandom.current().nextInt(right-left+1)+left;//生成范围内的随机数
        swap(array,idx,left);//交换随机数与left的值
        int pv = array[left];//基准点的值
        int i = left; //游标i,从最左边开始,找大的
        int j = right; //游标j,从最右边开始,找小的

        while (i < j){ //i<j的时候进行循环,一旦i=j或i>j,就要退出循环
            while (i < j && array[j] > pv){//找比基准点小的值,没找到j就--,一旦找到,就退出循环
                j--;
            }
            while (i<j && array[i]<=pv){//找比基准点大的值,没找到i就++,一旦找到,就退出循环
                i++;
            }
            swap(array,i,j);//交换
        }
        swap(array,left,i);
        return i;
    }
    /**
     * 改进后的算法
     * 为了处理重复的元素
     * */
    private static int partition4(int[] array, int left, int right) {
        int pv = array[left];//基准点的值
        int i = left+1; //游标i,从left+1开始,找大的
        int j = right; //游标j,从最右边开始,找小的

        while (i <= j){ //i<=j的时候进行循环,一旦i>j,就要退出循环,当i=j的时候也要进入循环
            while (i <= j && array[j] > pv){//找比基准点小或等于的值,没找到j就--,一旦找到,就退出循环
                j--;
            }
            while (i<=j && array[i] < pv){//找比基准点大或相等的值,没找到i就++,一旦找到,就退出循环
                i++;
            }
            if (i<=j){
                swap(array,i,j);//交换
                i++;
                j--;
            }
        }
        swap(array,left,j);
        return j;
    }
}

应用场景

算法分析

最佳情况:T ( n ) = O ( nlogn )

最差情况:T ( n ) = O ( n^2 )

平均情况:T ( n ) = O ( nlogn )

3.插入排序

算法简介

  1. 左边第一个元素可作为有序子数组;
  2. 从第二个元素开始,依次向前比较,大于该元素的则向右移一位,直到比该元素小的元素,插入其后;
  3. 依次向后推进,直至整个数组成为有序数组

动图演示

代码演示

package Sorts;

import java.util.Arrays;
//插入排序
public class InsertSort {
    public static void main(String[] args) {
        int [] arr = new int[]{5,9,2,7,3,1,10};
        Insert2(arr);
        System.out.println(Arrays.toString(arr));
    }
    //递归版的方法
    public static void Insert1(int[]arr, int low){
        if (low == arr.length)
            return;
        int t = arr[low];
        int i = low-1;
        //从右向左找插入位置,如果比待插入元素大,则不断右移,空出插入位置
        while (i >= 0 && t <arr[i]){
            arr[i+1] = arr[i];
            i--;
        }
        //找到插入位置
        if (i+1 != low){
            arr[i+1] = t;
        }
        Insert1(arr,low+1);
    }

    public static void Insert2(int[]arr){
        for (int low = 1; low < arr.length-1 ; low++) {
            int t = arr[low];
            int i = low-1;
            //从右向左找插入位置,如果比待插入元素大,则不断右移,空出插入位置
            while (i >= 0 && t <arr[i]){
                arr[i+1] = arr[i];
                i--;
            }
            //找到插入位置
            if (i+1 != low){
                arr[i+1] = t;
            }
        }
    }
}

应用场景

如果大部分数据距离它正确的位置很近或者近乎有序?例如银行的业务完成的时间
如果是这样的话,插入排序是更好的选择。

算法分析

最佳情况:T ( n ) = O ( n )

最坏情况:T ( n ) = O ( n^2 )

平均情况:T ( n ) = O ( n^2 )

4.选择排序

算法简介

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

动图演示

代码演示

package Sorts;

import java.util.Arrays;
//选择排序
public class ChooseSort {
    public static void main(String[] args) {
        int [] arr = new int [] {5,9,2,7,3,1,10};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void sort(int []arr){
        for (int i = 0; i <arr.length ; i++) {//从第一个索引开始,遍历数组
            for (int j = i; j <arr.length ; j++) {//从当前索引位置开始,遍历后面的数组
                if (arr[j]<arr[i]){//后面的数组元素比它小,就交换他两的位置(这两元素不相邻)
                    int temp = arr[i];
                    arr[i]=arr[j];
                    arr[j]=temp;
                }
            }
        }
    }
}

应用场景

当数据量较小的时候适用

算法分析

最佳情况:T ( n ) = O ( n^2 )

最差情况:T ( n ) = O ( n^2 )

平均情况:T ( n ) = O ( n^2 )

5.归并排序

算法简介

分:

  1. 一直分两组,分别对两个数组进行排序(根据上层对下层在一组的数据通过临时数组排序,再将有序数组挪回上层数组中)。
  2.  循环第一步,直到划分出来的“小组”只包含一个元素,只有一个元素的数组默认为已经排好序。

合:(合并时,站在上层合并下层(使组内有序))

  1. 将两个有序的数组合并到一个大的数组中。
  2. 从最小的只包含一个元素的数组开始两两合并。此时,合并好的数组也是有序的。最后把小组合成一个组。

图片演示

代码演示

package Sorts;

import java.util.Arrays;
//归并排序
public class Merge {
    public static void main(String[] args) {
        int[] array = new int[]{13,56,2,8,19,34,29};
        System.out.println("排序前:"+ Arrays.toString(array));
        mergeSort(array,0,array.length-1);
        System.out.println("排序后:"+Arrays.toString(array));
    }
    public static void mergeSort(int[] array, int low, int height){
        if (low >= height)
            return;
        int mid = (low+height)>>>1;
        mergeSort(array,low,mid);
        mergeSort(array,mid+1,height);
        merge(array,low,mid,height);
    }
    public static void merge(int[] array,int low,int mid,int height){
        int[] ret = new int[height-low+1];
        int i = 0;//新数组的索引
        int s1 = low;//前一个分段的初始位置
        int s2 = mid+1;//后一个分段的初始位置

        while (s1<=mid && s2<=height){
            if (array[s1]<=array[s2]){//比较元素的大小
                ret[i++] = array[s1++];//赋值给新数组,然后索引++
            }else {
                ret[i] = array[s2];
                i++;
                s2++;
            }
        }
        while (s1<=mid){//将前半段剩下的全部赋值到新数组中
            ret[i++] = array[s1++];
        }
        while (s2<=height){//将后半段剩下的全部赋值到新数组中
            ret[i++] = array[s2++];
        }
        for (int j = 0; j < ret.length; j++) {//将新数组中的元素全部挪到原数组中
            array[j+low] = ret[j];
        }
    }
}

应用场景

内存空间不足的时候使用归并排序,能够使用并行计算的时候使用归并排序。

算法分析

最佳情况:T ( n ) = O ( n )

最差情况:T ( n ) = O ( nlogn )

平均情况:T ( n ) = O ( nlogn )

6.希尔排序

算法简介

  1. 首先选择一个步长值gap,以步长值为间隔把数组分为gap个子数组gap=length/2
  2. 对每个子数组进行插入排序;
  3. 逐步减小步长 gap = gap/2,重复对数组进行1,2 步骤;
  4. 当步长值减为1时,相当于对数组进行一次直接插入排序。

图片演示

代码演示

package Sorts;

import java.util.Arrays;
//希尔排序
public class XiErSort {
    public static void main(String[] args) {
        int [] arr = new int []{5,9,2,7,3,1,10};
        xiEr(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void xiEr(int arr[]){
        for (int gap = arr.length/2; gap >0 ; gap /=2) {
            for (int low = gap; low <arr.length ; low++) {
                int t = arr[low];
                int i = low - gap;
                //自右向左找插入位置,如果比待插入的元素大,则不断右移,空出插入位置
                while (i >= 0 && t < arr[i]){
                    arr[i+gap] = arr[i];
                    i-=gap;
                }
                //找到插入位置
                if(i != low-gap){
                    arr[i+gap] = t;
                }
            }
        }
    }
}

应用场景

相对于直接插入排序,希尔排序要高效很多,因为当gap 值较大时,对子数组进行插入排序时要移动的元素很少,元素移动的距离很大,这样效率很高;在gap逐渐减小过程中,数组中元素已逐渐接近排序的状态,所以需要移动的元素逐渐减少;当gap为1时,相当于进行一次直接插入排序,但是各元素已接近排序状态,需要移动的元素很少且移动的距离都很小。

算法分析

最佳情况:T ( n ) = O ( nlog2 n )

最坏情况:T ( n ) = O ( nlog2 n )

平均情况:T ( n ) = O ( nlog2 n )

7.堆排序

算法简介

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

堆排序可以说是一种利用堆的概念来排序的选择排序。

分为两种方法:

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

图片演示

步骤一 :构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
  a.假设给定无序序列结构如下

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

此处必须注意,我们把 6 和 9 比较交换之后,必须考量 9 这个节点对于其子节点会不会产生任何影响?因为其是叶子节点,所以不加考虑;

但是,一定要熟练这种思维,写代码的时候就比较容易理解为什么会出现一次非常重要的交换了。

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

在真正代码的实现中,这时候4和9交换过后,必须考虑9所在的这个节点位置,因为其上的值变了,必须判断对其的两个子节点是否造成了影响,这么说不合适,实际上就是判断其作为根节点的那棵子树,是否还满足大根堆的原则,每一次交换,都必须要循环把子树部分判别清楚。

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

牢记上面说的规则,每次交换都要把改变了的那个节点所在的树重新判定一下,这里就用上了,4和9交换了,变动了的那棵子树就必须重新调整,一直调整到符合大根堆的规则为截。

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

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

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

这里,必须说明一下,所谓的交换,实际上就是把最大值从树里面拿掉了,剩下参与到排序的树,其实只有总结点的个数减去拿掉的节点个数了。所以图中用的是虚线。

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

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

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

代码演示

package Sorts;
//堆排
public class HeapSort {
    public static void main(String[] args) {
        int[] arr = {16, 7, 3, 20, 17, 8};
        heapSort(arr);
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }
    /**
     * 创建堆
     */
    private static void heapSort(int[] arr) {
        //创建堆
        for (int i = (arr.length - 1) / 2; i >= 0; i--) {
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr, i, arr.length);
        }

        //调整堆结构+交换堆顶元素与末尾元素
        for (int i = arr.length - 1; i > 0; i--) {
            //将堆顶元素与末尾元素进行交换
            int temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;
            //重新对堆进行调整
            adjustHeap(arr, 0, i);
        }
    }
    /**
     * 调整堆
     * @param arr 待排序列
     * @param parent 父节点
     * @param length 待排序列尾元素索引
     */
    private static void adjustHeap(int[] arr, int parent, int length) {
        //将temp作为父节点
        int temp = arr[parent];
        //左孩子
        int lChild = 2 * parent + 1;
        while (lChild < length) {
            //右孩子
            int rChild = lChild + 1;
            // 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
            if (rChild < length && arr[lChild] < arr[rChild]) {
                lChild++;
            }
            // 如果父结点的值已经大于孩子结点的值,则直接结束
            if (temp >= arr[lChild]) {
                break;
            }
            // 把孩子结点的值赋给父结点
            arr[parent] = arr[lChild];
            //选取孩子结点的左孩子结点,继续向下筛选
            parent = lChild;
            lChild = 2 * lChild + 1;
        }
        arr[parent] = temp;
    }
}

应用场景

堆排序适合于数据量非常大的场合(百万数据)。
堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。
堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。

算法分析

最佳情况:T ( n ) = O ( nlogn )

最差情况:T ( n ) = O ( nlogn )

平均情况:T ( n ) = O ( nlogn )

8.计数排序

算法分析

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

动图演示

代码演示

package Sorts;

import java.util.Arrays;

//计数排序
public class CountSort {
    
    public static void main(String[] args) {
        int[] arr = {5,1,1,1,3,0};
        System.out.println(Arrays.toString(arr));
        countSort1(arr);
        System.out.println(Arrays.toString(arr));
    }

    //数组中元素>=0
    private static void countSort1(int[] arr) {
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max)
                max = arr[i];
        }

        int[] count = new int[max+1];

        for (int v : arr) { //原始数组的元素
            count[v]++;
        }
        int k = 0;
        for (int i = 0; i < count.length; i++) {
            // i 代表原始数组中的元素,count[i]代表出现次数
            while (count[i]>0){
                arr[k++] = i;
                count[i]--;
            }
        }
    }

    //不限制数组中元素的大小,可以有负数
    private static void countSort2(int[] arr) {
        //让数组中的最小值映射到数组的count[0]位置,最大值映射到count的最右侧
        int min = arr[0];
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max)
                max = arr[i];
            if (arr[i] < min)
                min = arr[i];
        }

        int[] count = new int[max - min + 1];

        //原始数组元素 - 最小值 = count 索引
        for (int v : arr) { //原始数组的元素
            count[v - min]++;
        }
        int k = 0;
        for (int i = 0; i < count.length; i++) {
            // i+min 代表原始数组中的元素,count[i]代表出现次数
            while (count[i]>0){
                arr[k++] = i + min;
                count[i]--;
            }
        }
    }
}

应用场景

计数排序需要占用大量空间,它仅适用于数据比较集中的情况。比如 [0,100],[10000,19999] 这样的数据。

算法分析

最佳情况:T ( n ) = O ( n+k )

最差情况:T ( n ) = O ( n+k )

平均情况:T ( n ) = O ( n+k )

9.基数排序

算法分析

相比其它排序,主要是利用比较和交换,而基数排序则是利用分配和收集两种基本操作。基数排序是一种按记录关键字的各位值逐步进行排序的方法。此种排序一般适用于记录的关键字为整数类型的情况。所有对于字符串和文字排序不适合。

实现:将所有待比较数值(自然数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
基数排序的两种方式:
高位优先,又称为最有效键(MSD),它的比较方向是由右至左;
低位优先,又称为最无效键(LSD),它的比较方向是由左至右;

动图演示

代码演示

package Sorts;

import java.util.Arrays;

//基数排序
public class RadixSort {

    public static void main(String[] args) {
        int [] arr = new int[]{193,255,12,6,78,50,31,564};
        radixSort(arr);;
        System.out.println(Arrays.toString(arr));

    }

    public static void radixSort(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }

        int length = array.length;

        // 每位数字范围0~9,基为10
        int radix = 10;
        int[] aux = new int[length];
        int[] count = new int[radix + 1];
        // 以关键字来排序的轮数,由位数最多的数字决定,其余位数少的数字在比较高位时,自动用0进行比较
        // 将数字转换成字符串,字符串的长度就是数字的位数,字符串最长的那个数字也拥有最多的位数
        int x = Arrays.stream(array).map(s -> String.valueOf(s).length()).max().getAsInt();

        // 共需要d轮计数排序, 从d = 0开始,说明是从个位开始比较,符合从右到左的顺序
        for (int d = 0; d < x; d++) {
            // 1. 计算频率,在需要的数组长度上额外加1
            for (int i = 0; i < length; i++) {
                // 使用加1后的索引,有重复的该位置就自增
                count[digitAt(array[i], d) + 1]++;
            }
            // 2. 频率 -> 元素的开始索引
            for (int i = 0; i < radix; i++) {
                count[i + 1] += count[i];
            }

            // 3. 元素按照开始索引分类,用到一个和待排数组一样大临时数组存放数据
            for (int i = 0; i < length; i++) {
                // 填充一个数据后,自增,以便相同的数据可以填到下一个空位
                aux[count[digitAt(array[i], d)]++] = array[i];
            }
            // 4. 数据回写
            for (int i = 0; i < length; i++) {
                array[i] = aux[i];
            }
            // 重置count[],以便下一轮统计使用
            for (int i = 0; i < count.length; i++) {
                count[i] = 0;
            }

        }
    }

    //Description: 根据d,获取某个值的个位、十位、百位等,d = 0取出个位,d = 1取出十位,以此类推。对于不存在的高位,用0补
    private static int digitAt(int value, int d) {
        return (value / (int) Math.pow(10, d)) % 10;
    }
}

应用场景

算法分析

最佳情况:T ( n ) = O ( n * k )

最差情况:T ( n ) = O ( n * k )

平均情况:T ( n ) = O ( n * k )

10.桶排序

算法分析

桶排序(Bucket Sort)的原理很简单,它是将数组分到有限数量的桶子里。

假设待排序的数组a中共有N个整数,并且已知数组a中数据的范围[0, MAX)。在桶排序时,创建容量为MAX的桶数组r,并将桶数组元素都初始化为0;将容量为MAX的桶数组中的每一个单元都看作一个"桶"。
在排序时,逐个遍历数组a,将数组a的值,作为"桶数组r"的下标。当a中数据被读取时,就将桶的值加1。例如,读取到数组a[3]=5,则将r[5]的值+1。

动图演示

代码演示

package Sorts;

import java.util.*;

//桶排
public class BucketSort {
    public static void main(String[] args) {
        int [] arr = new int[]{193,255,12,6,78,50,31,564};
        bucketSort(arr);;
        System.out.println(Arrays.toString(arr));

    }

    public static void bucketSort(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }

        // 建立桶,个数和待排序数组长度一样
        int length = array.length;

        LinkedList<Integer>[] bucket = (LinkedList<Integer>[]) new LinkedList[length];

        // 待排序数组中的最大值
        int maxValue = Arrays.stream(array).max().getAsInt();
        // 根据每个元素的值,分配到对应范围的桶中
        for (int i = 0; i < array.length; i++) {
            int index = toBucketIndex(array[i], maxValue, length);
            // 没有桶才建立桶(延时)
            if (bucket[index] == null) {
                bucket[index] = new LinkedList<>();
            }
            // 有桶直接使用
            bucket[index].add(array[i]);
        }

        // 对每个非空的桶排序,排序后顺便存入临时的List,则list中已经有序)
        List<Integer> temp = new ArrayList<>();
        for (int i = 0; i < length; i++) {
            if (bucket[i] != null) {
                Collections.sort(bucket[i]);
                temp.addAll(bucket[i]);
            }
        }

        // 将temp中的数据写入原数组
        for (int i = 0; i < length; i++) {
            array[i] = temp.get(i);
        }
    }

    private static int toBucketIndex(int value, int maxValue, int length) {
        return (value * length) / (maxValue + 1);
    }

    /**
     *
    public static void bucketSort(int arr[] ){
        //定义二维数组
        int [][] bucket = new int[10][arr.length];
        //定义记录桶中数据个数的数组
        int [] bucketElement = new int[10];

        for (int i = 0; i <arr.length ; i++) {
            int element = arr[i] % 10;
            bucket[element][bucketElement[element]] = arr[i];
            bucketElement[element]++;
        }

        int index = 0;

        for (int k = 0; k <bucketElement.length ; k++) {
            if (bucketElement[k]!=0){
                for (int l = 0; l <bucketElement [k] ; l++) {
                    arr[index] = bucket[k][l];
                    index++;
                }
            }
            bucketElement[k]=0;
        }
    }
     */
}

应用场景

算法分析

桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。


最佳情况:T ( n ) = O ( n+k )

最差情况:T ( n ) = O ( n+k )

平均情况:T ( n ) = O ( n^2 )

11.Java自带的排序算法

JDK7-13:

JDK14-20:

  • 其中TimSort是用归并 + 二分插入排序的混合排序算法
  • 值得注意的是从JDK8开始支持 Arrays.parallelSort 并行排序
  • 根据最新的提交记录来看JDK21可能会引入基数排序等优化

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

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

相关文章

AGI的智力有可能在两年内超过人类水平

特斯拉CEO埃隆马斯克近日与挪威银行投资管理基金CEO坦根的访谈中表示&#xff0c;AGI的智力将在两年内可能超过人类智力&#xff0c;在未来五年内&#xff0c;AI的能力很可能超过所有人类。 马斯克透漏&#xff0c;去年人工智能发展过程中的主要制约因素是缺少高性能芯片&#…

信息集成:打通数据、深化业务协同与生产调度,降本增效第一步 | 创新场景...

ITValue 痛点 对于三一集团这样生产线复杂的大型组织&#xff0c;一辆重卡的生产由底盘、动力、车身、座舱、智驾网联等多个不同区域组成&#xff0c;各自使用工具与业务系统&#xff0c;彼此之间的数据各自孤立。如何打通业务部门的信息传递&#xff0c;深化业务协同与生产调度…

第11章 数据仓库和数据智能知识点梳理

第11章 数据仓库和数据智能知识点梳理&#xff08;附带页码&#xff09; ◼ 数据仓库&#xff08;Data Warehouse&#xff0c;DW&#xff09;&#xff1a;始于 20 世纪 80 年代&#xff0c;发展于 20 世纪 90 年代&#xff0c;后与商务智能&#xff08;Business Inteligence,BI…

【前后端的那些事】SpringBoot 基于内存的ip访问频率限制切面(RateLimiter)

文章目录 1. 什么是限流2. 常见的限流策略2.1 漏斗算法2.2 令牌桶算法2.3 次数统计 3. 令牌桶代码编写4. 接口测试5. 测试结果 1. 什么是限流 限流就是在用户访问次数庞大时&#xff0c;对系统资源的一种保护手段。高峰期&#xff0c;用户可能对某个接口的访问频率急剧升高&am…

mac 最小化全部程序回到桌面(基于alfred workflow)

前言 换到 mac 系统之后&#xff0c;很多快捷键根本就不好用&#xff0c;组合太多了&#xff0c;除了 cmd Q/W/A/S/X/R/Z/C/V &#xff0c;个人认为其它的真的一坨屎。像我的需求就是&#xff0c;开的窗口太多了&#xff0c;我需要全部最小化&#xff0c;再重新打开我需要那个…

Samtec应用分享 | 汽车应用中的视觉系统

【前言】 视觉系统在未来的汽车设计中扮演着关键的角色。 在过去&#xff0c;一直是由驾驶员掌握和应对道路上的危险&#xff0c;但现代车辆在保障驾驶安全方面发挥着前所未有的作用。 视觉系统&#xff0c;无论是可见光摄像头还是先进的探测系统&#xff0c;如激光雷达&…

nmap、john、tcpdump

Kali是基于Debian的Linux发行版&#xff0c;Kali Linux包含上百个安全相关工具&#xff0c;如渗透测试、安全检测、密码安全、反向工程等。 扫描&#xff1a;获取一些公开、非公开信息为目的&#xff1b;检查潜在的风险、查找可攻击的目标、收集设备/主机/系统/软件信息、发现可…

Elasticsearch:下载、启动和账号密码登录

因为我的电脑是 window&#xff0c;以下都是以 window 环境举例。 一、下载 Elasticsearch 是使用 java 开发的&#xff0c;且 7.8 版本的 ES 需要 JDK 版本 1.8 以上&#xff0c;安装前注意java环境的准备。 官网地址&#xff1a;https://www.elastic.co/cn/ 下载地址&#xf…

ros2的hello工程

第一步&#xff1a;在任意一个目录下创建功能包 ros2 pkg create --build-type ament_cmake ros2_demo --dependencies rclcpp std_msgsros2 pkg create --build-type ament_python learning_pkg_python # Python ros2命令中&#xff1a; pkg&#xff1a;表示功能包相关的功能…

【HCIP学习】OSPF协议基础

一、OSPF基础 1、技术背景&#xff08;RIP中存在的问题&#xff09; RIP中存在最大跳数为15的限制&#xff0c;不能适应大规模组网 周期性发送全部路由信息&#xff0c;占用大量的带宽资源 路由收敛速度慢 以跳数作为度量值 存在路由环路可能性 每隔30秒更新 2、OSPF协议…

使用python采集VIP会电商平台商品数据②单页数据采集

获取标头&#xff1a; 响应&#xff1a;服务器返回的数据 预览&#xff1a;查看响应得数据 载荷&#xff1a;查询参数/请求参数 标头&#xff1a;查看请求的网址/请求的标头/请求方法 数据包地址&#xff1a;https://mapi.vip.com/vips-mobile/rest/shopping/pc/product/mo…

2024五一杯数学建模A题思路分析

文章目录 1 赛题思路2 比赛日期和时间3 组织机构4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

如何在TestNG中生成报告

在这篇文章中&#xff0c;我们将学习如何在TestNG中生成测试报告。我们将使用ReportNG生成报告&#xff0c;因为它生成的报告比默认的TestNG报告更好。 我们将遵循一些简单的步骤来生成报告。 添加ReportNG依赖项 <!-- https://mvnrepository.com/artifact/org.uncommons…

GPT 浅析

GPT 浅析 文章目录 GPT 浅析GPT 1无监督预训练有监督微调任务相关的输入变换 GPT2GPT3 GPT 1 在模型架构上&#xff0c;GPT-1基于Transformer构造&#xff0c;这是因为与其他卷积神经网 络或者循环神经网络相比&#xff0c;Transformer提供了效率更高的方法来处理文本 中的长期…

kkFileView基于pdf.js实现多词高亮

参考文档&#xff1a; 1.文件文档在线预览转换解决方案和应用 2.kkfileview预览pdf格式文件&#xff0c;实现多关键词高亮和定位_kkfileview高亮方案-CSDN博客 3.PDF.js实现搜索多个不同的关键词高亮显示效果 最终效果&#xff1a; 需求描述&#xff1a; 预览文件时&#xff0…

Kafka服务端(含Zookeeper)一键自启软件

1. 前言 本文介绍了一款集成图形化界面配置和一键自启功能的Kafka与Zookeeper服务管理软件。该软件通过直观易用的图形界面&#xff0c;使用户能够轻松完成Kafka和Zookeeper的配置工作&#xff0c;有效避免了手动编辑配置文件可能带来的错误和不便。同时&#xff0c;软件还提供…

Django中的实时通信:WebSockets与异步视图的结合【第167篇—实时通信】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在现代Web应用程序中&#xff0c;实时通信已经成为了必不可少的功能之一。无论是在线聊天、…

云计算: OVN 集群 部署分布式交换机

目录 一、实验 1.环境 2.OVN 集群 部署云主机 3.中心端添加DVS分布式大二层交换机 二、问题 1.南向控制器查看主机名只显示localhost 2.中心端如何添加DVR分布式⼤三层路由器 一、实验 1.环境 (1) 主机 表1 宿主机 主机架构软件主要服务IP备注ovn_central中心端 ovn…

Unity 点击次数统计功能

介绍 &#x1f4a1;.调用方便&#xff0c;发生点击事件后直接通过"xxx".CacheClick缓存 &#x1f4a1;. 在允许的时间间隔内再次点击会累计点击次数&#xff0c;直到超出后触发事件 传送门&#x1f448;

WebLogic 数据源连接泄露

编码时&#xff0c;有时会忘记释放使用的数据源连接&#xff0c;造成连接泄露&#xff0c;没有连接资源可用。 现象 java.sql.SQLException: Cannot obtain XAConnectionat weblogic.jdbc.jta.DataSource.refreshXAConnAndEnlist(DataSource.java:1691)at weblogic.jdbc.jta.…