剑指offer-最小的K个数

题目:最小的K个数

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
 
分析:这是一道考察排序的题,借此重新复习下常见的排序
方法一:利用堆排序
建立并维持一个只有k个元素的最大堆,后面的元素进来时先与堆顶元素进行比较,如果比堆顶元素大,则不放进去;如果比堆顶元素小,则删掉堆顶元素,将次元素入堆,对整个堆重新进行排序,当着n个数都遍历完后,此时堆中元素便是最小的k个元素
代码参考了讨论区,java中优先队列是用堆实现的
 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型的值,意义如下(大小都是逻辑上的大小):

      1. positive – o1 的值比 o2大
      2. zero – o1 的值和 o2 一样大
      3. 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比较器原理

java比较器源码分析

java比较器排序原理

timSort排序原理

原文地址:https://www.cnblogs.com/pathjh/p/9291773.html