排序的稳定性:在排序过程中,如果任意两个元素的用于排序的基准值相同,排完序之后,如果他们的相对顺序不变,则称排序稳定,否则不稳定。
实际业务过程中我们有时候可能希望保留原始的一些信息(和相对次序有关)
不能做到稳定性的排序算法:堆排序、快速排序、二分插入排序
可以做到稳定性的排序算法:冒泡排序、朴素插入排序、归并排序
Java中的sort具有稳定性,当针对类型是基础数值类型如int、double、float、long时,规模很大的情况下采用快排的方式,因为对于基础数值类型来说,相同的值就是相同的,不含有任何附加信息;如果是复杂类型,使用比较器定义大小关系的时候,大规模情况下使用归并排序保证稳定性。当任务的样本量小于60时,采用插入排序,是因为尽管插入排序的复杂度有O(n^2),但是样本量很少的情况下,相比其他算法而言,插入排序的常数项要低得多。
Java的比较器使用(主要用于一些自定义复杂类型的比较):
需要补充的是,Java的Sort函数默认对基础数据类型使用值排序,对引用类型是对指向的地址值排序。
比较器的含义解释:
比较器需要重写compare方法,根据返回值的情况判定大小关系。如果返回值<0,则认为o1<o2,如果返回值=0,认为o1=o2,否则认为o1>o2,而Sort是按照升序排序的,所以小值在前大值在后。
public class IdComparator implements java.util.Comparator<Student> {
@Override
public int compare(Student o1,Student o2){
return o1.id-o2.id;
}
}
比较器的使用
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.TreeSet;
public class 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
public static class IdAscComparator implements java.util.Comparator<Student> {//升序
@Override
public int compare(Student o1,Student o2){
return o1.id-o2.id;
}
}
public static class IdDescComparator implements java.util.Comparator<Student> {//降序
@Override
public int compare(Student o1,Student o2){
return o2.id-o1.id;
}
}
public static void main(String[] args) {
Student student1=new Student("w",6,2);
Student student2=new Student("x",2,2);
Student student3=new Student("c",7,2);
System.out.println("升序结果:");
Student[] students=new Student[]{student1,student2,student3};
Arrays.sort(students,new IdAscComparator());
for (Student student : students) {
System.out.println(student);
}
System.out.println("======================================\n降序结果:");
Arrays.sort(students,new IdDescComparator());
for (Student student : students) {
System.out.println(student);
}
System.out.println("======================================\n优先级队列-升序:");
//优先级队列使用
PriorityQueue<Student> heap=new PriorityQueue<>(new IdAscComparator());
heap.add(student1);
heap.add(student2);
heap.add(student3);
while(!heap.isEmpty()){
Student student=heap.poll();
System.out.println(student);
}
System.out.println("======================================\nTreeSet降序");
TreeSet<Student> treeSet=new TreeSet<>(new IdAscComparator());
treeSet.add(student1);
treeSet.add(student2);
treeSet.add(student3);
for (Student student : treeSet) {
System.out.println(student);
}
}
}
排序算法补充:
1.归并排序的额外空间复杂度可以变成O(1),称为“归并排序 内部缓存法”
2.快速排序有一篇论文“01 stable sort”,可以做到稳定性,但是我们日常所写的快排是做不到的
3.奇偶面试题:有一个数组,要求排序后奇数在左偶数在右,并保证数据的相对次序不变(即具有稳定性)要求时间复杂度O(n),额外空间复杂度O(1),这其实就是变着法考察是否可以让快排变为稳定(一般没有面试官这么变态问这个->->,左神给你撑腰,碰到这种面试官给他说一下题目的来龙去脉让他自己回答,他回答不上来就【默默】举报他┑( ̄Д  ̄)┍)。因为快排也是一个01标准问题(<=bias放左边,>bias放右边)。但是如果给了O(n)的额外空间复杂度,可以用辅助数组+双指针的解法。
public class Study {
public static int[] AddAndOdd(int[] arr){
int[] help=new int[arr.length];
int add=0,odd=arr.length-1,L=0,R=arr.length-1;
while(L<=R){
if(arr[add]%2==1)help[L++]=arr[add];
if(arr[odd]%2==0)help[R--]=arr[odd];
++add;
--odd;
}
return help;
}
public static void main(String[] args) {
int[] arr=new int[]{1,3,2,56,3436,6768,23,67};
int[] ans=AddAndOdd(arr);
for (int i : ans) {
System.out.println(i+" ");
}
}
}
非基于比较的排序方法:桶排序、计数排序、基数排序
桶排序(是一个抽象的思想):由于桶本身就具有大小关系,所以可以不直接比较A、B,而是通过桶间接比较A、B。
计数排序(桶排序的一种实现思路):设计一个数组Count[i],Count[i]表示原数组中用于Count[i]个i。显然这种排序的瓶颈与数据范围有关,如果数据的跨度很大且分布不均匀,是非常不适合计数排序的。【如果说用基于比较的排序搞个离散化再进桶的话,为啥要这么多此一举呢23333】
面试题:给定一个数组,求如果排序之后,相邻两数的最大差值,要求时间复杂度O(N),且不能使用非基于比较的排序。
思路:对N个数,建立N+1个桶【0~N】。首先扫描一遍数组得到max和min,如果max==min,则证明数组的元素全部相同。否则,将min放入1号桶,将max放入N+1号桶,除了最后一个桶之外,我们让所有桶的数据跨度为d=(max-min+1)/(N+1),最后一个桶可能会因为除不尽而不足这个值。此时,把所有元素归位到属于的桶里index=(Arr[i]-min)/d。由于1和N+1号桶都有数,且总共有N个数、N+1个桶,根据鸽巢原理,中间至少存在一个空桶。
那么存在一个空桶,就可以保证,答案一定是>d的,而来自一个桶内的元素插值一定是<d的,所以答案不可能来自于同一个桶内部元素的差值。我们针对每个桶维护一个Max和Min,从左到右扫描数组,第i号桶贡献的答案为i号桶的最小值减掉i往左第一个非空桶的最大值。但是呢!!!必须要注意的是,答案并不一定是来自于被空桶所隔开的两个桶。空桶可以保证差值>d,但是两个相邻桶的最大差值可以达到2d-1【左边桶min=max=桶下届,右边桶min=max=桶上界】。
public static int maxGap(int[] nums){
if(nums == null|| nums.length <2)return 0;
int len=nums.length;
int min=Integer.MAX_VALUE;
int max=Integer.MIN_VALUE;
for (int i = 0; i < len; i++) {
min=Math.min(min,nums[i]);
max=Math.max(max,nums[i]);
}
if(min == max)return 0;
boolean[] hashNum=new boolean[len+1];
int[] maxs=new int[len+1];
int[] mins=new int[len+1];
int bid;
for(int i=0;i<len;++i){
bid=getIndex(nums[i],len,min,max);
mins[bid]=hashNum[bid]?Math.min(mins[bid],nums[i]):nums[i];
maxs[bid]=hashNum[bid]?Math.max(maxs[bid],nums[i]):nums[i];
hashNum[bid]=true;
}
int res=0;
int lastMax=maxs[0];
for(int i=1;i<=len;++i){
if(hashNum[i]){
res=Math.max(res,mins[i]-lastMax);
lastMax=maxs[i];
}
}
return res;
}
public static int getIndex(int num,int len,int min,int max){
return (num-min)*len/(max-min);
}