本文将先简单的介绍一下二叉堆,然后再使用二叉堆实现优先队列。
1、二叉堆实际上就是一种完全二叉树,即除了树的最后一层节点不需要是满的,其他的每一层从左到右都是完全满的。
2、常见的二叉堆有最大堆和最小堆,区别在于根节点是树中的最大值还是最小值。
3、在使用二叉堆实现的优先队列中进行数据的插入、删除的时间复杂度为O(logN)。
4、堆是弱序的,或者说堆是基本无序的。因为不像二叉搜索树一样中节点的右子节点都大于左子结点,在堆中只需要满足从根节点到任意个页节点的路径是单调递增或单调递减的即可。这意味着,堆不能很快的查找指定关键字对应的元素,同样也不能很快的删除指定关键字对应的元素。但是在堆上可以很快的插入元素,同时还可以很快的删除最大元素(最大堆)/最小元素(最小堆)。
5、数组表示的二叉堆中,节点n的左子结点是节点2n+1,右子结点是节点2n+2,父节点是(n-1)/2。
好了,下面是二叉堆的实现:
package com.wly.algorithmbase.datastructure; /** * 数组实现最大堆、最小堆 * @author wly * */ public class BinaryHeap { private final int MAX_HEAP = 1; //最大堆标记 private final int MIN_HEAP = 2; //最小堆标记 private int type; //用以表示堆的类型 private Node[] array; //用于存储树结构的数组 private int CAPACITY = 12; //数组扩展步长 private int mSize = 0; //用于表示树中当前的元素个数 public static void main(String[] args) { //测试最大堆 System.out.println("---测试最大堆---"); BinaryHeap binaryHeap = new BinaryHeap(1); binaryHeap.insert(new Node("A",23)); binaryHeap.insert(new Node("B",26)); binaryHeap.insert(new Node("C",43)); binaryHeap.insert(new Node("D",12)); binaryHeap.insert(new Node("E",78)); binaryHeap.insert(new Node("F",66)); System.out.println("构建堆:"); binaryHeap.printNodes(); binaryHeap.remove(); System.out.println("\n删除测试"); binaryHeap.printNodes(); //测试最小堆 System.out.println("\n---测试最小堆---"); binaryHeap = new BinaryHeap(2); binaryHeap.insert(new Node("A",23)); binaryHeap.insert(new Node("B",26)); binaryHeap.insert(new Node("C",43)); binaryHeap.insert(new Node("D",12)); binaryHeap.insert(new Node("E",78)); binaryHeap.insert(new Node("F",66)); System.out.println("构建堆:"); binaryHeap.printNodes(); binaryHeap.remove(); System.out.println("\n删除测试:"); binaryHeap.printNodes(); } /** * 构造函数,可以指定最大堆或最小堆 * @param heapType 1表示最大堆,2表示最小堆 */ public BinaryHeap(int heapType) { this.type = heapType; array = new Node[CAPACITY]; } /** * 插入节点,使用向上筛选法(为什么?保持二叉堆的二叉树的结构) */ public void insert(Node node) { if(mSize == 0) { //插入根节点 array[mSize++] = node; } else { if(mSize == CAPACITY) { Node[] temp = new Node[array.length + CAPACITY]; for(int i = 0;i<array.length;i++) { temp[i] = temp[i]; } array = temp; temp = null; } int parentIndex = (mSize-1)/2; int index = mSize; //插入到树最后一层的最右一个 array[mSize++] = node; Node parentNode = array[parentIndex];//拿到当前插入节点的父节点 switch(type) { case MAX_HEAP://最大堆 //向上筛选 while(parentNode != null && parentNode.getKey() < node.getKey()) { //交换位置 array[parentIndex] = node; array[index] = parentNode; index = parentIndex; parentIndex = (parentIndex-1)/2; parentNode = array[parentIndex]; } break; case MIN_HEAP: //最小堆 //向上筛选 while(parentNode != null && parentNode.getKey() > node.getKey()) { //交换位置 array[parentIndex] = node; array[index] = parentNode; index = parentIndex; parentIndex = (parentIndex-1)/2; parentNode = array[parentIndex]; } break; } } } /** * 移除根节点 * 一、移除根节点 * 二、将二叉堆的最后一层的最右边元素放到根节点位置(保持完全二叉树结构) * 三、将当前根节点向下遍历直到它找到合适的位置 */ public Node remove() { Node result; if(mSize == 0) { System.out.println("错误,堆已空无法再行移除元素"); return null; } else { //移除根节点,并将最后一个节点放置到根节点位置 result = array[0]; array[0] = array[mSize-1]; array[mSize-1] = null; mSize --; int index = 0; int comparaChildIndex = 0; switch(type) { case MAX_HEAP: //最大堆 while(index < mSize/2) { //很神奇的边界条件,可以考虑一下 int leftChild = 2*index+1; int rightChild = leftChild+1; //取两个子节点中较大的那个的索引值 if(rightChild < mSize && //右子结点是否 array[leftChild].getKey() <= array[rightChild].getKey()) { comparaChildIndex = rightChild; } else { comparaChildIndex = leftChild; } if(array[index].getKey() < array[comparaChildIndex].getKey()) { //交换 Node temp = array[index]; array[index] = array[comparaChildIndex]; array[comparaChildIndex] = temp; index = comparaChildIndex; } } break; case MIN_HEAP: //最小堆 while(index < mSize/2) { //很神奇的边界条件,可以考虑一下 //取两个子节点中较小的那个的索引值 if((2*index+2) < mSize && array[2*index+1].getKey() <= array[2*index+2].getKey()) { comparaChildIndex = 2*index+1; } else { comparaChildIndex = 2*index+2; } if(array[index].getKey() > array[comparaChildIndex].getKey()) { //交换 Node temp = array[index]; array[index] = array[comparaChildIndex]; array[comparaChildIndex] = temp; index = comparaChildIndex; } } break; } return result; } } /** * 打印节点 */ public void printNodes() { for(int i=0;i<mSize;i++) { System.out.print(array[i].getKey() + " "); } } } /** * 二叉堆中的节点类 * @author wly * */ class Node { String data; //包含的数据 int key; public Node(String data, int key) { super(); this.data = data; this.key = key; } public String getData() { return data; } public void setData(String data) { this.data = data; } public int getKey() { return key; } public void setKey(int key) { this.key = key; } }
运行效果:
---测试最大堆--- 构建堆: 78 43 66 12 23 26 删除测试 66 43 26 12 23 ---测试最小堆--- 构建堆: 12 23 43 26 78 66 删除测试: 23 26 43 66 78
下面是一个交换的元素移位的技巧,可以使用一次复制和多次覆盖,而不是多次交换。因为交换一次交换需要3次复制!
最后是用上面堆结构实现的优先队列:
package com.wly.algorithmbase.datastructure; /** * 基于二叉堆的优先队列实现 * @author wly * */ public class PriorityQueueWithHeap { private BinaryHeap heap; /** * 构造函数 * @param heapType 1最大堆的优先队列,2最小堆的优先队列 */ public PriorityQueueWithHeap(int heapType) { heap = new BinaryHeap(heapType); } public void insert(Node node) { heap.insert(node); } public Node remove() { return heap.remove(); } }
O啦~~~
转载请保留出处:javascript:void(0)
谢谢!!