这篇文章对排序算法进行一个汇总比较!
目录
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两个步骤,直至完成整个数组。
动图演示
代码演示
java
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.快速排序
算法简介
- 选择一个元素赋值给中间元素temp,默认选择左边第一个元素,这样左边第一个元素的位置就空出来了;
- 先从最右边的元素开始依次跟temp比较大小,大于等于temp元素的原地不动,遇到小于temp元素的则终止循环,把该元素赋值到左侧空出来的位置,同时左侧索引值自增,这是该元素原来的位置就空出;
- **然后左侧元素开始依次跟temp比较大小,**小于等于temp元素的原地不动,遇到大雨temp元素的则终止循环,把该元素赋值到右侧空出来的位置,同时右侧索引值自减;
- **依次循环2,3步,直至左侧索引等于右侧索引,**则完成一轮循环,把哨兵赋值到该索引的位置。
- 在分别递归地对 temp 左右两侧的子数组进行1234步,直至递归子数组只有一个元素则排序完成
动图演示
代码演示
java
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.插入排序
算法简介
- 左边第一个元素可作为有序子数组;
- 从第二个元素开始,依次向前比较,大于该元素的则向右移一位,直到比该元素小的元素,插入其后;
- 依次向后推进,直至整个数组成为有序数组
动图演示
代码演示
java
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.选择排序
算法简介
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
动图演示
代码演示
java
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.归并排序
算法简介
分:
- 一直分两组,分别对两个数组进行排序(根据上层对下层在一组的数据通过临时数组排序,再将有序数组挪回上层数组中)。
- 循环第一步,直到划分出来的"小组"只包含一个元素,只有一个元素的数组默认为已经排好序。
合:(合并时,站在上层合并下层(使组内有序))
- 将两个有序的数组合并到一个大的数组中。
- 从最小的只包含一个元素的数组开始两两合并。此时,合并好的数组也是有序的。最后把小组合成一个组。
图片演示
代码演示
java
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.希尔排序
算法简介
- 首先选择一个步长值gap,以步长值为间隔把数组分为gap个子数组gap=length/2
- 对每个子数组进行插入排序;
- 逐步减小步长 gap = gap/2,重复对数组进行1,2 步骤;
- 当步长值减为1时,相当于对数组进行一次直接插入排序。
图片演示
代码演示
java
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.
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
代码演示
java
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。
动图演示
代码演示
java
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),它的比较方向是由左至右;
动图演示
代码演示
java
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。
动图演示
代码演示
java
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可能会引入基数排序等优化