数据结构-优先队列和堆篇
文章目录
一. 优先队列
-
普通队列:先进先出,后进后出
-
优先队列:出队顺序和入队顺序无关;和优先级相关
动态选择优先级最高的任务执行
定义队列:
Interface Queue <-------------PriorityQueue implement Queue
- void enqueue(E)
- E dequeueu()
- E getFront()
- int getSize()
- boolean isEmpty()
数据结构 | 入队 | 出队 |
---|---|---|
普通线性结构 | O(1) | O(n) |
顺序线性结构 | O(logn) | O(1) |
堆 | O(n) | O(logn) |
二. 堆
二叉堆Binary Heap
-
二叉堆是一棵完全二叉树:把元素顺序排列成树的形状
-
堆中某个节点的值总是不大于其父节点的值→最大堆(相应的可以定义最小堆)
1. 最大堆定义
public class MaxHeap<E extends Comparable<E>> {
private Array1<E> data;
public MaxHeap(int capacity) {
data = new Array1<E>(capacity);
}
public MaxHeap() {
data = new Array1<E>();
}
//返回堆中的元素个数
public int size(){
return data.getSize();
}
//返回一个布尔值,表示堆中是否为空
public boolean isEmpty(){
return data.isEmpty();
}
//返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
private int parent(int index){
if(index==0){
throw new IllegalArgumentException("index-0 doesn't have parent.");
}
return (index-1)/2;
}
//返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
private int leftChild(int index){
return 2 * index +1;
}
//返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
private int rightChild(int index){
return 2 * index +2;
}
}
2. 堆的操作
- 在堆中添加元素
//向堆中添加元素
public void add(E e) {
data.addList(e);
siftUp(data.getSize() - 1);
}
private void siftUp(int k) {
while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
data.swap(k, parent(k));
k = parent(k);
}
}
- 在第一章数组 Array1中添加函数
/**
* 交换元素:在数组最后添加一个元素,然后跟父亲节点比较,大就交换
*/
public void swap(int i, int j) {
if (i < 0 || i >= size || j < 0 || j >= size) {
throw new IllegalArgumentException("Index is illegal");
}
E t = data[i];
data[j] = data[i];
data[j] = t;
}
- 在堆中取出元素
- 最后一个元素跟这个元素换,最后一个元素删除
- 每次下沉跟他的两个孩子比较,选择大的跟这个元素调换
- add和extracMax时间复杂度都是O(logn)
//看堆中的最大元素
public E findMax() {
if (data.getSize() == 0) {
throw new IllegalArgumentException("Can not findMax when heap is empty");
}
return data.get(0);
}
//从堆中取出最大元素
public E extractMax() {
E ret = findMax();
data.swap(0, data.getSize() - 1);
data.removeLast();
siftDown(0);
return ret;
}
private void siftDown(int k) {
while (leftChild(k) < data.getSize()) {
//左孩子节点值
int j = leftChild(k);
//如果右孩子不是叶子结点并且右孩子大于左孩子
if (j + 1 < data.getSize() &&
data.get(j + 1).compareTo(data.get(j)) > 0) {
j = rightChild(k);
}
//data[j]是leftChild和rightChild中的最大值
if (data.get(k).compareTo(data.get(j)) >= 0) {
break;
}
data.swap(k, j);
k = j;
}
}
- 测试类
public static void main(String[] args) {
int n = 1000000;
MaxHeap<Integer> maxHeap = new MaxHeap<Integer>();
Random random = new Random();
for (int i = 0; i < n; i++) {
maxHeap.add(random.nextInt(Integer.MAX_VALUE));
}
int[] arr = new int[n];
for(int i=0;i<n;i++){
arr[i] = maxHeap.extractMax();
}
for(int i=1;i<n;i++){
if(arr[i-1]<arr[i])
throw new IllegalArgumentException("Error");
}
System.out.println("Test MaxHeap completed");
}
3. Replace:取出最大元素后,放入一个新元素
- 实现:可以先extracMax,再add,两次O(logn)的操作
- 实现:可以直接将堆顶元素替换以后Sift Down,一次O(logn)操作
//取出堆中的最大元素,并且替换成元素e
public E replace(E e){
E ret = findMax();
data.set(0,e);
siftDown(0);
return ret;
}
4. heapify: 将任意数组整理成堆的形状
找到最后一个非叶子结点,向前遍历,对每个节点进行下浮操作
最后一个非叶子节点:parent(arr.length-1)
Heapify的算法复杂度:
-
将n个元素诸逐个插入到一个空堆中,算法复杂度是O(nlogn)
-
heapify的过程,算法复杂度为O(n)
Heapify实现
- 在Array中添加传入数组的构造函数
/**
* 传入数组
*/
public Array1(E[] arr){
data = (E[])new Object[arr.length];
for(int i=0;i<arr.length;i++){
data[i] = arr[i];
}
size = arr.length;
}
- 找到最后一个非叶子结点,向前遍历,对每个节点进行下沉操作
public MaxHeap(E[] arr){
data = new Array1<E>(arr);
for(int i=parent(arr.length-1);i>=0;i--){
siftDown(i);
}
}
- 复杂度测试
public class MaxHeapMain {
private static double testMaxHeap(Integer[] testData, boolean isHeapify) {
long startTime = System.nanoTime();
MaxHeap<Integer> maxHeap;
if (isHeapify) {
maxHeap = new MaxHeap<Integer>(testData);
} else {
maxHeap = new MaxHeap<Integer>();
for (int num : testData) {
maxHeap.add(num);
}
}
int[] arr = new int[testData.length];
for (int i = 0; i < testData.length; i++) {
arr[i] = maxHeap.extractMax();
}
for (int i = 1; i < testData.length; i++) {
if (arr[i - 1] < arr[i]) {
throw new IllegalArgumentException("Error");
}
}
long endTime = System.nanoTime();
System.out.println("Test MaxHeap completed");
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
int n = 1000000;
Random random = new Random();
Integer[] testData = new Integer[n];
for (int i = 0; i < n; i++) {
testData[i] = random.nextInt(Integer.MAX_VALUE);
}
double time1 = testMaxHeap(testData, false);
System.out.println("Without heapify:" + time1 + "s");
double time2 = testMaxHeap(testData, true);
System.out.println("With heapify:" + time2 + "s");
}
}
结果:
Test MaxHeap completed
Without heapify:0.249022s
Test MaxHeap completed
With heapify:0.1057637s
5. 基于堆的优先队列
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e); //入队
E dequeue(); //出队
E getFront(); //获取队首元素
}
//基于堆的优先队列
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
private MaxHeap<E> maxHeap;
public PriorityQueue(){
maxHeap = new MaxHeap<E>();
}
@Override
public int getSize(){
return maxHeap.size();
}
@Override
public boolean isEmpty(){
return maxHeap.isEmpty();
}
@Override
public E getFront(){
return maxHeap.findMax();
}
@Override
public void enqueue(E e){
maxHeap.add(e);
}
@Override
public E dequeue(){
return maxHeap.extractMax();
}
}