类型 | 名称 | 平均时间复杂度 | 最好时间复杂度 | 最坏时间复杂度 | 空间复杂度 |
---|---|---|---|---|---|
插入类 | 直接插入排序 | 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 04
(1)以增量5分割序列,得到以下几个子序列:
子序列1:49 13
子序列2: 38 27
子序列3: 65 49
子序列4: 97 55
子序列5: 76 04
分别对这5个子序列进行直接插入排序,得到:
子序列1:13 49
子序列2: 27 38
子序列3: 49 65
子序列4: 55 97
子序列5: 04 76
一趟希尔排序结束,结果为:
13 27 49 55 04 49 38 65 97 76
(2)再对上面排序的结果已增量3分割,得到以下几个子序列:
子序列1:13 55 38 76
子序列2: 27 04 65
子序列3: 49 49 97
分别对这3个子序列进行直接插入排序,得到:
子序列1:13 38 55 76
子序列2: 04 27 65
子序列3: 49 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;
}
}