剑指offer-最小的K个数
题目:最小的K个数
题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
1 import java.util.ArrayList; 2 import java.util.Comparator; 3 import java.util.PriorityQueue; 4 public class Solution { 5 public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { 6 ArrayList<Integer>result=new ArrayList<Integer>(); 7 int n=input.length; 8 if(k>n||k==0) return result; 9 PriorityQueue<Integer>maxHeap=new PriorityQueue<Integer>(k,new Comparator<Integer>(){ 10 @Override 11 public int compare(Integer o1,Integer o2){ 12 return o2.compareTo(o1); 13 } 14 }); 15 for(int i=0;i<n;i++){ 16 if(maxHeap.size()!=k){ 17 maxHeap.offer(input[i]); 18 }else if(maxHeap.peek()>input[i]){ 19 Integer tmp=maxHeap.poll(); 20 tmp=null; 21 maxHeap.offer(input[i]); 22 } 23 } 24 for(Integer integer:maxHeap){ 25 result.add(integer); 26 } 27 return result; 28 } 29 }
java优先队列回顾
java中提供了PriorityQueue,PriorityQueue是基于小顶堆实现的无界优先队列,这个优先队列中的元素可以默认自然排序(实现了Comparable接口或内建类型)或者通过提供的Comparator(比较器)在队列实例化的时进行排序。
优先队列的大小是不受限制的,但在创建时可以指定初始大小。当我们向优先队列增加元素的时候,队列大小会自动增加。如果不指定初始大小,其默认的初始大小为11。
优先队列使用实例:
首先创建一个用户类Customer,它没有提供任何类型的排序。当我们用它建立优先队列时,应该为其提供一个比较器对象。
1 public class Customer { 2 3 private int id; 4 private String name; 5 6 public Customer(int i, String n){ 7 this.id=i; 8 this.name=n; 9 } 10 11 public int getId() { 12 return id; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 }
使用Java随机数生成随机用户对象。对于自然排序,我们使用Integer对象,这也是一个封装过的Java对象。
1 import java.util.Comparator; 2 import java.util.PriorityQueue; 3 import java.util.Queue; 4 import java.util.Random; 5 6 public class PriorityQueueExample { 7 8 public static void main(String[] args) { 9 10 //优先队列自然排序示例 11 Queue<Integer> integerPriorityQueue = new PriorityQueue<>(7); 12 Random rand = new Random(); 13 for(int i=0;i<7;i++){ 14 integerPriorityQueue.add(new Integer(rand.nextInt(100))); 15 } 16 for(int i=0;i<7;i++){ 17 Integer in = integerPriorityQueue.poll(); 18 System.out.println("Processing Integer:"+in); 19 } 20 21 //优先队列使用示例 22 Queue<Customer> customerPriorityQueue = new PriorityQueue<>(7, idComparator); 23 addDataToQueue(customerPriorityQueue); 24 25 pollDataFromQueue(customerPriorityQueue); 26 27 } 28 29 //匿名Comparator实现 30 public static Comparator<Customer> idComparator = new Comparator<Customer>(){ 31 32 @Override 33 public int compare(Customer c1, Customer c2) { 34 return (int) (c1.getId() - c2.getId()); 35 } 36 }; 37 38 //用于往队列增加数据的通用方法 39 private static void addDataToQueue(Queue<Customer> customerPriorityQueue) { 40 Random rand = new Random(); 41 for(int i=0; i<7; i++){ 42 int id = rand.nextInt(100); 43 customerPriorityQueue.add(new Customer(id, "Pankaj "+id)); 44 } 45 } 46 47 //用于从队列取数据的通用方法 48 private static void pollDataFromQueue(Queue<Customer> customerPriorityQueue) { 49 while(true){ 50 Customer cust = customerPriorityQueue.poll(); 51 if(cust == null) break; 52 System.out.println("Processing Customer with ID="+cust.getId()); 53 } 54 } 55 56 }
java中比较器的用法
在Java中有两个接口来支持这两个概念(Comparable和Comparator),这两个接口都有连个需要被实现的方法。分别是:
* java.lang.Comparable: int comparaTo(Object o1)
该方法将该对象(this)和o1进行对比,返回一个int型的值,意义如下(大小都是逻辑上的大小):
1. positive – 该对象比o1大
2. zero – 该对象和o1对象一样大
3. negative – 该对象比o1对象小
*java.util.Comparator: int compare(Object o1, Object o2)
该方法将o1和o2进行比较,返回一个int型的值,意义如下(大小都是逻辑上的大小):
- positive – o1 的值比 o2大
- zero – o1 的值和 o2 一样大
- negative – o1 的值比 o2小
java.util.Collections.sort(List)
和java.util.Arrays.sort(Object [])
这两个方法用把指定的list按照升序排列,这时list中的元素必须实现java.lang.Comparable
接口.
java.util.Collections.sort(List,Comparator)
和java.util.Arrays.sort(Object[], Comparator)
这两个方法在能够提供Comparator时对list进行排序。
关于比较器的返回值的正负与排序时升序还是降序的关系之前一直比较模糊,即什么时候返回1,就是升序,什么时候返回-1,也是升序,什么时候又是降序。
实质上
jdk
官方默认是升序,是基于:int compare(Object o1, Object o2)
< return -1
= return 0
> return 1
官方的源码就是基于这个写的;可以理解为硬性规定。
也就是说,排序是由这三个参数同时决定的。
有时候我们在排序时会直接使用
return o1.compareTo(o2)
这句代码实质上和上面<,=,>是一样的,我们根据其定义
将该对象(o1)和o2进行对比,返回一个int型的值,意义如下(大小都是逻辑上的大小):
1. 负 – 该对象比o2对象小
2. 正 – 该对象比o2大
3. 0 – 该对象和o2对象一样大
所以他表示的就是将集合从前到后进行升序排列
那么,同理
return o2.compareTo(o1)
表示的就是从前到后降序排列
同理,对于前面的那种,如果要降序就必须完全相反:
< return 1
= return 0
> return -1
我们需要注意的是比较器,起的作用就是比较,它返回的是你想要一个什么样的序列,所以他的返回结果是需要你进行设计的,即你想要什么顺序就让它返回对应的结果,打个比方我想要从前到后升序排列,对于
compare(Object o1, Object o2)
来说,它在运行时o1自然就代表着前一个值,o2自然就代表着后一个值,随着函数的调用,从前往后依次进行比较在从前到后的每一次的两两比较中,由于我想要得到升序排列,所以我的返回值需要设计的和默认顺序(默认顺序为升序)的返回值一致
所以,我仅仅需要写出以下语句即可
public int compare(Integer o1, Integer o2) { //下面这么写,结果是升序 if(o1 < o2){ return -1; }else if(o1 > o2){ return 1; } return 0; } }或是直接写
public int compare(Integer o1, Integer o2) { //下面这么写,结果是升序 return o1.compareTo(o2); }这样的返回值和官方默认顺序时的返回值时一致的,所以便能得到从前到后升序的排列
如果你想得到降序的排列,你只需使和前面的返回值相反就行
而至于排列是如何排的,真正要排序的工作并不交给比较器,而是由sort()函数里的Timsort()算法来进行排序
而Timsort算法来确定是进行升序还是降序排列的的依据就是前面的返回值
给个例子说明下:
需求:设计一个学生类, 属性有姓名,年龄, 成绩,并产生一个数组,要求安照成绩从高到低,如果成绩相等则由年龄由低到高排序。
实现代码:
1 import java.util.Arrays; 2 class Student implements Comparable<Student>{ 3 private String name; 4 private int age; 5 private float score; 6 public Student(String name, int age, float score) { 7 this.name = name; 8 this.age = age; 9 this.score = score; 10 } 11 public String toString() { 12 return name + " " + age + " " + score; 13 } 14 public int compareTo(Student stu) { 15 if(this.score > stu.score) { 16 return -1; 17 } else if(this.score < stu.score) { 18 return 1; 19 } else { 20 if(this.age > stu.age) { 21 return 1; 22 } else if(this.age < stu.age) { 23 return -1; 24 } else { 25 return 0; 26 } 27 } 28 } 29 } 30 31 public class ComparableDemo { 32 public static void main(String[] args) { 33 Student students[] = {new Student("张三", 20, 90.0f), new Student("李四", 22, 90.0f), 34 new Student("王五", 20, 99.0f), new Student("赵六", 20, 70.0f), new Student("孙七", 22, 100.0f)}; 35 Arrays.sort(students); 36 for (Student stu: students) { 37 System.out.println(stu); 38 } 39 } 40 41 }
方法二:有空再补上
参考:java比较器原理