八大排序算法总结

八大排序

文章目录

  • 八大排序
    • 冒泡排序
      • 基本思想
      • 算法描述
      • 代码实现
      • 复杂度分析
    • 快速排序
      • 基本思想
      • 算法描述
      • 代码实现
      • 复杂度分析
    • 直接插入排序
      • 基本思想
      • 算法描述
      • 代码实现
      • 复杂度分析
    • 希尔排序
      • 基本思想
      • 算法描述
      • 代码实现
      • 复杂度分析
    • 简单选择排序
      • 基本思想
      • 算法描述
      • 代码实现
      • 复杂度分析
    • 归并排序
      • 基本思想
      • 算法描述
      • 代码实现
      • 复杂度分析
    • 堆排序
      • 基本思想
      • 算法描述
      • 代码实现
      • 复杂度分析
    • 基数排序
      • 基本思想
      • 算法描述
      • 代码实现
      • 复杂度分析
    • 总结

八大排序有:冒泡排序、快速排序、希尔排序、直接插入排序、简单选择排序、堆排序、归并排序、基数排序

冒泡排序

基本思想

冒泡排序(Bubble Sort)就是一种简单排序,一次比较相邻两个元素,将大数置后,如水开的气泡从小到大进行排序,重复走访排序数列,直到没有要交换为止,即数列已经排序完成。

算法描述

  1. 比较相邻的元素,如果第一个比第二个大,就交换他们两个
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现

public class BubbleSort {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }

    private static void sort(int[] numbers) {
        for (int i = 0; i < numbers.length - 1; i++) {
            //内层循环控制到达位置
            for (int j = 0; j < numbers.length - 1 - i; j++) {
                //比较前面的元素比后面元素大交换,同理也可以比较小
                if (numbers[j] > numbers[j + 1]) {
                    int temp = numbers[j];
                    numbers[j] = numbers[j + 1];
                    numbers[j + 1] = temp;
                }
            }
        }
    }
}

代码优化

可能在最后几趟已经排序好了,还在继续排序操作,所以我们可以在交换的地方加一个标记,如果那一趟排序没有交换元素,说明这组数据已经有序,不用再继续下去。

public class BubbleSort {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }

    private static void sort(int[] numbers) {
        for (int i = 0; i < numbers.length - 1; i++) {
            boolean flag = false;
            for (int j = 0; j < numbers.length - 1 - i; j++) {
                if (numbers[j] > numbers[j + 1]) {
                    int temp = numbers[j];
                    numbers[j] = numbers[j + 1];
                    numbers[j + 1] = temp;
                    flag = true;
                }
            }
            if (!flag) {
                break;
            }
        }
    }
}

复杂度分析

平均时间复杂度 最好情况 最坏情况 空间复杂度
O(n²) O(n) O(n²) O(1)

冒泡排序是最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近n²/2次, 时间复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1).

快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

基本思想

快速排序的基本思想:挖坑填数+分治法

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好。

算法描述

快速排序使用分治策略来把一个序列(list)分为两个子序列(sub-lists)。步骤为:

  1. 从数列中挑出一个元素,称为“基准”
  2. 重新排序数列,所有比基准值小的元素摆在基准的前面,所有比基准大的元素摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
  3. 递归(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序
  4. 递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去

代码实现

递归版本

//递归版本  的快速排序,
//通过基准值来插入合适位置实现分治,递归对分治后的两个划分继续快排
public class QuickSort {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers,0,numbers.length-1);
        System.out.println(Arrays.toString(numbers));

    }
    private static void sort(int[] a, int low, int high) {
        //这里的low 和hight 传递的是数组的起始位置0,和数组的结束位置length-1
        //已经排完
        if (low >= high) {
            return;
        }
        int left = low;//起始位置0,这位置是改变的
        int right = high;//结束位置length-1,这位置是改变的

        //保存基准值
        //这里是把起始位置的数,设置为基准数,如60
        int pivot = a[left];
        while (left < right) {
            //从后向前找到比基准小的元素
            while (left < right && a[right] >= pivot)
                right--;
            a[left] = a[right];
            //从前往后找到比基准大的元素
            while (left < right && a[left] <= pivot)
                left++;
            a[right] = a[left];
        }
        // 放置基准值,准备分治递归快排
        a[left] = pivot;
        QuickSort(a, low, left - 1);
        QuickSort(a, left + 1, high);
    }

}

非递归版本

递归的本质就是栈,那么非递归版本实现可借助栈来保存中间变量实现非递归。

中间变量也就是通过 partition函数划分区间之后分成左右两部分的首尾指针,只需要保存这两部分的首尾指针即可。

//非递归版本
public class QuickSortByStack {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }
    private static void sort(int[] a) {
        Stack<Integer> stack = new Stack<Integer>();//定义栈

        //初始状态的左右指针入栈
        stack.push(0);
        stack.push(a.length - 1);
        while (!stack.isEmpty()) {
            //出栈进行划分
            int high = stack.pop();
            int low = stack.pop();

            int pivotIndex = partition(a, low, high);

            //保存中间变量
            if (pivotIndex > low) {
                stack.push(low);
                stack.push(pivotIndex - 1);
            }
            if (pivotIndex < high && pivotIndex >= 0) {
                stack.push(pivotIndex + 1);
                stack.push(high);
            }
        }
    }
    private static int partition(int[] a, int low, int high) {
        if (low >= high) return -1;
        int left = low;
        int right = high;
        //保存基准的值
        int pivot = a[left];//最左边的数值为基准
        while (left < right) {
            //从后向前找到比基准小的元素,插入到基准位置中
            while (left < right && a[right] >= pivot) {
                right--;
            }
            a[left] = a[right];
            //从前往后找到比基准大的元素
            while (left < right && a[left] <= pivot) {
                left++;
            }
            a[right] = a[left];
        }
        //放置基准值,准备分治递归快排
        a[left] = pivot;
        return left;
    }
}

复杂度分析

平均时间复杂度 最好情况 最坏情况 空间复杂度
O(nlog₂n) O(nlog₂n) O(n²) O(nlog₂n)

冒泡排序和快速排序属于交换排序

直接插入排序

基本思想

就像玩扑克一样,拿起一张牌会插入到已经有序的牌中的适当位置,组成顺子,在计算机中为了要给插入的元素腾出空间,需将其余元素向右移动一位

算法描述

插入排序采用in-place在数组上实现。具体算法描述如下:

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

代码实现

public class InsertSort {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }
    private static void sort(int[] numbers) {
        for (int i=0;i<numbers.length-1;i++){
            //外层循环控制比较次数
            for (int j=i+1;j>0;j--){
                //从第二个元素开始比较
                if (numbers[j]<numbers[j-1]) {
                    //例如第一次,如果第2个元素小于默认排好的第一个元素
                    int temp=numbers[j];//第二个元素赋值给临时变量
                    numbers[j]=numbers[j-1];//第一个元素赋值给第二元素
                    numbers[j-1]=temp;//临时变量赋值给第一个元素

                }
            }
        }
    }
}

二分查找

public class binaryInsertSort {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }
    private static void sort(int[] numbers) {
        if (numbers != null && numbers.length > 1) {
            for (int i = 1; i < numbers.length; i++) {
                int left = 0;
                int right = i-1;
                int mid;
                int temp = numbers[i];
                if(temp < numbers[right]){   // 当前值小于有序序列的最大值时,开始查找插入位置
                    while(left <= right){
                        mid = (left + right)/2;
                        if(numbers[mid] < temp){
                            left = mid + 1;    // 缩小插入区间
                        }else if(numbers[mid] > temp){
                            right = mid - 1;    // 缩小插入区间
                        }else{        // 待插入值与有序序列中的target[mid]相等,保证稳定性的处理
                            left = left + 1;
                        }
                    }

                    // left及其后面的数据顺序向后移动,并在left位置插入
                    for (int j = i; j > left; j--) {
                        numbers[j] = numbers[j-1];
                    }
                    numbers[left] = temp;
                }
            }
        }
    }
}

复杂度分析

平均时间复杂度 最好情况 最坏情况 空间复杂度
O(n²) O(n) O(n²) O(1)

希尔排序

希尔排序,也称 递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法

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

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一

希尔排序是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

基本思想

将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次再将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。一般来说最简单的步长取值是初次取数组长度的一半为增量,之后每次再减半,直到增量为1

算法描述

  1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
  2. 按增量序列个数 k,对序列进行 k 趟排序;
  3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

代码实现

//希尔排序 针对有序序列在插入时采用交换法
public class ShellSort {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }
    private static void swap(int[] arr, int a, int b) {
        arr[a] = arr[a] + arr[b];
        arr[b] = arr[a] - arr[b];
        arr[a] = arr[a] - arr[b];
    }


    private static void sort(int[] arr) {
        //增量gap,并逐步缩小增量
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            //从第gap个元素,逐个对其所在组进行直接插入排序操作
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                while (j - gap >= 0 && arr[j] < arr[j - gap]) {
                    //插入排序采用交换法
                    swap(arr, j, j - gap);
                    j -= gap;
                }
            }
        }
    }
}

复杂度分析

平均时间复杂度 最好情况 最坏情况 空间复杂度
O(n¹‘³) O(n) O(n²) O(1)

希尔排序更高效的原因是它权衡了子数组的规模和有序性。排序之初,各个子数组都很短,排序之后子数组都是部分有序的,这两种情况都很适合插入排序。

简单选择排序

基本思想

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

算法描述

  1. 从未排序序列中,找到关键字最小的元素
  2. 如果最小元素不是未排序序列的第一个元素,将其和未排序序列第一个元素互换
  3. 重复1、2步,直到排序结束。

代码实现

public class SelectSort {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }

    private static void sort(int[] numbers) {
        for (int i = 0; i < numbers.length; i++) {
            int min = i;//默认从0开始的序号的值为最小值
            //选出之后待排序中值最小的位置
            for (int j = i + 1; j < numbers.length; j++) {
                if (numbers[j] < numbers[min]) {//比较值的大小,拿到序号
                    min = j;//拿到序号
                }
            }
            //最小值不等于当前值时进行交换
            if (min != i) {
                int temp = numbers[i];
                numbers[i] = numbers[min];
                numbers[min] = temp;
            }
        }
    }
}

复杂度分析

平均时间复杂度 最好情况 最坏情况 空间复杂度
O(n²) O(n²) O(n²) O(1)

选择排序的简单和直观名副其实,这也造就了它“出了名的慢性子”,无论是哪种情况,哪怕原数组已排序完成,它也将花费将近n²/2次遍历来确认一遍。即便是这样,它的排序结果也还是不稳定的。 唯一值得高兴的是,它并不耗费额外的内存空间。

归并排序

归并排序是建立在归并操作上的一种有效的排序算法,1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。

基本思想

归并排序算法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

算法描述

归并排序可通过两种方式实现:

  • 自上而下的递归
  • 自下而上的迭代

递归法(假设序列共有n个元素):

  1. 序列每相邻两个数字进行归并操作,形成 floor(n/2)个序列,排序后每个序列包含两个元素;
  2. 将上述序列再次归并,形成 floor(n/4)个序列,每个序列包含四个元素;
  3. 重复步骤2,直到所有元素排序完毕。

迭代法

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

代码实现

归并排序其实要做两件事:

  • 分解:将序列每次折半拆分
  • 合并:将划分后的序列段两两排序合并

因此,归并排序实际上就是两个操作,拆分+合并

public class MergeSort {
    public static void main(String[] args) {
        //归并所需的辅助数组
        private static int[] aux;

        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }

    private static void sort(int[] numbers) {
        //一次性分配空间
        aux = new int[numbers.length];
        sort(numbers, 0, numbers.length - 1);
    }

    private static void sort(int[] a, int low, int high) {
        if (low >= high) {
            return;
        }
        int mid = (low + high) / 2;
        //将左半边排序
        sort(a, low, mid);
        //将右半边排序
        sort(a, mid + 1, high);
        merge(a, low, mid, high);
    }

    /**
     * 该方法先将所有元素复制到aux[]中,然后在归并会a[]中。方法咋归并时(第二个for循环)
     * 进行了4个条件判断:
     * - 左半边用尽(取右半边的元素)
     * - 右半边用尽(取左半边的元素)
     * - 右半边的当前元素小于左半边的当前元素(取右半边的元素)
     * - 右半边的当前元素大于等于左半边的当前元素(取左半边的元素)
     * @param a
     * @param low
     * @param mid
     * @param high
     */
    public static void merge(int[] a, int low, int mid, int high) {
        //将a[low..mid]和a[mid+1..high]归并
        int i = low, j = mid + 1;
        for (int k = low; k <= high; k++) {
            aux[k] = a[k];
        }

        for (int k = low; k <= high; k++) {
            if (i > mid) {
                a[k] = aux[j++];
            } else if (j > high) {
                a[k] = aux[i++];
            } else if (aux[j] < aux[i]) {
                a[k] = aux[j++];
            } else {
                a[k] = aux[i++];
            }
        }
    }

}

复杂度分析

平均时间复杂度 最好情况 最坏情况 空间复杂度
O(nlog₂n) O(nlog₂n) O(nlog₂n) O(1)

从效率上看,归并排序可算是排序算法中的”佼佼者”. 假设数组长度为n,那么拆分数组共需logn,, 又每步都是一个普通的合并子数组的过程, 时间复杂度为O(n), 故其综合时间复杂度为O(nlogn)。另一方面, 归并排序多次递归过程中拆分的子数组需要保存在内存空间, 其空间复杂度为O(n)。

堆排序

:n个元素的序列{k₁,k₂,…,kn} 当且仅当满足

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dXw0YJaV-1680103143111)(…/AppData/Roaming/Typora/typora-user-images/image-20230329230641910.png)]

时,称之为堆,把此序列对应的二维数组看成一个完全二叉树。

那么堆的含义就是:完全二叉树中任何一个非叶子节点的值均不大于(或不小于)其左,右孩子节点的值。 可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。

可使用大顶堆进行升序排序, 使用小顶堆进行降序排序。

基本思想

以大顶堆为例,堆排序的过程就是将待排序的序列构造成一个堆,选出堆中最大的移走,再把剩余的元素调整成堆,找出最大的再移走,重复直至有序。

算法描述

  1. 先将初始序列K[1…n]建成一个大顶堆, 那么此时第一个元素K1最大, 此堆为初始的无序区。
  2. 再将关键字最大的记录K1 (即堆顶, 第一个元素)和无序区的最后一个记录 Kn 交换, 由此得到新的无序区K[1…n−1]和有序区K[n], 且满足K[1…n−1].keys⩽K[n].key。
  3. 交换K1 和 Kn 后, 堆顶可能违反堆性质, 因此需将K[1…n−1]调整为堆. 然后重复步骤2, 直到无序区只有一个元素时停止。

代码实现

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆函数,二是反复调用建堆函数以选择出剩余未排元素中最大的数来实现排序的函数。

  • 最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
  • 创建最大堆(Build_Max_Heap):将堆所有数据重新排序
  • 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
public class HeapSort {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }

    private static void sort(int[] numbers) {
        for (int i = numbers.length - 1; i > 0; i--) {
            max_heapify(numbers, i);

            //堆顶元素(第一个元素)与Kn交换
            int temp = numbers[0];
            numbers[0] = numbers[i];
            numbers[i] = temp;
        }
    }

    /***
     *
     *  将数组堆化
     *  i = 第一个非叶子节点。
     *  从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
     *  叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
     *
     * @param a
     * @param n
     */
    public static void max_heapify(int[] a, int n) {
        int child;
        for (int i = (n - 1) / 2; i >= 0; i--) {
            //左子节点位置
            child = 2 * i + 1;
            //右子节点存在且大于左子节点,child变成右子节点
            if (child != n && a[child] < a[child + 1]) {
                child++;
            }
            //交换父节点与左右子节点中的最大值
            if (a[i] < a[child]) {
                int temp = a[i];
                a[i] = a[child];
                a[child] = temp;
            }
        }
    }
}

复杂度分析

  1. 建立堆的过程,从length/2一直处理到0,时间复杂度为O(n);
  2. 调整堆的过程是沿着堆的父子节点进行调整,执行次数为堆的深度,时间复杂度为O(Ign);
  3. 堆排序的过程由n次第2步完成,时间复杂度为O(nlgn).
平均时间复杂度 最好情况 最坏情况 空间复杂度
O(nlog₂n) O(nlog₂n) O(nlog₂n) O(1)

由于堆排序中初始化堆的过程比较次数较多, 因此不太适用于小序列。 同时由于多次任意下标相互交换位置, 相同元素之间原本相对的顺序被破坏了, 因此, 它是不稳定的排序

基数排序

基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

基本思想

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

按照优先从高位或低位来排序有两种实现方案:

  • MSD(Most significant digital) 从最左侧高位开始进行排序。先按k1排序分组, 同一组中记录, 关键码k1相等, 再对各组按k2排序分成子组, 之后, 对后面的关键码继续这样的排序分组, 直到按最次位关键码kd对各子组排序后. 再将各组连接起来, 便得到一个有序序列。MSD方式适用于位数多的序列。
  • LSD (Least significant digital)从最右侧低位开始进行排序。先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。LSD方式适用于位数少的序列。

算法描述

以LSD为例,从最低位开始,具体算法描述如下:

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);

代码实现

通过序列中各个元素的值,对排序的N个元素进行若干趟的“分配”与“收集”来实现排序。

  • 分配:我们将L[i]中的元素取出,首先确定其个位上的数字,根据该数字分配到与之序号相同的桶中。
  • 收集:当序列中所有的元素都分配到对应的桶中,再按照顺序依次将桶中的元素收集形成新的一个待排序列L[]。对新形成的序列L[]重复执行分配和收集元素中的十位、百位…直到分配完该序列中的最高位,则排序结束。
public class RadixSort {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16};
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    }

    private static void sort(int[] numbers) {
        if (arr.length <= 1) return;

        //取得数组中的最大数,并取得位数
        int max = 0;
        for (int i = 0; i < arr.length; i++) {
            if (max < arr[i]) {
                max = arr[i];
            }
        }
        int maxDigit = 1;
        while (max / 10 > 0) {
            maxDigit++;
            max = max / 10;
        }
        //申请一个桶空间
        int[][] buckets = new int[10][arr.length];
        int base = 10;

        //从低位到高位,对每一位遍历,将所有元素分配到桶中
        for (int i = 0; i < maxDigit; i++) {
            int[] bktLen = new int[10];        //存储各个桶中存储元素的数量

            //分配:将所有元素分配到桶中
            for (int j = 0; j < arr.length; j++) {
                int whichBucket = (arr[j] % base) / (base / 10);
                buckets[whichBucket][bktLen[whichBucket]] = arr[j];
                bktLen[whichBucket]++;
            }

            //收集:将不同桶里数据挨个捞出来,为下一轮高位排序做准备,由于靠近桶底的元素排名靠前,因此从桶底先捞
            int k = 0;
            for (int b = 0; b < buckets.length; b++) {
                for (int p = 0; p < bktLen[b]; p++) {
                    arr[k++] = buckets[b][p];
                }
            }
            System.out.println("Sorting: " + Arrays.toString(arr));
            base *= 10;
        }
    }
}

复杂度分析

平均时间复杂度 最好情况 最坏情况 空间复杂度
O(d(r+n)) O(d(rd+n)) O(d(r+n)) O(rd+n)

其中,d 为位数,r 为基数,n 为原数组个数。在基数排序中,因为没有比较操作,所以在复杂上,最好的情况与最坏的情况在时间上是一致的,均为 O(d*(n + r))。

总结

八大排序算法总结_第1张图片

你可能感兴趣的:(Java之旅,排序算法,算法,java)