关联:http://blog.youkuaiyun.com/tonghuawanli/article/details/70140441
建堆过程
堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。
既然是堆排序,自然需要先建立一个堆,而建堆的核心内容是调整堆,使二叉树满足堆的定义(每个节点的值都不大于其父节点的值)。调堆的过程应该从最后一个非叶子节点开始,假设有数组A = {1, 3, 4, 5, 7, 2, 6, 8, 0}。
建堆过程如下:
例题:输入4,5,1,6,2,7,3,8这8个数字,查找最小的4个数字是1,2,3,4
题目分析:
在找出前k个最小(或最大)的元素时,如果元素个数较少,可以采用简单选择排序;如果元素较多,可以采用堆排序;如果元素基本有序,可以采用冒泡排序。本文采用的是堆排序O(nlogk)
方法1:利用java的PriorityQueue
想求最小的k个数,可以用一个大根堆过滤大的数;
如果想求k个最大的数,可以用小根堆过滤小的数;
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
if(input.length<1 || k<1 || input.length<k)
return list;
// 小根堆
//PriorityQueue<Integer> minHeap = new PriorityQueue<>();
// 建个大根堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {
// 如果上面的写法出错就用下面的这个
// PriorityQueue<Integer> maxHeap = new PriorityQueue<>(1,new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// TODO Auto-generated method stub
return o2-o1;
}
});
for(int i=0;i<input.length;i++){
maxHeap.offer(input[i]);
// 保证堆中元素数目始终是k个
if(i>=k)
maxHeap.poll();
}
int[] a = new int[k];
// 暂时求的数组是降序的
for(int i=0;i<k;i++)
a[i]=maxHeap.poll();
for(int i=k-1;i>=0;i--)
list.add(a[i]);
return list;
}
时间复杂度分析:
PriorityQueue的peek()和element操作是常数时间,add(), offer(), 无参数的remove()以及poll()方法的时间复杂度都是log(N)
因为在程序中限制了堆中元素数目始终<=k个,循环了n次,时间复杂度O(nlogk)
方法2:网友写的创立小根堆与大根堆。
(1)小根堆
代码结合着建堆过程图看
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
Main main = new Main();
int[] b = {98,15,12,8,8,4,43,11,15,9,64,2,76,35,11,3,1};
int k = 5;
System.out.println(main.sort(b, k));
}
//在数组b中查找前k个最小的数据
public ArrayList<Integer> sort(int[] b, int k) {
ArrayList<Integer> list = new ArrayList<>();
if(b.length<k)
return list;
int n=b.length;
//移除堆顶,将最后一个元素推入堆顶
for(int i=0;i<k;i++){
int k1 = (n-i)/2-1; //k1为待调整的初始堆最后一个非叶子节点
for(int j=k1;j>=0;j--){ //建堆
smallHeap(b, j, n-i-1);
}
//System.out.print(b[0]+" ");
list.add(b[0]);
int temp = b[0]; //此处是将堆顶与待排序的最后一个元素交换
b[0] = b[n-i-1];
b[n-i-1] = temp;
}
return list;
}
//建小根堆,从最后一个非终端节点至顶点,参数是(b[], 开始比较的节点下标,最后一个待比较的元素的下标)
// 注:就是调整第k个结点(结点从0开始编号)为根的那棵树。
public static void smallHeap(int[] b, int k, int end){
int i=k, j=2*i+1;//i为要筛选的节点的下标,从0开始,j为 i的左孩子
while(j<=end){ //当还没有比较到叶子节点
if(j<end && b[j]>b[j+1]){ //j保存i的左右孩子中较大的孩子的下标
j++;
}
if(b[i]<b[j]){ //如果i比左右孩子都小,则结束本轮比较
break;
}
else{ // i节点与j节点交换,
int temp = b[i];
b[i] = b[j];
b[j] = temp;
i = j;
j=2*i+1;
}
}
}
}
// 输出:[1, 2, 3, 4, 8]
(2)大根堆
如果求最大的k个数,就建立大根堆。
import java.util.ArrayList;
public class Main3 {
public static void main(String[] args) {
Main3 main3 = new Main3();
int[] b = {98,15,12,8,8,4,43,11,15,9,64,2,76,35,11,3,1};
int k = 5;
System.out.println(main3.sort(b, k));
}
//在数组b中查找前k个最大的数据
public ArrayList<Integer> sort(int[] b, int k) {
ArrayList<Integer> list = new ArrayList<>();
if(b.length<k)
return list;
int n=b.length;
//移除堆顶,将最后一个元素推入堆顶
for(int i=0;i<k;i++){
int k1 = (n-i)/2-1; //k1为待调整的初始堆最后一个非叶子节点
for(int j=k1;j>=0;j--){ //建堆
bigHeap(b, j, n-i-1);
}
//System.out.print(b[0]+" ");
list.add(b[0]);
int temp = b[0]; //此处是将堆顶与待排序的最后一个元素交换
b[0] = b[n-i-1];
b[n-i-1] = temp;
}
return list;
}
//建大根堆
// 注:就是调整第k个结点(结点从0开始编号)为根的那棵树。
public static void bigHeap(int[] b, int k, int end){
int i=k, j=2*i+1;//i为要筛选的节点的下标,从0开始,j为 i的左孩子
while(j<=end){ //当还没有比较到叶子节点
if(j<end && b[j]<b[j+1]){ //j保存i的左右孩子中较大的孩子的下标
j++;
}
if(b[i]>b[j]){ //如果i比左右孩子都大,则结束本轮比较
break;
}
else{ // i节点与j节点交换,
int temp = b[i];
b[i] = b[j];
b[j] = temp;
i = j;
j=2*i+1;
}
}
}
}
// 结果:[98, 76, 64, 43, 35]
本文介绍堆排序的基本概念,包括大根堆与小根堆的建立及调整过程,并通过两个实例详细展示了如何利用堆排序来找出数组中的最小或最大k个数。
1375

被折叠的 条评论
为什么被折叠?



