使用Java实现最大堆

最大堆和最小堆是二叉堆的两种形式。

最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。

最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。

堆实际上是一种完全二叉树。最大(小)堆建堆的复杂度是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();
    }
}

我的实现思路在注释里。欢迎拍砖。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值