排序
关于衡量一个算法的好坏标准----时间复杂度
- 常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。
- 时间复杂度为一个算法流程中,常数操作数量的指标。常用O(读作bigO)来表示。具体来说,在常数操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果记为f(N),那么时间复杂度为O(f(N))。评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行时间,也就是常数项时间。
常见的排序算法
冒泡排序(稳定排序)
- 原理:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
- 实现代码
//冒泡法排序 public static void bubbleSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = arr.length-1; i > 0; i--) { for (int j = 0; j < i; j++) { if (arr[j] > arr[j+1]) { swap(arr, j, j+1); } } } }
- 算法时间复杂度: O ( N 2 ) O(N^2) O(N2)
选择排序(不稳定排序)
-
原理:
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。 -
实现代码
//选择排序 public static void selectSort(int[] arr) { for (int i = 0; i < arr.length-1; i++) { int minIndex = i; for (int j = i+1; j < arr.length; j++) { minIndex=arr[j]<arr[minIndex]?j:minIndex; } SortUtil.swap(arr,i,minIndex); } }
-
时间复杂度: O ( N 2 ) O(N^2) O(N2)
插入排序(稳定排序)
- 原理:
- 第一轮:第二个数和第一个数比较,若第二个数小则将第二个数插入到第一个数的前面
- 第二轮:第三个数和第二个数比较,若第三个数小,则将第二个数与第三个数交换位置,然后再看第二个数,比较同第一轮
- 后面的数按前面的规则依次插入已经排好序的队列中
- 实现代码
//插入排序 public static void insertSort(int[] arr) { for (int i = 0; i < arr.length-1 ; i++) { for (int j = i + 1; j >0 && arr[j]<arr[j-1];j--) { SortUtil.swap(arr,j,j-1); } } }
- 时间复杂度
(最坏情况): O ( N 2 ) O(N^2) O(N2)
最好情况: O ( N ) O(N) O(N)
对数器
- 用途
当我们验证一个排序算法是否正确时,需要进行大量的验证,这时我们可以借助对数器来完成。 - 使用方法
- 准备一个完全正确的排序算法
- 准备一个生成随机数组的方法
- 写一个方法将自己写的算法对随机生成的数组排序,然后再用准备好的完全正确的排序算法对这个随机生成的数组排序,最后看两个算法排序的结果是否相同。
- 重复进行多次验证,对比结果,如每次均相同,则可认为自己写的排序算法正确
- 例:
import java.util.Arrays; public class BubbleSort { //冒泡法排序 public static void bubbleSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = arr.length-1; i > 0; i--) { for (int j = 0; j < i; j++) { if (arr[j] > arr[j+1]) { swap(arr, j, j+1); } } } } //交换两个数 private static void swap(int[] arr, int j, int i) { int temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } //准备一个绝对正确的方法 public static void arraySort(int [] arr) { Arrays.sort(arr); } //生成随机数组 public static int [] generateRandomArray(int maxSize, int maxValue) { //随机生成一个最大长度可控的数组 int [] randomArray = new int[(int) (Math.random()*(maxSize+1))]; for (int i = 0; i < randomArray.length; i++) { //生成最大值可控的数,初始化数组 randomArray[i] = (int) ((maxValue + 1) * Math.random()) - (int)(maxValue * Math.random()); } return randomArray; } //拷贝数组 public static int [] copyArray(int[] arr) { if (arr == null) { return null; } int [] res = new int[arr.length]; for (int i = 0; i < arr.length; i++) { res[i] = arr[i]; } return res; } //判断两个数组是否相等 public static boolean isEqual(int[] arr1, int[] arr2) { if ((arr1 == null && arr2 != null) || (arr2 == null && arr1 != null)) { return false; } if (arr1 == null && arr2 == null) { return true; } if (arr1.length != arr2.length) { return false; } for (int i = 0; i < arr1.length; i++) { if (arr1[i] != arr2[i]) { return false; } } return true; } //打印数组 public static void printArray(int[] arr) { if (arr == null) { return; } for (int i = 0; i < arr.length; i++) { System.out.print(arr[i]+"\t"); } System.out.println(); } //验证排序方法是否正确 public static void main(String[] args) { int testTime = 500000; int maxSize = 100; int maxValue=100; boolean success = true; for (int i = 0; i < testTime; i++) { //生成随机数组 int[] arr1 = generateRandomArray(maxSize, maxValue); //拷贝数组 int[] arr2 = copyArray(arr1); //用自己写的排序算法对数组进行排序 bubbleSort(arr1); //用绝对正确的方法对数组进行排序 arraySort(arr2); //判断两个排序后的数组是否完全相等 if (!isEqual(arr1, arr2)) { success = false; printArray(arr1); printArray(arr2); break; } } System.out.println(success ? "排序算法正确":"排序算法错误"); } }
递归行为的实质
递归行为实质:一个函数调用子过程之前,会把自己的所有过程全部压到栈里,信息完全保存,子过程返回之后会利用栈里的这些信息完全还原现场。最终实现子过程和父过程的通信。
递归行为的时间复杂度估算
master公式的使用
样本量为N的情况的时间复杂度,N/b指子过程的样本量,N^d指除了调用子过程之外,剩下的代价
T(N) = a*T(N/b) + O(N^d)
- log(b,a) > d -> 复杂度为O(N^log(b,a))
- log(b,a) = d -> 复杂度为O(N^d * logN)
- log(b,a) < d -> 复杂度为O(N^d)
- 例:递归方法求最大值,先求左部分的最大值,再求右部分的最大值,最后左部分的最大值和右部分的最大值中的大者即为最大值。
- 此例中样本量为N,左部分求一次最大值,右部分求一次最大值,操作了两次,所以a=2,子过程的样本量为N/2,所以b=2,除去调用子过程的操作,剩下的代价为0,所以d=0;
- 所以此例中递归方法的时间复杂度的Master表达式为T(N) = 2*T(N/2) + O(N^0)
- log(b,a)=log(2,2)=1,d=0,所以log(b,a) > d,时间复杂度为O(N^log(b,a))=O(N)
归并排序
- 原理:归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
- 归并操作的工作原理如下:
- 第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针超出序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
- 代码
public static void mergeSort(int[] arr) { if (arr == null || arr.length < 2) { return; } mergeSort(arr, 0, arr.length - 1); } public static void mergeSort(int[] arr, int L, int R) { if (L == R) { return; } int mid = L + ((R - L) >> 1); mergeSort(arr,L,mid); mergeSort(arr,mid+1,R); merge(arr,mid,L,R); } public static void merge(int[] arr, int mid, int L, int R) { int[] help = new int[R - L + 1]; int i = 0; int p1=L; int p2 = mid + 1; while (p1 <= mid && p2 <= R) { help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } //若左边没有越界,则将左边部分剩余的数拷贝到help中 while (p1 <= mid) { help[i++] = arr[p1++]; } //若右边没有越界,则将左边部分剩余的数拷贝到help中 while (p2 <= R) { help[i++] = arr[p2++]; } //将help中已排好序的数组复制到原数组中 for (i = 0; i < help.length; i++) { arr[L + i] = help[i]; } }
小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组
的小和。
- 原理:直观理解为把一个数组分成左右两部分,数组左侧部分排序过程中产生的小和加上右侧部分排序过程中产生的小和,再加上左部分和右部分合并时所产生的小和。
- 举例操作:待操作数组[4,2,5,3,1];
-
将4和2排序时,不产生小和,将2,4与5和并时产生小和2+4=6,数组的左半部分就排序为2,4,5;
-
将3和1排序时,不产生小和,数组的右半部分就排序为1,3;
-
将4,2,5与3,1整体和并:2>1不产生小和,然后指向右半部分的指针向右移一位;
2<3产生小和,产生的小和为3后面的数的个数(包括三)乘以2; 左半部分的指针向右移一位,4>3不产生小和;
所以最终的小和结果为:2+4+2=8;
- 代码实现
public class SmallSum {
/**
* 小和问题
*/
public static int smallSum(int [] arr) {
if(arr==null || arr.length<2) {
return 0;
}
return mergeSort(arr,0,arr.length-1);
}
public static int mergeSort(int[] arr, int L, int R) {
if(L==R) {
return 0;
}
int mid=L+(R-L)/2;
return mergeSort(arr,L,mid)+mergeSort(arr,mid+1,R)+merge(arr,L,mid,R);
}
public static int merge(int[] arr, int L, int mid, int R) {
int [] help=new int[R-L+1];
int i=0;
int p1=L;
int p2=mid+1;
int res=0;
while(p1<=mid && p2<=R) {
res+=arr[p1]<arr[p2]?arr[p1]*(R-p2+1):0;
help[i++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
}
while(p1<=mid) {
help[i++]=arr[p1++];
}
while(p2<=R) {
help[i++]=arr[p2++];
}
for(i=0;i<help.length;i++) {
arr[i+L]=help[i];
}
return res;
}
逆序对问题
在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印所有逆序
对。
- 原理:与小和问题相同
- 代码
package primary.day01;
import util.Util;
public class InversePairs {
/**
* 逆序对问题
*/
public static void InversePairs(int [] arr) {
if(arr==null || arr.length<2) {
System.out.println("无逆序对");
return;
}
mergeSort(arr,0,arr.length-1);
}
public static void mergeSort(int[] arr, int L, int R) {
if(L==R) {
return;
}
int mid=L+(R-L)/2;
mergeSort(arr,L,mid);
mergeSort(arr,mid+1,R);
merge(arr,L,mid,R);
}
public static int merge(int[] arr, int L, int mid, int R) {
int [] help=new int[R-L+1];
int i=0;
int p1=L;
int p2=mid+1;
int res=0;
while(p1<=mid && p2<=R) {
if(arr[p2]<arr[p1]) {
for(int j=p1;j<=mid;j++) {
System.out.println(arr[j]+"-"+arr[p2]);
}
}
help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
}
while(p1<=mid) {
help[i++]=arr[p1++];
}
while(p2<=R) {
help[i++]=arr[p2++];
}
for(i=0;i<help.length;i++) {
arr[i+L]=help[i];
}
return res;
}
// for test
public static int comparator(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int res = 0;
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < i; j++) {
res += arr[j] < arr[i] ? arr[j] : 0;
}
}
return res;
}
public static void main(String args[]) {
int maxSize=5;
int maxValue=10;
boolean success=true;
int [] res=Util.generateRandomArray(maxSize, maxValue);
Util.printArray(res);
System.out.println();
InversePairs(res);
}
}
荷兰国旗问题
给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的 数放在数组的右边。
原理:
- 准备一个指针less,less的左边代表比小于num的区域;
- 准备一个more指针,more的右边代表大于num的区域;
- 再准备一个cur指针,其代表当前数。
- cur指针从数组的第一位开始,若cur指向的数比num小,则将cur指向的数与小于区域的下一个数交换,小于区域扩一个位置即less++,cur++;
- 若cur指向的数比num大,则将cur指向的数与大于区域的前一个数交换,more++,cur不变
- 若cur指向的数等于num,则cur++即可;
- 当less等于cur时结束。
代码:
package day02;
import util.Util;
/**
* 荷兰国旗问题
*/
public class NetherlandsFlag {
public static int [] partition(int [] arr,int L,int R,int p){
int less=L-1;
int more=R+1;
while (L<more){
if (arr[L]<p) {
Util.swap(arr,++less,L++);
}else if (arr[L]>p) {
Util.swap(arr,L,--more);
}else {
L++;
}
}
return new int [] {less+1,more-1};
}
public static void main(String[] args) {
int[] test = Util.generateRandomArray(10,3);
Util.printArray(test);
System.out.println();
int [] p=partition(test,0,test.length-1,2);
Util.printArray(test);
System.out.println();
System.out.println("等于区域:");
Util.printArray(p);
}
}
经典快排
给定一个数组,取数组中的最后一个数num,小于等于num的数放左边,大于num的数放右边,num放中间。然后左部分递归,右部分也递归,最终实现排序。
注意:
1. 因为每次都是取数组的最后一位数作为参照,使得时间复杂度与数据状况有关
2. 经典快排实际上每次只搞定了一个数,所以效率低
荷兰国旗问题改进后的快排(提高了效率)
给定一个数组,取数组中的最后一个数num,小于num的数放左边,大于num的数放右边,等于num的数放中间。然后左部分递归,右部分也递归,最终实现排序。
解决的问题:每次搞定的是一组等于num的数,而不是一个数,所以提高了效率。
随机快排
为了解决快速排序的时间复杂度与数据状况有关的弊端,则每次将数组的最后一位数和数组中的任意一个数交换位置。
代码:
public class QuickSort {
public static void quickSort(int [] arr){
if (arr.length<2 && arr==null){
return;
}
quickSort(arr,0,arr.length-1);
}
public static void quickSort(int [] arr,int L,int R){
if (L<R){
Util.swap(arr, L+(int) (Math.random()*(R-L+1)),R); //加上此代码就是随机快排
int [] p=partition(arr,L,R);
quickSort(arr,L,p[0]-1);
quickSort(arr,p[1]+1,R);
}
}
public static int [] partition(int [] arr,int L,int R){
int less=L-1;
int more=R;
while (L<more){
if (arr[L]<arr[R]){
Util.swap(arr,++less,L++);
} else if (arr[L]>arr[R]) {
Util.swap(arr,--more,L);
}else {
L++;
}
}
Util.swap(arr,more,R);
return new int []{less+1,more};
}
public static void main(String args[]) {
int testTime=500000;
int maxSize=5;
int maxValue=10;
boolean success=true;
for(int i=0;i<testTime;i++) {
int [] res=Util.generateRandomArray(maxSize, maxValue);
int [] arr1=Util.copyArray(res);
int [] arr2=Util.copyArray(res);
Util.comparator(arr1);
quickSort(arr2);
if(!Util.isEqual(arr1, arr2)) {
Util.printArray(res);
System.out.println();
Util.printArray(arr2);
success=false;
break;
}
}
System.out.println(success?"恭喜你,算法正确":"算法错误");
}
}
堆排序(非稳定排序)
- 堆的相关概念及结论:
- 堆结构其实是一个完全二叉树
- 任何一个父节点(下标为i)的左孩子的下标2i+1,右孩子的下标2i+2。
- 任何一个叶节点(下标为i)的父节点的下标(i-1)/2;
- 大根堆:任何一个父节点都比他的左右孩子的值大
- 小根堆:任何一个父节点都比他的左右孩子的值小
-
建立大根堆
将插入节点与其父节点比较,若插入节点大于父节点,则将插入节点与父节点交换,交换后再将父节点与父节点的父节点比较,做相同的操作,直到这个节点的值不比其父节点的值大或者到达堆顶为止。
代码:public static void heapInsert(int[] arr, int index) { while (arr[index] > arr[(index - 1) / 2]) { Util.swap(arr, index, (index - 1) / 2); index = (index - 1) / 2; } }
-
若大根堆中的某一节点的值变小时,将其重新调整为大根堆
将变化节点的左右孩子作比较,选出较大的一个,然后将较大的一个和变化节点作比较,若变化节点更小,则将此节点与变化节点交换。否则结束操作。然后将交换后的变化节点再重复上一步操作。public static void heapify(int[] arr, int index, int heapSize) { int left = 2 * index + 1; while (left < heapSize) { int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left; largest = arr[largest] > arr[index] ? largest : index; if (largest == index) { break; } Util.swap(arr, largest, index); index = largest; left = 2 * index + 1; } }
-
堆排序原理:先把数组建立成大根堆,然后将堆顶弹出,把堆中的最后一个数移到堆顶,将堆的大小减一。然后重复上述过程依次将堆顶弹出。
代码:public class HeapSort { public static void heapSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = 0; i < arr.length; i++) { heapInsert(arr, i); } int heapSize = arr.length; while (heapSize > 0) { Util.swap(arr, --heapSize, 0); heapify(arr, 0, heapSize); } } /** * 建立大根堆的过程 * * @param arr * @param index */ public static void heapInsert(int[] arr, int index) { while (arr[index] > arr[(index - 1) / 2]) { Util.swap(arr, index, (index - 1) / 2); index = (index - 1) / 2; } } /** * 当大根堆中的某一个数值改变时,将其重新调整为小根堆的过程 * * @param arr * @param index * @param heapSize */ public static void heapify(int[] arr, int index, int heapSize) { int left = 2 * index + 1; while (left < heapSize) { int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left; largest = arr[largest] > arr[index] ? largest : index; if (largest == index) { break; } Util.swap(arr, largest, index); index = largest; left = 2 * index + 1; } } public static void main(String args[]) { int testTime = 500000; int maxSize = 5; int maxValue = 10; boolean success = true; for (int i = 0; i < testTime; i++) { int[] res = Util.generateRandomArray(maxSize, maxValue); int[] arr1 = Util.copyArray(res); int[] arr2 = Util.copyArray(res); Util.comparator(arr1); heapSort(arr2); if (!Util.isEqual(arr1, arr2)) { Util.printArray(res); System.out.println(); Util.printArray(arr2); success = false; break; } } System.out.println(success ? "恭喜你,算法正确" : "算法错误"); } }
堆的应用
- 题目:一个数据流中,随时可以取得中位数
- 整体思路:
- 使用两个堆结构,一个大根堆,一个小根堆。将接收的所有数的较小的一半放入大根堆,将接收的较大的一半放入小根堆。如果接收的个数为奇数,中位数就是小根堆和大根堆中元素数量多的那个堆的堆顶,比如吐出的数是6,1,3,0,9,8,7,小根堆中存放6,7,8,9,大根堆存放0,1,3,小根堆的元素个数多,它的堆顶就是该序列的中位数,即6;如果接收的个数是偶数,中位数就是两个堆顶相加除以2。
- 每次接收到一个数,都要正确选择放入哪个堆,小于小根堆堆顶的都放入大根堆,否则放入小根堆。如果出现一个堆的元素个数比另一个堆的元素多两个的情况,将前者的堆顶弹出添加到后者中,并重新调整两个堆。总之要始终保持两个堆元素个数的差值不大于1。
- 代码
package day07; import util.Util; import java.util.Arrays; import java.util.Comparator; import java.util.PriorityQueue; public class MadianQuick { public static int getMedian(int[] arr) { PriorityQueue<Integer> minHeap = new PriorityQueue<>(new MinHeapComparator()); PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new MaxHeapComparator()); for (int i = 0; i < arr.length; i++) { addNum(minHeap, maxHeap, arr[i]); } //&:位运算,只有同时为1时,结果才为1 if (((maxHeap.size() + minHeap.size()) & 1) == 0) { return (maxHeap.peek() + minHeap.peek()) / 2; } return maxHeap.size() > minHeap.size() ? maxHeap.peek() : minHeap.peek(); } public static void addNum(PriorityQueue<Integer> minHeap, PriorityQueue<Integer> maxHeap, int num) { if (maxHeap.isEmpty()) { maxHeap.add(num); return; } if (maxHeap.peek() >= num) { maxHeap.add(num); } else { minHeap.add(num); } modifyTwoHeapsSize(minHeap, maxHeap); } private static void modifyTwoHeapsSize(PriorityQueue<Integer> minHeap, PriorityQueue<Integer> maxHeap) { if (maxHeap.size() == minHeap.size() + 2) { minHeap.add(maxHeap.poll()); } if (minHeap.size() == maxHeap.size() + 2) { maxHeap.add(minHeap.poll()); } } public static class MaxHeapComparator implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { if (o2 > o1) { return 1; } else { return -1; } } } public static class MinHeapComparator implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { if (o2 < o1) { return 1; } else { return -1; } } } public static int getMedianOfArray(int[] arr) { int[] newArr = Arrays.copyOf(arr, arr.length); Arrays.sort(newArr); int mid = (newArr.length - 1) / 2; if ((newArr.length & 1) == 0) { return (newArr[mid] + newArr[mid + 1]) / 2; } else { return newArr[mid]; } } public static void main(String[] args) { int testTime = 5000; int maxSize = 30; int maxValue = 100; boolean success = true; for (int i = 0; i < testTime; i++) { int[] res = Util.generateRandomArray(maxSize, maxValue); int[] arr1 = Util.copyArray(res); int[] arr2 = Util.copyArray(res); if (getMedian(arr1) != getMedianOfArray(arr2)) { System.out.println(getMedian(arr1)); System.out.println(getMedianOfArray(arr2)); success = false; break; } } System.out.println(success ? "恭喜你,算法正确" : "算法错误"); } }
工程中的综合排序
- 如果是基础类型则采用快排
- 如果比较的数组长度小于60,则直接用插入排序,因为当样本量小的时候插入排序在时间复杂度上的劣势体现不出来,而且插入排序的常数项极低,所以当小样本的情况下插入排序的速度极快
- 当快排递归的过程中,若长度小于60了,则直接采用插排
- 如果不是基础类型则采用归并排序,举个例子:假如要按成绩对一个班的学生进行排序,因为每个学生都是不同的,所以此处的排序要求具有稳定性,选择归并排序
快排可以做到稳定性,但是非常难,不需要掌握,可以搜“01 stable sort”
比较器
为什么要使用比较器,比较器有什么作用
- 当Array.sort()方法中比较的类型不是基础类型,而是自定义类型时,在使用此方法时,必须要传入一个比较器,因为自定类型,系统不知道采用什么规则比较,就会按照内存地址比较,这样就毫无意义。
- 自创比较器类必须要实现Comparator类,而且要实现compare方法,在方法中传入自定义类型中需要排序的字段
- 如果compare方法的返回值如果是负数,则表明比较后,第一个参数排在前面。若返回正数,则表明比较后,第二个参数排在前面。
package day01;
import java.util.Arrays;
import java.util.Comparator;
public class Code_09_Comparator {
public static class Student {
public String name;
public int id;
public int age;
public Student(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
}
//按id升序比较
public static class IdAscendingComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.id - o2.id;
}
}
//按id降序比较
public static class IdDescendingComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o2.id - o1.id;
}
}
public static class AgeAscendingComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
public static class AgeDescendingComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o2.age - o1.age;
}
}
public static void printStudents(Student[] students) {
for (Student student : students) {
System.out.println("Name : " + student.name + ", Id : " + student.id + ", Age : " + student.age);
}
System.out.println("===========================");
}
public static void main(String[] args) {
Student student1 = new Student("A", 1, 23);
Student student2 = new Student("B", 2, 21);
Student student3 = new Student("C", 3, 22);
Student[] students = new Student[] { student3, student2, student1 };
printStudents(students);
Arrays.sort(students, new IdAscendingComparator());
printStudents(students);
Arrays.sort(students, new IdDescendingComparator());
printStudents(students);
Arrays.sort(students, new AgeAscendingComparator());
printStudents(students);
Arrays.sort(students, new AgeDescendingComparator());
printStudents(students);
}
}
桶排序
-
桶排序是非基于比较的排序,受数据状况的影响,所以使用的并不是很多。
-
原理:找出数组中的最大值max,准备一个长度为max+1的数组bucket。然后遍历原数组,每遍历一个值就将bucket数组中对应下标的值加一。最后再遍历bucket数组,每次遍历的过程中将bucket数组中数值不为0的下标依次拷贝到原数组中,每拷贝一次就将bucket当前位置的数减一,直到当前位置的数为0为止。然后接着往下遍历bucket数组。
代码package day02; import util.Util; public class BucketSort { public static void bucketSort(int [] arr){ if (arr.length<2 || arr==null){ return; } int max=Integer.MIN_VALUE; for (int i=0;i<arr.length;i++){ max=Math.max(max,arr[i]); } int [] bucket = new int [max+1]; for (int i=0;i<arr.length;i++){ bucket[arr[i]]++; } int j=0; for (int i=0;i<bucket.length;i++){ while (bucket[i]-->0){ arr[j++]=i; } } } public static void main(String args[]) { int testTime = 500000; int maxSize = 5; int maxValue = 10; boolean success = true; for (int i = 0; i < testTime; i++) { int[] res = Util.generateRandomArray(maxSize, maxValue); int[] arr1 = Util.copyArray(res); int[] arr2 = Util.copyArray(res); Util.comparator(arr1); bucketSort(arr2); if (!Util.isEqual(arr1, arr2)) { Util.printArray(res); System.out.println(); Util.printArray(arr2); success = false; break; } } System.out.println(success ? "恭喜你,算法正确" : "算法错误"); } }
-
给定一个数组,求如果排序之后,相邻两数的最大差值,要求时间复杂度O(N),且要求不能用基于比较的排序。
代码:package day02; import util.Util; import java.util.Arrays; /** * 给定一个数组,求如果排序之后,相邻两数的最大差值,要求时 * 间复杂度O(N),且要求不能用基于比较的排序。 */ public class MaxGap { public static int maxGap(int [] arr){ if (arr.length<2 || arr==null){ return 0; } int min=Integer.MAX_VALUE; int max=Integer.MIN_VALUE; int len=arr.length; for (int i=0;i<len;i++){ min=Math.min(min,arr[i]); max=Math.max(max,arr[i]); } if (min==max){ return 0; } boolean [] hasNum = new boolean[len+1]; int [] mins = new int[len+1]; int [] maxs = new int [len+1]; int bid=0; for (int i=0;i<len;i++){ bid=bucket(min,max,len,arr[i]); mins[bid]=hasNum[bid]?Math.min(mins[bid],arr[i]):arr[i]; maxs[bid]=hasNum[bid]?Math.max(maxs[bid],arr[i]):arr[i]; hasNum[bid]=true; } int res=0; int lastMax=maxs[0]; for (int i=1;i<len+1;i++){ if (hasNum[i]){ res=Math.max(mins[i]-lastMax,res); lastMax=maxs[i]; } } return res; } private static int bucket(int min,int max,int len,int num) { return (num - min) * len / (max - min); } public static int comparator(int[] nums) { if (nums == null || nums.length < 2) { return 0; } Arrays.sort(nums); int gap = Integer.MIN_VALUE; for (int i = 1; i < nums.length; i++) { gap = Math.max(nums[i] - nums[i - 1], gap); } return gap; } public static void main(String[] args) { int testTime = 500000; int maxSize = 10; int maxValue = 10; boolean succeed = true; for (int i = 0; i < testTime; i++) { int[] arr1 = Util.generateRandomArray(maxSize, maxValue); int[] arr2 = Util.copyArray(arr1); if (maxGap(arr1) != comparator(arr2)) { succeed = false; break; } } System.out.println(succeed ? "Nice!" : "Fucking fucked!"); } }