最大堆和最小堆是二叉堆的两种形式。
最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。
最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。
堆实际上是一种完全二叉树。最大(小)堆建堆的复杂度是o(n),也就是说可以在线性时间内把一个无序的序列转化为堆。插入的时间复杂度为o(log2(n))(2为底n的对数的时间复杂度),删除的时间复杂度也是这么多,因为对于插入删除,在最坏的情况下,插入将整个树从最低的一端比较到树的最高一端,而删除是从树的最高的一端比较到树最低的一端。
PS、这里有个疑问,就是从算法上大致看过去,会认为建堆的复杂度是o(nlog2(n)),实际计算出来结果是o(n),证明过程如下:
建堆的计算时间为2^i(log2(n)-1)从i=0加到i=log2(n),因为第i层的结点最多2^i个,每个元素在建堆调整位置的时候最坏情况是从最底层调整到最高层,因此第i层最坏情况下的总共调整次数应该是2^i(log2(n)-1),这个结果经过整理计算出来实际上复杂度是o(n)。
在之前我们用Java实现了二叉树的顺序存储和链式存储,这次用Java实现的最大堆是基于前面写的链式存储的二叉树实现的。
在实现最大堆的时候,需要在之前的TreeNode<E>类的基础上加上一个变量num表示当前结点在堆中的编号,而且要求泛型要是整形,所以我定义了一个HeapNode继承了TreeNode<Integer>(优先考虑复用,但是这里明显出现了HeapNode is TreeNode<Integer>的情形,因此采用继承):
package com.maxHeap;
import com.linkedTree.TreeNode;
public class HeapNode extends TreeNode<Integer> {//HeapNode is TreeNode<Integer>
private HeapNode leftChild;
private HeapNode rightChild;
private int num;//编号
public HeapNode(Integer e) {
super(e);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public TreeNode<Integer> getRightChild() {
return rightChild;
}
@Override
public void setRightChild(TreeNode<Integer> rightChild) {
this.rightChild = (HeapNode) rightChild;
}
@Override
public TreeNode<Integer> getLeftChild() {
return this.leftChild;
}
@Override
public void setLeftChild(TreeNode<Integer> leftChild) {
this.leftChild = (HeapNode) leftChild;
}
}
紧接着,同理写了一个MaxHeap类继承LinkedTree<Integer>:
package com.maxHeap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.linkedTree.LinkedTree;
import com.linkedTree.TreeNode;
public class MaxHeap extends LinkedTree<Integer> {//MaxHeap is LinkedTree<Integer> MaxHeap要求是完全二叉树
// private int size;//不用定义size,因为nodeMap.size()就是树的大小
private Map<Integer,HeapNode> nodeMap = new HashMap<Integer,HeapNode>();//编号与结点的映射
private void setRoot(HeapNode root){
this.root = root;
}
@Override
//重写getParent方法 因为节点有编号之后 有更快的方法获得父节点
public TreeNode<Integer> getParent(TreeNode<Integer> node) {//获取node结点的父节点
HeapNode heapNode = (HeapNode)node;
int nodeNum = heapNode.getNum();
return (nodeNum%2==0)?nodeMap.get((nodeNum>>1)-1):nodeMap.get((nodeNum-1)>>1);//获取nodeMap中对应结点 采用位运算增加效率
}
private void swapNode(HeapNode node1 , HeapNode node2){//交换node1和node2以及它的映射
int item1 = node1.getItem();
node1.setItem(node2.getItem());
node2.setItem(item1);
nodeMap.put(node1.getNum(), node1);
nodeMap.put(node2.getNum(), node2);
}
public void add(int data){//添加一个元素
HeapNode heapNode = new HeapNode(data);
shiftUpNode(nodeMap.size(),heapNode);//从最新的位置移到应该放的位置
}
public void add(Set<Integer> dataSet){//添加一个集合 迭代添加集合中的每个元素
Iterator<Integer> i = dataSet.iterator();
while(i.hasNext()){
add(i.next());
}
}
public MaxHeap(Set<Integer> set){//通过一个set创建一个MaxHeap
Iterator<Integer> i = set.iterator();
int num = 0;
while(i.hasNext()){
Integer e = i.next();
HeapNode node = new HeapNode(e);
shiftUpNode(num, node);//从最新的一个位置的结点移到他应该在的位置
num++;
}
}
private void shiftUpNode(int num, HeapNode node) {//从最靠后的结点移到准确的位置
if(this.root==null){
this.setRoot(node);//没有根则设置根
node.setNum(num);//设置编号
}
else{
boolean right = (num%2 == 0);//是否是父节点的右子树
if(right){
nodeMap.get((num>>1)-1).setRightChild(node);
}
else{
nodeMap.get((num-1)>>1).setLeftChild(node);
}
node.setNum(num);
HeapNode heapNode = node;//中间量
while(this.getParent(heapNode).getItem()<heapNode.getItem()){//构建最大堆
swapNode((HeapNode) this.getParent(heapNode),heapNode);//交换节点
heapNode = (HeapNode) this.getParent(heapNode);//交换完之后 保存父节点的位置 继续与其父节点进行比较
if(this.getParent(heapNode)==null)
break;
}
}
nodeMap.put(num, node);//保存映射
}
private void shiftDownNode(){//把根节点下移到准确的位置
HeapNode pointer = (HeapNode) root;
while(true){
HeapNode leftChild = (HeapNode) pointer.getLeftChild();
HeapNode rightChild = (HeapNode) pointer.getRightChild();
boolean left = leftChild != null;
boolean right = rightChild != null;
if(left&&(pointer.getItem()<leftChild.getItem())||(right&&pointer.getItem()<rightChild.getItem())){//有左结点或者右结点并且可交换
if(left&&right){//左右节点都有
if(leftChild.getItem()<rightChild.getItem()){//谁大谁上
swapNode(pointer,(HeapNode) pointer.getRightChild());
pointer = (HeapNode) pointer.getRightChild();//继续跟踪被下放的根节点
}
else{
swapNode(pointer,(HeapNode) pointer.getLeftChild());
pointer = (HeapNode) pointer.getLeftChild();
}
}
else if(!left){//没有左结点
swapNode(pointer,(HeapNode) pointer.getRightChild());
pointer = (HeapNode) pointer.getRightChild();
}
else{//没有右结点
swapNode(pointer,(HeapNode) pointer.getLeftChild());
pointer = (HeapNode) pointer.getLeftChild();
}
}
else
return;
}
}
public int peek(){//获取最大的结点 不删除
return this.root.getItem();
}
public int pop(){//获取最大的结点 并且删除
int size = nodeMap.size();//映射个数
swapNode((HeapNode) this.root, nodeMap.get(size-1));//交换根结点和最后一个结点
int maxNum = nodeMap.get(size-1).getItem();//最大值
if((size-1)%2==0)//最后一个结点是父节点的右结点
getParent(nodeMap.get(size-1)).setRightChild(null);//将父节点的右结点置空 help gc 下同
else
getParent(nodeMap.get(size-1)).setLeftChild(null);
nodeMap.remove(size-1);//删除最大的
shiftDownNode();//将根结点调到适当的位置
return maxNum;
}
}
最后写一个测试类Test来进行测试:
class Test{
public static void main(String[] args){
System.out.println("MaxHeap Test:");
MaxHeap maxHeap = new MaxHeap(set);
maxHeap.breadFirstOrder();
maxHeap.preOrder();
System.out.println(maxHeap.getRoot().getItem());
maxHeap.add(9);
maxHeap.breadFirstOrder();
Set<Integer> set2 = new HashSet<Integer>();
set2.add(9);
set2.add(11);
set2.add(10);
set2.add(19);
maxHeap.add(set2);
maxHeap.breadFirstOrder();
maxHeap.pop();
maxHeap.breadFirstOrder();
}
}
我的实现思路在注释里。欢迎拍砖。