java实现常用排序

在这里插入图片描述

类型名称平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度
插入类直接插入排序O(n^2)O(n)O(n^2)O(1)
插入类折半插入排序O(n^2)O(n)O(n^2)O(1)
插入类希尔排序(缩小增量排序)不稳定的O(nlog2n)--O(1)
交换类冒泡排序O(n^2)O(n)O(n^2)O(1)
交换类快速排序O(nlog2n)-O(n^2)O(log2n)
选择类简单选择排序(移动元素次数最少)O(n^2)--O(1)
选择类堆排序(适用于记录数最多的)O(nlog2n)-O(nlog2n)O(1)
二路归并排序O(nlog2n)O(nlog2n)O(nlog2n)O(n)
基数排序O(d(n + rd))-O(d(n + rd))O(rd)

O(d(n + rd))
d 为元素的关键字位数
n 为序列中的元素数
rd 为关键字的取值范围

快排越接近无
排序方法的元素比较次数和原始序列无关:简单选择排序和折半插入排序
排序方法的排序趟数和原始序列有关:交换类的排序(冒泡、快排)

心情不稳定,快(快排)些(希尔)选(简单选择)一堆(堆排序)好朋友来聊天吧
快(快排)些(希尔)以nlog2n 的速度归(归并排序)队(堆排序)。

比较排序

冒泡排序(Bubble Sort)

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作。

当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。

在这里插入图片描述

/**
 * Created by zxm on 2018/4/8.
 * 冒泡排序
 */
public class test1141 {

    public static void main(String[] args){
        int[] a={4,2,1,6,3,6,0,-5,1,1};
        bubbleSort(a);
        for (int i=0;i<a.length;i++){
            System.out.println(a[i]);
        }

    }

   // 冒泡排序,a表示数组
    public static void bubbleSort(int[] a) {
        for (int i = 0; i < a.length; ++i) {
            // 提前退出冒泡循环的标志位
            boolean flag = false;
            for (int j = 0; j < a.length - i - 1; ++j) {
                if (a[j] > a[j + 1]) { // 交换
                    int tmp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = tmp;
                    flag = true;  // 表示有数据交换
                }
            }
            if (!flag) {
                break;  // 没有数据交换,提前退出
            }
        }
    }
}

第一,冒泡排序是原地排序算法吗?
冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为O(1),是原地排序算法

第二,冒泡排序是稳定的排序算法吗?
为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候,我们不做交换,相同大小的数据在排序前后不会改变顺序,所以冒泡排序是稳定的排序算法

第三,冒泡排序的时间复杂度是多少?
最好情况下,要排序的数据已经是有序的了,我们只需要进行一次冒泡操作,就可以结束了,所以最好情况时间复杂度是O(n)。
而最坏的情况是,要排序的数据刚好是倒序排列的,我们需要进行n次冒泡操作,所以最坏情况时间复杂度为O(n^2)。

平均时间复杂度也是O(n^2)

插入排序(Insertion Sort)

我们将数组中的数据分为两个区间,已排序区间未排序区间
初始已排序区间只有一个元素,就是数组的第一个元素。
插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。
在这里插入图片描述

/**
 * Created by zxm on 2018/4/8.
 * 插入排序
 */
public class test1143 {

    public static void main(String[] args){
        int[] a={4,2,1,6,3,6,0,-5,1,1};
        insertSort(a);
        for (int i=0;i<a.length;i++){
            System.out.println(a[i]);
        }
    }

	//依次将后面无序的数字,在前面有序的数列中找到合适的位置并插入
    private static void insertSort(int[] a) {
	    // 循环未排序区
        for (int i = 1; i < a.length; i++) {
            int temp=a[i];
            int j ;
            // 依次和已排序区元素比较,找到插入的位置
            for (j = i-1; (j >=0) && (temp<a[j]); j--) {
                a[j+1]=a[j];
            }
            // 插入元素
            a[j+1]=temp;
        }
    }

}

第一,插入排序是原地排序算法吗?
从实现过程可以很明显地看出,插入排序算法的运行并不需要额外的存储空间,所以空间复杂度是O(1),是一个原地排序算法

第二,插入排序是稳定的排序算法吗?
在插入排序中,对于值相同的元素,我们可以选择将后面出现的元素,插入到前面出现元素的后面,这样就可以保持原有的前后顺序不变,所以插入排序是稳定的排序算法

第三,插入排序的时间复杂度是多少?
如果要排序的数据已经是有序的,我们并不需要搬移任何数据。如果我们从尾到头在有序数据组里面查找插入位置,每次只需要比较一个数据就能确定插入的位置。所以这种情况下,最好是时间复杂度为O(n)。注意,这里是从尾到头遍历已经有序的数据。
如果数组是倒序的,每次插入都相当于在数组的第一个位置插入新的数据,所以需要移动大量的数据,所以最坏情况时间复杂度为O(n^2)。

在数组中插入一个数据的平均时间复杂度是O(n)。所以,对于插入排序来说,每次插入操作都相当于在数组中插入一个数据,循环执行n次插入操作,所以平均时间复杂度为O(n^2)

直接插入排序每次都是在一个已经有序的序列中插入一个新的记录,所以在这个有序序列寻找插入位置就可以用折半查找的方法。这种叫做折半插入排序。适合记录数较多的场景。时间复杂度:最好O(n),最差O(n^2),平均 O(n^2)。

冒泡排序和插入排序的时间复杂度都是O(n^2),都是原地排序算法,为什么插入排序要比冒泡排序更受欢迎呢?
冒泡排序不管怎么优化,元素交换的次数是一个固定值,是原始数据的逆序度。插入排序是同样的,不管怎么优化,元素移动的次数也等于原始数据的逆序度。
但是,从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要3个赋值操作,而插入排序只需要1个。耗时实际更短。

冒泡排序、选择排序,可能就纯粹停留在理论的层面了,学习的目的也只是为了开拓思维,实际开发中应用并不多,但是插入排序还是挺有用的,有些编程语言中的排序函数的实现原理会用到插入排序算法。
在这里插入图片描述

选择排序(Selection Sort)

选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
选择排序

/**
 * Created by zxm on 2018/4/8.
 * 选择排序
 */
public class test1142 {

    public static void main(String[] args){
        int[] a={4,2,1,6,3,6,0,-5,1,1};
        selectSort(a);
        for (int i=0;i<a.length;i++){
            System.out.println(a[i]);
        }
    }

    private static void selectSort(int[] a) {
        for (int i = 0; i < a.length; i++) {
            for(int j=i+1;j<a.length;j++){
                if(a[i]>a[j]){
                    int temp=a[i];
                    a[i]=a[j];
                    a[j]=temp;
                }
            }
        }
    }

}

选择排序空间复杂度为O(1),是一种原地排序算法。

选择排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为O(n^2)。

选择排序是一种不稳定的排序算法。

正是因此,相对于冒泡排序和插入排序,选择排序就稍微逊色了。

希尔排序

又叫缩小增量排序,本质还是插入排序,只不过是将待排序的序列按某种规则分成几个子序列,分别对这几个子序列进行直接插入排序。这个规则就是增量。

直接插入排序适合于序列基本有序的情况,希尔排序的每趟排序,都会使整个序列变得更加有序,等整个序列基本有序了,再来一趟直接插入排序,这样会使排序效率更高,这就是希尔排序的思想。

原始序列 49    38    65    97    76    13    27    49    55    041)以增量5分割序列,得到以下几个子序列:
子序列149                            13
子序列238                            27
子序列365                            49
子序列497                             55
子序列576                            04

分别对这5个子序列进行直接插入排序,得到:
子序列113                            49
子序列227                            38
子序列349                            65
子序列455                             97
子序列504                            76

一趟希尔排序结束,结果为:
       13    27    49    55    04    49    38     65    97    762)再对上面排序的结果已增量3分割,得到以下几个子序列:
子序列113                55                38                 76
子序列227                04                 65
子序列349                49                 97

分别对这3个子序列进行直接插入排序,得到:
子序列113                38                55                 76
子序列204                27                 65
子序列349                49                 97

又一趟希尔排序结果为:
        13    04    49    38    27    49    55    65    97    76
观察发现,现在基本有序了。

(3)最后以增量1分割,即对上面结果的全体记录进行一趟直接插入排序,从而完成整个希尔排序。
最后希尔排序结果为:
		04    13    27    38    49    49    55    65    76    97

希尔排序是不稳定的。
平均时间复杂度:O(nlog2n)
空间复杂度:O(1)

希尔排序的增量,最后一个值一定是1;没有除一之外的公因子,如8、4、2 这样的序列不要取。

/**
 * Created by zxm on 2018/4/8.
 * 希尔排序
 */
public class test1144 {

    public static void main(String[] args){
        int[] a={3,0,-5,1,4,2,6,2,7,2,3,45,2,53,1,5,6,-3,34,56,-7};
        shellSort(a);
        for (int i=0;i<a.length;i++){
            System.out.println(a[i]);
        }
    }

    private static void shellSort(int[] a) {
        int DataLength;//分组的长度
        int Pointer; //本组的前面点的位置
        int Index=a.length-1;

        DataLength=Index/2;
        while (DataLength!=0){
            for(int j=DataLength;j<=Index;j++){
                int temp=a[j];
                Pointer=j-DataLength;
                while (  Pointer>=0 && a[Pointer]>temp){//依次与同组的后面的数比较,找到合适的位置
                    a[Pointer+DataLength]=a[Pointer];
                    Pointer=Pointer-DataLength;
                }
                a[Pointer+DataLength]=temp;
            }
            DataLength=DataLength/2;
        }
    }

}

二分排序

/**
 * Created by zxm on 2018/4/8.
 * 二分排序
 */
public class test1145 {

    public static void main(String[] args){
        int[] a={4,2,1,6,3,6,0,-5,1,1};
        midInsertSort(a);
        for (int i=0;i<a.length;i++){
            System.out.println(a[i]);
        }
    }

    private static void midInsertSort(int[] a) {
        int temp;//存储当前要插入的值
        int low,high,mid;
        for (int i = 1; i < a.length; i++) {
            temp=a[i];
            low=0;
            high=i-1;
            while (low<=high){//开始折半查找,找到要插入的位置
                mid=(low+high)/2;
                if(a[mid]>temp){
                    high=mid-1;
                }else{
                    low=mid+1;
                }
            }
            for(int j=i-1;j>high;j--){//将有序数列中大于他的数全部前移一个位置
                a[j+1]=a[j];
            }
            a[high+1]=temp;//插入该元素, 由于前面元素都向前移一位,插入位置也+1
        }
    }

}

快速排序

第一步,取基准数
在这里插入图片描述
第二步,分区过程
分区过程,将比基准数大的数全放到它的右边,比基准数小的或者相等的数全放到它的左边。

我们首先把第一个元素arr[0]=4定义为基准元素,此时数组第一个位置就是坑,那么我们要从数组的右边向左开始查找小于基准数的元素,并与坑互换位置
在这里插入图片描述
换好位置之后,现在转换,从数组的左边向右边查找比基准数大的元素:
在这里插入图片描述
换好位置之后,现在又重新开始从数组右边向左边开始查找,比基准数小的元素:
在这里插入图片描述
不断重复此类操作,直到分成左右两个分区,再把基准数填入坑中,这样第一趟排序完成。如下:
在这里插入图片描述
第三步,对两个区间重复进行分区操作
这里,我们对分好的两个区间重复进行上述分区操作,直到每个区间只有一个元素为止。
在这里插入图片描述

/**
 * Created by zxm on 2018/4/8.
 * 快速排序
 */
public class test1146 {

    public static void main(String[] args){
        int[] a={4,2,1,6,3,6,0,-5,1,1};
        quickSort(a,0,a.length-1);
        for (int i=0;i<a.length;i++){
            System.out.println(a[i]);
        }
    }

    private static void quickSort(int[] a,int low,int high) {
        if(low<high){
            int i=low;
            int j=high;
            int x=a[i];
            while(i<j){
                while (i<j && a[j]>x){//从最右侧开始寻找,找到小于当前位置的值后退出
                    j--;
                }
                if(i<j){
                    a[i]=a[j]; //将小于x的值放到原位置
                    i++;
                }
                while (i<j && a[i]<x){//从左侧开始寻找,找到大于当前值的位置后退出
                    i++;
                }
                if(i<j){
                    a[j]=a[i];//将大于x的值放到上一个位置
                    j--;
                }
            }
            a[i]=x;//进项完上述过程,x,左边的值都小于他,右边的值都大于他,分别对左右两个子列递归进行快排
            quickSort(a,low,i-1);
            quickSort(a,i+1,high);
        }
    }

}

时间复杂度
快速排序最坏时间复杂度是O(n^2),最好的时间复杂度为O(nlogn),平均时间复杂度O(nlogn)。

空间复杂度
空间复杂度是O(1),没有用到额外开辟的集合空间。

算法稳定性
快速排序是不稳定的排序算法。因为我们无法保证相等的数据按顺序被扫描到和按顺序存放。

在这里插入图片描述

归并排序的处理过程是由下到上的,先处理子问题,然后再合并。而快排正好相反,它的处理过程是由上到下的,先分区,然后再处理子问题。归并排序虽然是稳定的、时间复杂度为O(nlogn)的排序算法,但是它是非原地排序算法。我们前面讲过,归并之所以是非原地排序算法,主要原因是合并函数无法在原地执行。快速排序通过设计巧妙的原地分区函数,可以实现原地排序,解决了归并排序占用太多内存的问题。

归并排序

核心思想:如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。
在这里插入图片描述
分治算法一般都是用递归来实现的。
分治是一种解决问题的处理思想,递归是一种编程技巧,这两者并不冲突。

写递归代码的技巧就是,分析得出递推公式,然后找到终止条件,最后将递推公式翻译成递归代码。

递推公式:

递推公式:
merge_sort(start…end) = merge(merge_sort(start…mid), merge_sort(mid+1…end))
其中:mid = (start + end)

终止条件:
start >= end 不用再继续分解

merge(merge_sort(start…mid), merge_sort(mid+1…end))这个函数的作用就是,将已经有序的[start…mid]和 [mid+1…end] 合并成一个有序的数组,并且放入A[start…end]。

/**
 * Created by zxm on 2018/4/8.
 * 归并排序
 */
public class test1147 {

    public static void main(String[] args) {
        int[] a = {4, 2, 1, 6, 3, 6, 0, -5, 1, 1};
        mergeSort(a, 0, a.length - 1);
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }
    }

    private static void mergeSort(int[] a, int start, int end) {
        if (start < end) {
            int mid = (start + end) / 2;//先分
            mergeSort(a, start, mid);
            mergeSort(a, mid + 1, end);
            merge(a, start, mid, mid + 1, end);//再合
        }
    }

    private static void merge(int[] a, int start1, int end1, int start2, int end2) {
        int i = start1;//表一和表二的游标
        int j = start2;
        int k = 0;
        int[] temp = new int[end2 - start1 + 1];//建立临时数组,长度为两表之和
        while (i <= end1 && j <= end2) {
            if (a[i]>a[j]){
                temp[k++]=a[j++];
            }else {
                temp[k++]=a[i++];
            }
        }
        //把剩下的元素依次放入临时数组中,肯定是只剩下一方
        while(i<=end1){
            temp[k++]=a[i++];
        }
        while (j<=end2){
            temp[k++]=a[j++];
        }
        //把临时数组元素复制到原数组
        k=start1;
        for(int element:temp){
            a[k++]=element;
        }
    }

}

稳定的排序算法。

归并排序的执行效率与要排序的原始数组的有序程度无关,不管是最好情况、最坏情况,还是平均情况,时间复杂度都是O(nlogn)。

空间复杂度是O(n)。

堆排序

/**
 * 堆排序
 * 时间复杂度:O(nlogn)  空间复杂度:O(1)
 * 基本思想:将待排序的序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根结点。将它移走(与堆数组的末尾元素交换),
 * 然后将剩下的 n-1 个序列重新构造成一个堆,这样就会得到n个元素中大打的值。如此反复,得到一个有序序列。
 * 需要解决两个关键问题:
 * (1)将一个无序序列构造成一个堆。
 * (2)输出堆顶元素后,调整剩余元素成为一个新堆。
 * 注意:
 * ①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
 * ②堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前
 */
public class test1148 {

    public static void main(String[] args) {
        int[] a = {4, 2, 1, 6, 3, 6, 0, -5, 1, 1};
        heapSort(a);
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }
    }

    private static void heapSort(int[] a) {
        int i;
        for (i=a.length/2-1;i>=0;i--){//构建初始堆。从第一个非叶子节点开始调整,完成第一次建堆后,以后只用调整第一个结点即可。
            adjustHeap(a,i,a.length-1);
        }
        for (i = a.length-1; i >0 ; i--) {//将堆顶记录和当前未经排序子序列的最后一个记录交换
            int temp=a[0];
            a[0]=a[i];
            a[i]=temp;
            adjustHeap(a,0,i-1);
        }
    }

    private static void adjustHeap(int[] a, int i, int len) {
        int temp=a[i];//要调整的元素  //i指向要调整的元素,j指向i的左孩子
        for(int j=2*i+1;j<len;j=i*2+1){//j 为 i 的左孩子位置
            if(j<len && a[j]<a[j+1]) // i 的左右孩子比较大小
                ++j; //如果右孩子大,j指向右孩子
            if(temp>=a[j])  //如果i 的左右孩子都小于a[i] 则不用调整
                break;
            a[i]=a[j]; //将较大的孩子值赋给其其父节点位置
            i=j; // 从其交换的较大孩子节点的位置,继续比较
        }
        a[i]=temp;
    }
}

非比较排序

非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置。
非比较排序只要确定每个元素之前的已有的元素个数即可,所以一次遍历即可解决。算法时间复杂度O(n)。
非比较排序时间复杂度低,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。

计数排序

/**
 * 计数排序
 * 计数排序需要占用大量空间,它仅适用于数据比较集中的情况。比如 [0~100],[10000~19999] 这样的数据。
 * 用一个组数help记录每个数出现的次数,这个数组的长度为待排序元素的 最大值-最小值+1,数组的位置下标+最小值即为原数列元素的值
 */
public class test1149 {

    public static void main(String[] args) {
        int[] a = {4, 2, 1, 6, 3, 6, 0, -5, 1, 1};
        int[] result = countSort(a);
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }
    }

    private static int[] countSort(int[] a) {
        if (a == null || a.length == 0) {
            return null;
        }
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        //找出数组中的最大最小值
        for (int i = 0; i < a.length; i++) {
            max = Math.max(a[i], max);
            min = Math.min(a[i], min);
        }
        int[] help = new int[max-min+1];//辅助计数数组,help数组中每个下标对应arr中的一个元素,help用来记录每个元素出现的次数
        for (int i = 0; i < a.length; i++) {
            int position = a[i] - min;
            help[position]++;
        }
        int index = 0;
        for (int i = 0; i < help.length; i++) {//遍历辅助数组,如果大于零,这根据下标还原出原数值
            while (help[i]-- > 0) {
                a[index++] = i + min;//直接覆盖原数组
            }
        }
        return a;
    }

}

桶排序

在这里插入图片描述

/**
 * 桶排序
 * 桶排序可用于最大最小值相差较大的数据情况,比如[9012,19702,39867,68957,83556,102456]。
 * 但桶排序要求数据的分布必须均匀,否则可能导致数据都集中到一个桶中。比如[104,150,123,132,20000], 这种数据会导致前4个数都集中到同一个桶中。导致桶排序失效。
 *  桶排序的基本思想是:把数组 arr 划分为n个大小相同子区间(桶),每个子区间各自排序,最后合并。
 *  计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。
 *  1.找出待排序数组中的最大值max、最小值min
 *  2.我们使用 动态数组ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(max-min)/arr.length+1
 *  3.遍历数组 arr,计算每个元素 arr[i] 放的桶
 *  4.每个桶各自排序
 *  5.遍历桶数组,把排序好的元素放进输出数组
 */
public class test11410 {

    public static void main(String[] args) {
        int[] a = {4, 2, 1, 6, 3, 6, 0, -5, 1, 1};
        int[] result = bucketSort(a);
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }
    }

    private static int[] bucketSort(int[] a) {
        if (a == null || a.length == 0) {
            return null;
        }
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        //找出数组中的最大最小值
        for (int i = 0; i < a.length; i++) {
            max = Math.max(a[i], max);
            min = Math.min(a[i], min);
        }
        //桶数
        int bucketNum=(max-min)/a.length+1;
        ArrayList<ArrayList<Integer>> bucketArr=new ArrayList<>(bucketNum);
        for (int i = 0; i < bucketNum; i++) {
            bucketArr.add(new ArrayList<Integer>());
        }
        //将每个元素放入桶
        for (int i = 0; i < a.length; i++) {
            int num=(a[i]-min)/a.length;
            bucketArr.get(num).add(a[i]);
        }
        //对每个桶进行排序
        for(int i=0;i<bucketArr.size();i++){
            Collections.sort(bucketArr.get(i));
        }
        //将结果放入原数组
        int i=0;
        for(ArrayList<Integer> evetyBuckerArr:bucketArr){
            for(int x:evetyBuckerArr){
                a[i++]=x;
            }
        }
        return a;
    }

}

桶排序的时间复杂度为什么是O(n)呢?
如果要排序的数据有n个,我们把它们均匀地划分到m个桶内,每个桶里就有k=n/m个元素。每个桶内部使用快速排序,时间复杂度为O(k * logk)。m个桶排序的时间复杂度就是O(m * k * logk),因为k=n/m,所以整个桶排序的时间复杂度就是O(n*log(n/m))。当桶的个数m接近数据个数n时,log(n/m)就是一个非常小的常量,这个时候桶排序的时间复杂度接近O(n)。

桶排序对要排序数据的要求是非常苛刻:

  • 桶排序要求数据的分布必须均匀,在极端情况下,如果数据都被划分到一个桶里,那就退化为O(nlogn)的排序算法了。

桶排序比较适合用在外部排序中。所谓的外部排序就是数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中。

比如说我们有10GB的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百MB,没办法一次性把10GB的数据都加载到内存中。这个时候该怎么办呢?

现在我来讲一下,如何借助桶排序的处理思想来解决这个问题。

我们可以先扫描一遍文件,看订单金额所处的数据范围。假设经过扫描之后我们得到,订单金额最小是1元,最大是10万元。我们将所有订单根据金额划分到100个桶里,第一个桶我们存储金额在1元到1000元之内的订单,第二桶存储金额在1001元到2000元之内的订单,以此类推。每一个桶对应一个文件,并且按照金额范围的大小顺序编号命名(00,01,02…99)。

理想的情况下,如果订单金额在1到10万之间均匀分布,那订单会被均匀划分到100个文件中,每个小文件中存储大约100MB的订单数据,我们就可以将这100个小文件依次放到内存中,用快排来排序。等所有文件都排好序之后,我们只需要按照文件编号,从小到大依次读取每个小文件中的订单数据,并将其写入到一个文件中,那这个文件中存储的就是按照金额从小到大排序的订单数据了。

不过,你可能也发现了,订单按照金额在1元到10万元之间并不一定是均匀分布的 ,所以10GB订单数据是无法均匀地被划分到100个文件中的。有可能某个金额区间的数据特别多,划分之后对应的文件就会很大,没法一次性读入内存。这又该怎么办呢?

针对这些划分之后还是比较大的文件,我们可以继续划分,比如,订单金额在1元到1000元之间的比较多,我们就将这个区间继续划分为10个小区间,1元到100元,101元到200元,201元到300元…901元到1000元。如果划分之后,101元到200元之间的订单还是太多,无法一次性读入内存,那就继续再划分,直到所有的文件都能读入内存为止。

基数排序

/**
 * 基数排序
 * 基数排序的总体思路就是将待排序数据拆分成多个关键字进行排序
 * 如果按照习惯思维,会先比较百位,百位大的数据大,百位相同的再比较十位,十位大的数据大;最后再比较个位。
 * 人得习惯思维是最高位优先方式。但一旦这样,当开始比较十位时,程序还需要判断它们的百位是否相同--这就认为地增加了难度,
 * 计算机通常会选择最低位优先法。
 * 基数排序方法对任一子关键字排序时必须借助于另一种排序方法,而且这种排序方法必须是稳定的。
 * 对于多关键字拆分出来的子关键字,它们一定位于0-9这个可枚举的范围内,这个范围不大,因此用桶式排序效率非常好。
 * 对于多关键字排序来说,程序将待排数据拆分成多个子关键字后,对子关键字排序既可以使用桶式排序,也可以使用任何一种稳定的排序方法。
 * 先 分配 在 收集
 */
public class test11411 {

    public static void main(String[] args) {
        int[] a = {1100, 192, 221, 12, 23 };
        radixSort(a,10,4); //10为桶的个数,4为基数(关键字)个数
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }
    }

    private static void radixSort(int[] data,int radix,int d) {
        //缓存数组
        int[] tmp=new int[data.length];
        //buckets记录待排序元素的信息
        int[] buckets=new int[radix];
        for (int i = 0,rate=1 ; i <d ; i++) {
            //重置count数组,开始统计下一个关键字
            Arrays.fill(buckets,0);
            //将data中的元素完全复制到tmp数组中
            System.arraycopy(data, 0, tmp, 0, data.length);

            //计算每个待排序数据的子关键字
            for (int j = 0; j < data.length; j++) {
                int subKey=(tmp[j]/rate)%radix;
                buckets[subKey]++;
            }
            for (int j = 1; j < radix; j++) {
                buckets[j]=buckets[j]+buckets[j-1];
            }
            //按子关键字对指定的数据进行排序
            for (int m = data.length-1; m >=0 ; m--) {
                int subKey=(tmp[m]/rate)%radix;
                data[--buckets[subKey]]=tmp[m];
            }
            rate*=radix;
        }
    }

}

构建单链表

/**
 * Created by zxm on 2018/4/11.
 * input:[1,2,3,4,5,6,8,9,10]
 */

class ListNode{
    int val;
    ListNode next;
    ListNode(int x){
        val=x;
    }
}

public class 链表 {
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext()){
            String line=sc.nextLine();
            ListNode node= stringToListNode(line);
            printLinkedList(node);
        }
    }

    private static void printLinkedList(ListNode node) {
        if(node==null){
            System.out.println("Empty LinkedList");
        }else{
            while(node.next!=null){
                System.out.print(node.val+"->");
                node=node.next;
            }
            System.out.println(node.val);
        }
    }


    public static ListNode stringToListNode(String input){
        input=input.trim();
        input=input.substring(1,input.length()-1);
        if(input.length()==0){
            return null;
        }
        String[] parts=input.split(",");
        ListNode headNode=new ListNode(0);//头结点
        ListNode p=headNode;//指向当前节点
        for(String s:parts){
            p.next=new ListNode(Integer.parseInt(s.trim()));
            p=p.next;
        }
        return headNode.next;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值