数据结构和算法分析 优先队列
也可以使用普通的数组来实现优先队列,当然push复杂度为O(1),pop复杂度为O(N)。
也可以使用二叉查找树来实现,如果使用平衡树,那么插入和查找的复杂度就是所使用的平衡树的复杂度。
一般使用堆来实现优先队列。
堆:
- 堆是一棵完全二叉树(complete binary tree)。
- 由于完全二叉树的规律性,它可以用一个数组来实现而不需要指针。排除数组中位置0,在数组中任一位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元(2i+1)上。它的父亲节点在位置i/2上。
- 在一个堆中,对于每一个节点X,都小于它子节点的值。
堆的基本操作操作
插入操作:在堆的下一个空闲位置放入插入元素,然后将它不断上浮到合适的位置为止。时间复杂度:O(LogN)。
删除最小元:将堆顶的元素移除,然后将最后一个元素移动到堆顶,然后将堆顶元素不断下沉到合适的位置。时间复杂度:O(LogN)。
构建堆:将堆的元素的所有非叶子节点下沉到合适的位置(从最后一个非叶子结点开始,到根节点结束)。时间复杂度是O(N),而不是O(NlogN)。
代码:
-
package com.zjf;
-
-
import java.util.Arrays;
-
import java.util.List;
-
-
public class PriorityHeap<E extends Comparable<? super E>> {
-
-
public static void main(String[] args) {
-
// TODO Auto-generated method stub
-
PriorityHeap<Integer> heap = new PriorityHeap<Integer>();
-
heap.push(5);
-
heap.push(3);
-
heap.push(8);
-
heap.push(1);
-
heap.push(7);
-
heap.push(6);
-
System.out.println(heap);
-
System.out.println(heap.pop());
-
System.out.println(heap);
-
Integer[] ia = {2,5,4,9,7,3,6,8};
-
PriorityHeap<Integer> heap1 = new PriorityHeap<Integer>(ia);
-
System.out.println(heap1);
-
}
-
-
// 简单实现 就写死了 不再考虑扩展
-
private int currentSize = 0;
-
private Comparable[] arr = new Comparable[1024];
-
-
public void push(E e) {
-
// 注意数组0下标的位置 不存储内容:为了满足n和2n+1的关系
-
// 插入到最后一个位置
-
arr[currentSize + 1] = e;
-
int i = currentSize + 1;
-
// 将刚才插入到最后一个位置的节点慢慢上浮到合适的位置
-
while (i > 1) {
-
if (arr[i].compareTo(arr[i / 2]) < 0) {
-
// 交换上浮
-
swap(i, i / 2);
-
}
-
i = i / 2;
-
}
-
// 操作成功后currentSize++
-
currentSize++;
-
}
-
-
public PriorityHeap(E[] es) {
-
// 将传入数组的内容一字排开放入堆中
-
for (int i = 0; i < es.length; i++) {
-
arr[i + 1] = es[i];
-
}
-
currentSize = es.length;
-
// 构建堆
-
buildHeap();
-
}
-
-
public PriorityHeap() {
-
-
}
-
-
// 构建堆:将乱序的数组 构建成满足堆序要求的堆结构
-
public void buildHeap() {
-
// 从currentSize的一半开始 往上 逐个下沉到合适的位置
-
//currentSize/2是最后一个有子节点的位置 再往后就是叶子结点了
-
for(int i = currentSize/2; i >=1;i--)
-
{
-
down(i);
-
}
-
}
-
-
public E pop() {
-
if (currentSize == 0) {
-
return null;
-
}
-
E first = (E) arr[1];
-
// 第一个元素空了下来 将最后一个元素移到这个位置 然后下沉到合适的位置
-
arr[1] = arr[currentSize];
-
// 下沉第一个元素
-
down(1);
-
// 成功后currentSize--
-
currentSize--;
-
return first;
-
}
-
-
// 下沉
-
private void down(int i) {
-
int child = i * 2;
-
// 没有子节点 跳出循环
-
if (child > currentSize) {
-
return;
-
}
-
// 对比有两个子节点 那么需要对比它们两个 找出最小的一个
-
if ((child + 1 <= currentSize)
-
&& arr[child].compareTo(arr[child + 1]) > 0) {
-
child++;
-
}
-
if (arr[child].compareTo(arr[i]) < 0) {
-
swap(i, child);
-
down(child);
-
}
-
}
-
-
// 交换
-
private void swap(int i, int j) {
-
Comparable temp = arr[i];
-
arr[i] = arr[j];
-
arr[j] = temp;
-
-
}
-
-
@Override
-
public String toString() {
-
List l = Arrays.asList(arr).subList(1, currentSize);
-
return Arrays.toString(l.toArray());
-
}
-
}
打印:
[1, 3, 6, 5, 7]
1
[3, 5, 6, 8]
[2, 5, 3, 8, 7, 4, 6]
原始的二叉堆使用数组实现,是一个完全二叉树,其实现简单,但是在合并方面不尽人意,只能通过构建一个新的堆来实现两个堆的合并,时间复杂度为O(N)。而左式堆和斜堆是两种合并高效的堆,并且左式堆以及斜堆的insert以及deleteMin等操作都是以merge操作为基础的。merge时间复杂度可以达到O(logN)。
我们习惯用自己的行为准则审视他人,并时刻准备加以指摘。