十大排序算法详解(带动图演示)

目录

一、算法概述

二、冒泡排序

1.冒泡排序基础知识

 1.1 普通冒泡排序

 1.2 优化冒泡排序

三、快速排序

1.快速排序基础知识

1.1 Hoare法

1.2 挖坑法

1.3 前后指针法

2.快速排序实现 

2.1混合排序优化

2.2三数取中法优化

3.完整代码

4.非递归实现快速排序

四、简单插入排序

1.插入排序基础知识

五、希尔排序

1.希尔排序基础知识

六、选择排序

1.选择排序基础知识

1.1选择排序优化

七、堆排序

1.堆排序基础知识

八、归并排序

 1.归并排序基础知识

九、计数排序

 1.计数排序基础知识

十、桶排序

 1.桶排序基础知识

十一、基数排序

1.基数排序基础知识

十二、十大排序复杂度总结

一、算法概述

十种常见排序算法可以分为两大类:

  • 比较类排序:比较类排序是一类基于比较操作来进行排序的算法,它们的实现核心是比较两个元素的大小关系。通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  • 非比较类排序:非比较类排序是不依赖于元素之间大小关系进行排序的算法。它们通常需要通过额外的空间来记录元素的状态,从而进行排序。它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。 

十大排序算法详解(带动图演示)_第1张图片

 相关概念

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
  • 时间复杂度:时间复杂度是衡量算法运行时间长短的一种指标,常用大O符号表示。时间复杂度描述的是算法在处理数据规模增大时,运行时间的增长趋势,表示算法的执行效率。
  • 空间复杂度:空间复杂度是算法需要使用的内存空间的大小,也常用大O符号表示。空间复杂度与算法的时间复杂度类似,描述的是算法在处理数据规模增大时,所需内存空间的增长趋势。

二、冒泡排序

1.冒泡排序基础知识

冒泡排序是一种简单的排序算法,它的基本思想是比较相邻的两个元素,如果它们的顺序错误就交换它们。具体实现过程如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个。
  2. 对每一对相邻的元素重复上述步骤,直到最后一对元素。此时,最后的元素一定是序列中最大的元素。
  3. 针对所有的元素重复上述步骤,除了最后一个已经排好序的元素。这样每次排序都会少一个元素,直到所有元素都排好序。

 此处引用一张经典的冒泡排序动图进行演示:

 1.1 普通冒泡排序

​
//冒泡排序普通版
    public static void bubbleSort(int[] array){
        for (int i = 0; i < array.length-1; i++) {           
            for (int j = 0 ; j < array.length-1-i; j++) {
                if (array[j]>array[j+1]){
                    swap(array,j,j+1);
                    flog = true;
                }
            }
        }
    }

​

我们观察这个普通冒泡排序可以发现,有些数据排序后 ,后面的数组元素已经处于有序状态,此后所有的比较都不必进行 ,所以我们需要对他进行优化

 1.2 优化冒泡排序

如果某次比较过程中,发现没有任何元素移动,则不再进行接下来的比较。具体的做法是在每趟比较时,引入一个boolean型变量flog,来判断下次比较还有没有必要进行。示例代码如下:

//冒泡排序
    public static void bubbleSort(int[] array){
        for (int i = 0; i < array.length-1; i++) {
            boolean flog = false;
            for (int j = 0 ; j < array.length-1-i; j++) {
                if (array[j]>array[j+1]){
                    swap(array,j,j+1);
                    flog = true;
                }
            }
            if (flog == false){
                return;
            }
        }
    }

该变量的作用是“假如本趟比较过程中没有交换发生,则不必进行下一趟比较”

冒泡排序的特点总结

1.冒泡排序算法很容易理解和掌握,但是它在处理一组很大无序的数据时效率极低。

2.时间复杂度O(N^2)——将逆序转成顺序,O(1)——本身就有序,空间复杂度O(1)

3.是一种稳定的排序

4.冒泡排序适用于数据量很小的排序场景,因为冒泡的实现方式较为简单。

三、快速排序

1.快速排序基础知识

快速排序(Quicksort)是一种快速且高效的排序算法,被广泛应用于数据处理的各个领域。它是一种基于分治的算法,其基本思想是通过一次排序将待排数组分割成独立的两部分,其中一部分的所有元素都比另一部分小,然后再按此方法对这两部分分别进行排序,以达到整个序列有序的目的。快速排序的基本步骤如下:

  1. 选取一个基准元素(pivot)作为枢轴值,一般选取数组开头或结尾的元素。
  2. 将序列中比基准元素小的元素都放到基准元素的前面,比基准元素大的元素都放到基准元素的后面。这个过程成为分区。
  3. 分别对分区后的小序列和大序列递归地执行步骤1和步骤2,直到序列中只剩下一个元素或没有元素。

快速排序的时间复杂度为O(nlogn),但是最坏情况下时间复杂度可以达到O(n^2)。空间复杂度为O(logn),因为递归调用的层数为logn。

快速排序动图演示:

快速排序最关键在于划分区域 下面将讲解三种不同的方法划分区域

1.1 Hoare法

它的基本思想是选取数组的头部元素为基准元素,同时在数组的两端设置两个指针left和right,指向数组的首尾位置,然后从右向左扫描数组,寻找第一个小于等于基准元素的数,然后从左向右扫描数组,寻找第一个大于等于基准元素的数,将这两个数字进行交换,重复这个过程,直到left和right相遇。最后将left和right相遇的位置left和基准元素i进行交换,此时左侧的数都比基准元素小,右侧的数都比基准元素大

此时我们需要注意的是为什么要从数组尾部开始扫描 , 因为如果从头开始进行的话 那么我右边一定是停止在比基准大的数据上 ,所以此时如果左边走过来 相遇 ,那么会将这个值放到基准值的位置, 那么此时就不满足左侧的数都比基准元素小,右侧的数都比基准元素大了,挖坑法也是同样的. 所以我们需要让右边先走.

//Hoare法
    private static int partition2(int[] array,int left,int right) {
        int tmp = array[left];
        int i = left;
        while (left < right) {
            while (left< right && array[right] >= tmp) {
                right--;
            }
            while (left< right && array[left] <= tmp) {
                left++;
            }
            swap(array,left,right);
        }
        swap(array,left,i);
        return  left;
    }

1.2 挖坑法

  1. 选取数组的开头元素为基准元素,将基准元素挖出一个坑,即将array[left]赋值给tmp变量保存,并将array[left]位置作为空位。

  2. 从数组的尾部开始向前扫描(从right开始),找到第一个小于基准元素的数,将该数填到上一个坑中(即array[left])。

  3. 继续从数组开头向后扫描(从left+1开始),找到第一个大于基准元素的数,将该数填到上一个坑中(即array[right])。

  4. 重复步骤2、3,直到left=right时,将基准元素填入坑中(即arr[left]=tmp)。

  5. 对左右两个子序列分别进行递归排序。             

    //挖坑法
    private static int partition(int[] array,int left,int right) {
        int tmp = array[left];
        while (left < right) {
            while (left< right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while (left< right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }

1.3 前后指针法

  1. 选取数组的最左边元素为基准元素pivot。

  2. 定义二个指针prev和cur.

  3. 从cur开始遍历数组,如果遇到小于等于pivot的元素,就将其和所指的下一个元素交换,并将prev指针向右移动一个位置。

  4. 遍历结束后,将基准元素pivot和left所指定的元素交换。

  5. 对左右两个子序列分别进行递归排序。

    //前后指针法 
    private static int partition3(int[] array,int left,int right) {
        int prev = left ;
        int cur = left+1;
        while (cur <= right) {
            if(array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array,cur,prev);
            }
            cur++;
        }
        swap(array,prev,left);
        return prev;
    }

2.快速排序实现 

 接下来我们可以对快速排序进行代码实现了

public static void quickSort(int[] array) {
        quick(array,0,array.length-1);
    
}

private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        int pivot = partition(array,start,end);//划分
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
   
 }

2.1混合排序优化

我们发现,当给一组有序且很大的数据时,如果用快速排序就会造成栈溢出,溢出的原因就是因为快速排序在递归的时候要开辟内存空间,而递归的次数受树高度的影响。给定一组有序序列,在快排时就等同于只有左树或者只有右树,这样递归的次数就是这个序列的长度,因此给定的序列越大,递归次数越多就会越容易栈溢出。所以我们采用混合排序对其及进行优化 .当快排到数组趋于有序的时候 ,我们采用直接快速排序 ,这样就可以在一定程度上减缓栈溢出的概率

    public static void quickSort1(int[] array) {
        quick(array,0,array.length-1);
    }
    private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }

        //使用这个优化 主要解决 减少递归的次数
        if(end - start + 1 <= 14) {
            //插入排序
            insertSort2(array,start,end);
            return;
        }

        int pivot = partition(array,start,end);//划分
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }

    private static void insertSort2(int[] array,int left,int right) {
        for (int i = left+1; i <= right; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= left ; j--) {
                if(array[j] > tmp) {
                    array[j+1] = array[j];
                }else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

2.2三数取中法优化

该方法指的是选取基准值时,不再取固定位置(如第一个元素、最后一个元素)的值,因为这种固定取值的方式在面对随机输入的数组时,效率是非常高的。但是一旦输入数据是有序的,使用固定位置取值,效率就会非常低。因此此时引入了三数取中,即在数组中随机选出三个元素,然后取三者的中间值做为基准值。

    private static int midThree(int[] array,int left,int right) {
        int mid = (left+right) / 2;
        //6  8
        if(array[left] < array[right]) {
            if(array[mid] < array[left]) {
                return left;
            }else if(array[mid] > array[right]) {
                return right;
            }else {
                return mid;
            }
        }else {
            //array[left] > array[right]
            if(array[mid] < array[right]) {
                return right;
            }else if(array[mid] > array[left]) {
                return left;
            }else {
                return mid;
            }
        }

    }

3.完整代码

    public static void quickSort1(int[] array) {
        quick(array,0,array.length-1);
    }
    private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }

        //混合排序减少递归的次数
        if(end - start + 1 <= 14) {
            //插入排序
            insertSort2(array,start,end);
            return;
        }
        //三数取中法
        int index = midThree(array, start,end);
        swap(array,index,start);

        int pivot = partition(array,start,end);//划分
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }

    private static void insertSort2(int[] array,int left,int right) {
        for (int i = left+1; i <= right; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= left ; j--) {
                if(array[j] > tmp) {
                    array[j+1] = array[j];
                }else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

    private static int midThree(int[] array,int left,int right) {
        int mid = (left+right) / 2;
        //6  8
        if(array[left] < array[right]) {
            if(array[mid] < array[left]) {
                return left;
            }else if(array[mid] > array[right]) {
                return right;
            }else {
                return mid;
            }
        }else {
            //array[left] > array[right]
            if(array[mid] < array[right]) {
                return right;
            }else if(array[mid] > array[left]) {
                return left;
            }else {
                return mid;
            }
        }

    }
    //挖坑法
    private static int partition(int[] array,int left,int right) {
        int tmp = array[left];
        while (left < right) {
            while (left< right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while (left< right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }
    //Hoare法
    private static int partition2(int[] array,int left,int right) {
        int tmp = array[left];
        int i = left;
        while (left < right) {
            while (left< right && array[right] >= tmp) {
                right--;
            }
            while (left< right && array[left] <= tmp) {
                left++;
            }
            swap(array,left,right);
        }
        swap(array,left,i);
        return  left;
    }

    //前后指针法 
    private static int partition3(int[] array,int left,int right) {
        int prev = left ;
        int cur = left+1;
        while (cur <= right) {
            if(array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array,cur,prev);
            }
            cur++;
        }
        swap(array,prev,left);
        return prev;
    }

4.非递归实现快速排序

快速排序通常使用递归实现,但也可以使用非递归的方法实现。非递归实现快速排序的基本思路是使用一个栈来保存子区间的起始和终止下标,每次取出栈顶的子区间进行分区(使用双指针法或前后指针法求基准),然后将分区后的左右子区间的下标入栈,直到栈为空时排序结束。

//快排非递归实现
    public static void quickSort3(int[] array) {
        Deque stack = new LinkedList<>();

        int left = 0;
        int right = array.length-1;

        stack.push(left);
        stack.push(right);

        while (!stack.isEmpty()){
            right = stack.pop();
            left = stack.pop();
            int pivot = partition(array,left,right);
            if (pivot > left+1){
                stack.push(left);
                stack.push(pivot-1);
            }
            if (pivot < right-1){
                stack.push(pivot+1);
                stack.push(right);
            }
        }
    }

快速排序的特点总结

1.时间复杂度O(N*logN)——所有的左右子序列刚好是整体序列的一半时

2.空间复杂度O(logN)——满二叉树的高度

3.不稳定

 四、简单插入排序

1.插入排序基础知识

插入排序是一种简单直观的排序算法,也是我们常用的一种排序方法。其基本思想是:将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增加1的有序表。插入排序的实现过程就像人们玩扑克牌时将手中的牌一张一张地插入到已经有序的牌堆中一样。

插入排序的过程可以分为两个部分:第一部分是建立有序序列,第二部分是将元素插入到有序序列中。

思路:

1.首先判断数组的长度,长度<=1,直接返回

2.将数组第i(>=1)个下标位置的元素放在temp中

3.将j下标位置的元素与temp比较。当小于时退出循环,执行4;当大于时,将j位置的元素赋给j+1,然后j--,然后重复这一步直到退出循环(退出循环的结果有两种)

4.将temp的位置赋给j+1下标的值

动图演示:

//    直接插入排序
    public static void insertSort(int[] array){
        for (int i = 1; i < array.length; i++) {

            int tmp = array[i];
            int j = i-1;
            for (; j >= 0 ; j--) {
                if (array[j] > tmp){
                    array[j+1] = array[j];
                }else {
                    break;
                }
            }
            array[j+1] = tmp;
        }

    }

直接插入排序总结:

1.时间复杂度:O(N^2)(逆序的数据)、O(N)(数据本身就是有序的)

2.空间复杂度O(1)

3.适用于数据量小且数据本身趋于有序的情况

4.是一种稳定排序算法 

五、希尔排序

1.希尔排序基础知识

希尔排序(Shell Sort)是插入排序的一种改进版本,也称为“缩小增量排序”,是一种非稳定的排序算法。希尔排序的核心思想是将原序列按一定间隔分成若干子序列,对每个子序列进行插入排序,缩小间隔,再进行插入排序。希尔排序的具体步骤如下:

  1. 首先,设定一个增量ghp,一般取序列长度的一半,然后将序列分成 ghp个子序列。

  2. 对于每个子序列,进行插入排序。

  3. 将 ghp 缩小为原来的一半,重新分组并进行插入排序,直到 ghp 缩小为 1,此时整个序列就变成了一个有序序列。

引用一张经典的希尔排序动图演示:

十大排序算法详解(带动图演示)_第2张图片

//希尔排序
    public static void shellSort(int[] array){
        int ghp = array.length;
        while (ghp>1){
            shell(array,ghp);
            ghp/=2;
        }
        shell(array,ghp);
    }


    public static void shell(int[] array, int ghp){
        for (int i = ghp; i < array.length; i++) {
            int tmp = array[i];
            int j = i-ghp;
            for (; j >= 0 ; j-=ghp) {
                if (array[j] > tmp){
                    array[j+ghp] = array[j];
                }else {
                    break;
                }
            }
            array[j+ghp] = tmp;
        }
    }

希尔排序总结:

1.是对直接插入排序的优化

2.希尔排序中gap的取法很多,但是我们一般都取序列长度的一半或者按照质素来取。

3.时间复杂度:O(n^1.3)~O(n^1.5)

4.不稳定排序 

六、选择排序

1.选择排序基础知识

选择排序(Selection Sort)是一种简单直观的排序算法。它的基本思想是:给定一个序列,每次从中选出最大或最小的元素,并将该元素放到序列的最前面或最后面,然后对剩余的元素重复该过程,直到整个序列有序为止。

思路:

1.创建一个零时变量minIndex,让minIndex记录i下标

2.遍历数组,找到最小(最大)的一个数的下标j,将minIndex记录为j。如果这个最小的数就是第一个(或最后一个)数,就不动,否者让minIndex下标的数和第一个数交换。

3.重复1~3直到i>array.length

 动图演示:

    //选择排序
    public static void selectSort(int[] array){

        for (int i = 0; i < array.length; i++) {
            int minIndex = i;
            for (int j = i+1; j 

1.1选择排序优化

选择排序的优化思路一般是在一趟遍历中,同时找出最大值与最小值,放到数组两端,这样就能将遍历的趟数减少一半。第一次选择最大值与最小值,过程如下

十大排序算法详解(带动图演示)_第3张图片

代码如下: 

    public static void selectSort2(int[] array){
        int left = 0;
        int right = array.length-1;

        while (leftarray[max]){
                    max = i;
                }
            }
            swap(array , left ,min);
            if (left == max){
                max = min;
            }
            swap(array , right ,max);
            left++;
            right--;
        }
    }

选择排序的特点总结

  1. 选择排序的效率很低
  2. 时间复杂度O(N^2),空间复杂度O(1)
  3. 不稳定

七、堆排序

1.堆排序基础知识

堆排序(Heap Sort)是一种基于堆数据结构的排序算法,它的基本思想是:将待排序序列构造成一个大顶堆或小顶堆,然后依次取出堆顶元素放到已排序序列的末尾,直到整个序列有序为止。

动图演示: 

 

 代码如下:(此处以创建 一个大根堆为例 ,如需创建小根堆请参考下面链接https://blog.csdn.net/qq_62274623/article/details/131770274?spm=1001.2014.3001.5501)

    //堆排序

    public static void heapSort(int[] array){
        creatBigHeap(array);
        int end = array.length-1;
        while (end>0){
            swap(array, 0 ,end);
            shiftDown(array,0,end);
            end--;
        }
    }

    public static void creatBigHeap(int[] array){
        for (int parent = (array.length-1-1)/2; parent >= 0 ; parent--) {
            shiftDown(array ,parent,array.length);
        }
    }

    public static void shiftDown(int[] array , int parent , int len){

        int child = 2*parent+1;
        while (childarray[parent]){
                swap(array,child,parent);
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }

    }

堆排序的特点总结:

  1. 效率很高
  2. 时间复杂度O(N*logN),空间复杂度O(1)
  3. 不稳定 

 八、归并排序

 1.归并排序基础知识

归并排序(Merge Sort)是一种经典的、稳定的、基于比较的排序算法。它的基本思想是将待排序序列不断地分成两个子序列,对每个子序列进行排序,然后将两个有序子序列合并成一个有序序列,最终得到排序后的完整序列。

归并排序的步骤:

1>创建临时数组,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2>设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3>比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4>重复步骤 3 直到某一指针达到序列尾;
5>将另一序列剩下的所有元素直接复制到合并序列尾。

 动图演示:

十大排序算法详解(带动图演示)_第4张图片

代码实现递归版本: 

 public static void mergeSort(int[] array){
        mergeFunc(array,0,array.length-1);
    }

    public static void mergeFunc(int[] array,int left,int right){
        if (left >= right){
            return;
        }

        int mid = (left+right)/2;
        mergeFunc(array,left,mid);
        mergeFunc(array,mid+1,right);
        merge(array,left,right,mid);
    }
                                          ///   0       1       0
    public static void merge(int[] array,int start,int end,int mid){
        int s1 = start;//0
        int s2 = mid+1;//1

        int[] tmp = new int[end-start+1]; //2
        int k = 0; //表示tmp数组的下标
        while (s1 <= mid && s2 <= end){
            if (array[s1] <= array[s2]){
                tmp[k++] = array[s1++];
            }else{
                tmp[k++] = array[s2++];
            }
        }
        while (s1 <= mid){
            tmp[k++] = array[s1++];
        }
        while (s2 <= end){
            tmp[k++] = array[s2++];
        }
        for (int i = 0; i 

  2.非递归归并排序

此时我们可以认为数组是单个单个有序的 ,然后将他们变成两个两个有序的 ,在这个情况下在将他们变成四个四个有序的.以此类推代码如下

    public static void mergeSort2(int[] array) {
        int gap = 1;
        while (gap < array.length) {
            // i += gap * 2 当前gap组的时候,去排序下一组
            for (int i = 0; i < array.length; i += gap * 2) {
                int left = i;
                int mid = left+gap-1;//有可能会越界
                if(mid >= array.length) {
                    mid = array.length-1;
                }
                int right = mid+gap;//有可能会越界
                if(right>= array.length) {
                    right = array.length-1;
                }
                merge(array,left,right,mid);
            }
            //当前为2组有序  下次变成4组有序
            gap *= 2;
        }
    }

  九、计数排序

 1.计数排序基础知识

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

  1. 找出原数组中元素值最大的,记为max ,最小值,记为min。
  2. 创建一个新数组count,其长度是max-min+1,其元素默认值都为0。
  3. 遍历原数组中的元素,以原数组中的元素作为count数组的索引,以原数组中的元素出现次数作为count数组的元素值。
  4. 创建索引index,作为array数组的新下标
  5. 遍历count数组,找出其中元素值大于0的元素,将其对应的索引作为元素值填充到array数组中去,每处理一次,count中的该元素值减1,直到该元素值不大于0,依次处理count中剩下的元素。

动图演示: 

 代码如下:

    public static void countSort(int[] array) {
        //1. 遍历数组 找到最大值 和 最小值
        int max = array[0];
        int min = array[0];
        //O(N)
        for (int i = 1; i < array.length; i++) {
            if(array[i] < min) {
                min = array[i];
            }
            if(array[i] > max) {
                max = array[i];
            }
        }
        //2. 根据范围 定义计数数组的长度
        int len = max - min + 1;
        int[] count = new int[len];
        //3.遍历数组,在计数数组当中 记录每个数字出现的次数 O(N)
        for (int i = 0; i < array.length; i++) {
            count[array[i]-min]++;
        }
        //4.遍历计数数组
        int index = 0;// array数组的新的下标 O(范围)
        for (int i = 0; i < count.length; i++) {
            while (count[i] > 0) {
                //这里要加最小值  反应真实的数据
                array[index] = i+min;
                index++;
                count[i]--;
            }
        }
    }

 计数排序特点总结:

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限
  2. 时间复杂度:O(MAX(N,范围))
  3. 空间复杂度:O(范围)
  4. 是一种稳定的排序

  十、桶排序

 1.桶排序基础知识

桶排序是一种线性排序算法,它通过将要排序的元素分配到若干个桶中,对每个桶中的元素进行排序,最后将每个桶中的元素按顺序依次收集起来,即可得到有序序列。

具体过程如下:

  1. 首先确定桶的数量以及每个桶所对应的元素取值范围。

  2. 将要排序的元素逐个放入对应的桶中。

  3. 对每个桶中的元素进行排序(可以选择任意排序算法,如冒泡排序、插入排序等)。

  4. 将每个桶中的元素按顺序依次收集起来,即可得到有序序列。

引用一张经典的动图演示:

public static void bucketSort(int[] arr) {
    int n = arr.length;

    // 定义桶的数量
    int bucketNum = 5;

    // 初始化桶
    List[] buckets = new ArrayList[bucketNum];
    for (int i = 0; i < bucketNum; i++) {
        buckets[i] = new ArrayList<>();
    }

    // 将每个元素放入对应的桶中
    for (int i = 0; i < n; i++) {
        int index = arr[i] / bucketNum;
        buckets[index].add(arr[i]);
    }

    // 对每个桶中的元素进行排序,并将排序后的元素放回原数组
    int k = 0;
    for (int i = 0; i < bucketNum; i++) {
        int size = buckets[i].size();
        if (size > 0) {
            // 如果桶中只有一个元素,则无需排序,直接放回原数组
            if (size == 1) {
                arr[k++] = buckets[i].get(0);
            } else {
                // 对桶中的元素进行排序
                int[] temp = new int[size];
                for (int j = 0; j < size; j++) {
                    temp[j] = buckets[i].get(j);
                }
                Arrays.sort(temp);

                // 将排序后的元素放回原数组
                for (int j = 0; j < size; j++) {
                    arr[k++] = temp[j];
                }
            }
        }
    }
}

桶排序特点总结:

  1. 时间复杂度O(N)
  2. 空间复杂度O(N+M)
  3. 不稳定 

  十一、基数排序

1.基数排序基础知识

基数排序(Radix Sort)是一种非比较排序算法,它将整数按位数切割成不同的数字,然后按每个位数分别排序。基数排序的时间复杂度为O(d(n+k)),其中d是最大数字的位数,n是待排序数组的元素个数,k是每个桶中的数字个数。基数排序是一种稳定的排序算法,适用于范围较小的整数排序。

基数排序的实现:

  1. 找到待排序数组中最大的数,并计算出它的位数d。

  2. 从低位到高位,依次遍历每一位,将元素按照该位上的数字进行排序。

  3. 重复第二步,直到对数组的最高位排序完成。

具体实现可以使用桶排序的思想:先按照数组中每个元素的最低位进行排序,将它们存入对应的桶中;然后依次按照每个元素的下一位排序,重复这个过程,直到完成对最高位的排序。排序后,再将桶中的元素按照顺序合并到一个新的数组中,即得到了排好序的数组。

引用一张经典的动图演示:

public static void radixSort(int[] arr) {
    if (arr == null || arr.length == 0) {
        return;
    }
    int max = arr[0];
    for (int i = 1; i < arr.length; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    int d = 1;
    while (max / d > 0) {
        int[] temp = new int[10];
        for (int i = 0; i < arr.length; i++) {
            int index = (arr[i] / d) % 10;
            temp[index]++;
        }
        for (int i = 1; i < temp.length; i++) {
            temp[i] += temp[i - 1];
        }
        int[] result = new int[arr.length];
        for (int i = arr.length - 1; i >= 0; i--) {
            int index = (arr[i] / d) % 10;
            result[temp[index] - 1] = arr[i];
            temp[index]--;
        }
        for (int i = 0; i < arr.length; i++) {
            arr[i] = result[i];
        }
        d *= 10;
    }
}

基数排序特点总结:

  1. 时间复杂度O(n)
  2. 当元素取值范围较大,但元素个数较少时可以利用基数排序 

十二、十大排序复杂度总结

十大排序算法详解(带动图演示)_第5张图片

如有错误 , 还请指正,欢迎阅读.

你可能感兴趣的:(数据结构,排序算法,算法)