泛型优先队列API
返回值 | 方法名 | 作用 |
---|---|---|
构造函数 | MaxPQ() | 创建一个优先队列 |
构造函数 | MaxPQ(int N) | 创建一个大小为N的优先队列 |
构造函数 | MaxPQ(Key[] a) | 利用数组a创建一个优先队列 |
void | insert(Key v) | 向队列中插入元素v |
Key | Max() | 返回最大元素 |
Key | delMax() | 删除最大元素并返回 |
boolean | isEmpty() | 判断优先队列是否为空 |
int | size() | 返回优先队列的大小 |
优先队列最重要的操作就是删除最大元素和插入元素
一个优先队列的用例
package cn.ywrby.test;
import edu.princeton.cs.algs4.*;
//一个优先队列的用例
//输入多组数据,打印出其中最大的M行数据
//tinyBatch.txt测试用例
public class TopM {
public static void main(String[] args){
int M=5;
MinPQ<Transaction> pq=new MinPQ<Transaction>(M+1);
while(StdIn.hasNextLine()){
pq.insert(new Transaction(StdIn.readLine())); //将输入插入优先队列
if(pq.size()>M){ //当队列长度大于输出长度,删除最小值
pq.delMin();
}
}
//将队列中的元素放入栈,然后输出,实现倒序
Stack<Transaction> stack=new Stack<Transaction>();
while(!pq.isEmpty()){
stack.push(pq.delMin());
}
for(Transaction t:stack){
StdOut.println(t);
}
}
}
二叉堆
在二叉堆的数组中,每个元素都保证大于等于另外两个特定位置的元素,以此类推,便得到这种结构
当一棵二叉树的每个节点都大于等于它的两个子节点时,它被称为堆有序
易知,根节点是对有序的二叉树的最大节点
二叉堆是一组能够用对有序的完全二叉树排序的元素,并在数组中按照层级储存(不使用数组的第一个位置)
在一个堆中,位置k的结点的父结点的位置为,而它的子节点的位置分别为2k,2k+1。
因此我们即使在不借助指针的情况下,也可以利用索引在树中上下移动
一棵大小为N的完全二叉树的高度不会超过lgN
基于堆的优先序列
package cn.ywrby.sorts;
import cn.ywrby.tools.StopWatch;
import edu.princeton.cs.algs4.*;
import java.util.Random;
/*
* 基于堆的优先序列
* 利用二叉堆这种数据结构可以很轻松的实现对新插入元素与原先数组内元素比较的工作
* 因此可以处理动态传入的大量数据
* 与其他排序最有别的就是其中的上浮(swim)与下沉(sink)两部操作
* */
public class MaxPQ<Key extends Comparable<Key>> {
private Key[] pq;
private int N = 0;
//无参构造函数,创建空的优先队列
public MaxPQ() {
pq = (Key[]) new Comparable[1];
}
//创造大小为max的优先队列
public MaxPQ(int max) {
pq = (Key[]) new Comparable[max + 1];
}
//利用数组a创建优先队列
public MaxPQ(Key[] a) {
pq = a;
}
//插入元素,将元素插入到数组最后一位,然后上浮至合适位置
public void insert(Key v) {
pq[++N] = v;
swim(N);
}
//返回最大值,在优先队列中也就是根节点1的位置
public Key Max() {
return pq[1];
}
/*
* 删除最大值
* 保存根节点。然后交换根节点与末尾结点
* 再对新的根节点进行下沉到合适位置
* */
public Key delMax() {
Key max = pq[1];
exch(1, N--);
pq[N + 1] = null;
sink(1);
return max;
}
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j])<0;
}
private void exch(int i, int j) {
Key temp=pq[i];
pq[i]=pq[j];
pq[j]=temp;
}
/*
* 上浮操作
* 位置为k的结点,它的父结点位置为k/2向下取整
* 将它与其父结点比较,若父结点小于该节点就进行交换
* 重新判断它与新的父结点大小关系
* 直至符合二叉堆条件
* */
private void swim(int k) {
while(k>1 && less(k/2,k)){
exch(k,k/2);
k=k/2;
}
}
/*
* 下沉操作
* 位置为k的结点,其子节点为2k与2k+1
* 与上浮操作基本一致,就是不断比较
* 但由于一个节点有两个子节点,所以比较时要先选择较大的子节点
* */
private void sink(int k) {
while(2*k<N){
int j=2*k;
if(j<N && less(j,j+1)) j++;
if(!less(k,j)) break;
exch(k,j);
k=j;
}
}
public static void main(String[] args){
int N=100000;
int M=5;
MaxPQ<Double> pq=new MaxPQ<Double>(M+1);
StopWatch watch=new StopWatch();
for(int i=0;i<N;i++){
pq.insert(Math.random());
if(pq.size()>M){
pq.delMax();
}
}
double time=watch.elapsedTime();
Stack<Double> stack=new Stack<Double>();
while(!pq.isEmpty()){
stack.push(pq.delMax());
}
for(Double t:stack){
StdOut.println(t);
}
System.out.println("time="+time);
}
}
算法分析
对于一个含有N个元素的基于堆的优先队列,插入元素的操作只需要不超过(lgN+1)次比较(由上述结论一致二叉堆深度不超过lgN,故最坏情况在每层都进行比较),删除元素操作只需要不超过2lgN次比较(删除操作每次进行两次比较,一次挑出子节点中较大值,另一次与目标节点进行比较)
堆排序
堆排序可以分为两个阶段
1. 首先在堆的构造阶段,将原始数组重新组织成为一个二叉堆
2. 然后在下沉排序阶段,从堆中按递减顺序取出所有元素并得到排序结果
堆排序实现
public void sort(){
for(int k=N/2;k>=1;k--){
sink(k);
}
while(N>1){
//StdOut.print(pq[1]+" ");
exch(1,N--);
sink(1);
}
//StdOut.println();
}