Java数据结构-堆排序实现优先队列

堆其实可以看作一个二叉树。大顶堆,也就是树中的每一个父节点都比子节点大,但是左右节点并没有严格的大小关系。小顶堆就是树中的每一个父节点都比子节点小。
在这里插入图片描述
对堆中的元素进行排序,按层进行排序,第一层即根节点为0,第二层的两个结点分别为1,2,使用数组进行存储,这样在存储时,这些排序就是各个结点在数组中的索引。
向堆中插入一个元素的过程
向一个大顶堆中插入一个元素,首先把这个元素放在堆的最后一个位置,也即放入树中的最后一个结点,因为这里实现堆是使用的数组,所以在数组中访问最后一个元素的时间复杂度为O(1),然后用此结点的索引值计算出它的父亲结点,接着继续让这个结点和它的父节点进行比较,如果大于它的父节点,就进行值交换。比如原来树中一共有7个结点,再插入一个结点后,此结点的索引值为7,父节点的索引值为3,让数组中索引为3和索引为7的结点进行交换。交换之后新插入的结点就在索引为3的这个位置,继续与父节点进行比较,它的父节点索引为1,比较结果同上。如果它比父节点小的话就不用进行比较了,因为既然比父节点都小,那必然比它的爷爷结点更小。
在这里插入图片描述
其实堆排序天然适合实现优先队列。为什么这么说呢,每次进入队列时带有优先级,就像这里的大小值一样。每次进入堆中进行简单的排序。然后每次出队时,直接出根节点,因为根节点属于优先级最高的,那出了根节点之后,树不就成了两半了吗,所以这里引出了一个出队的方法。
出队的过程
出队即删除根节点,因为根节点在数组中在索引为0的位置,首先让索引为0的元素和数组中最后一个元素交换值,然后删除最后一个元素。经过这样的操作后,这个堆就不符合规则了,因为可能交换了之后根节点的值是最小的。所以需要有一个沉底的过程,即让根节点和左右子树中较大的进行比较,如果小于左右子树中较大的子树就就进行交换,交换之后接着进行与左右子树比较。
如果采用数组实现优先队列,则必须入队时就进行排序,后者出队时进行排序,时间复杂度为O(n),而使用堆排序实现优先队列每次只需要排列每条树的路径上的元素,设树的高度为h,时间复杂度为O(h),即O(logn),当队列中的元素越多,性能差异越明显。当n=1024,h=10;n=1000000时,h=20…
大顶堆的实现:

/**
 * @Author: Cui
 * @Date: 2020/12/22
 * @Description: 大顶堆
 */
public class MaxHeap<E extends Comparable<E>> {

    private Array<E> data;

    public MaxHeap(int capacity){
        data = new Array<>(capacity);
    }
    public MaxHeap(){
        data = new Array<>();
    }

    //返回堆中元素个数
    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 index * 2 + 1;
    }

    //返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
    private int rightChild(int index){
        return index * 2 + 2;
    }

    //向堆中添加元素
    public void add(E e){
        data.addLast(e);
        siftUp(data.getSize()-1);
    }

    //向堆中添加元素时进行比较,当索引=0或者自身值<父亲结点值时停止循环
    private void siftUp(int k) {
        while (k > 0 && data.get(parent(k)).compareTo(data.get(k))<0){
            data.swap(k,parent(k));     //交换数组中索引为k和它父节点的元素
            k = parent(k);
        }
    }

    //看堆中的最大元素
    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++;//进入这里的条件是存在右孩子,且左孩子的值小于右孩子的值
            }
            //这时 j 存储的为左右孩子中值较大的

            //当未违反堆的性质时,即自身结点大于孩子结点的值的较大值时,直接结束循环
            if(data.get(k).compareTo(data.get(j)) >= 0){
                break;
            }
            //当违反了堆的性质时,进行交换
            data.swap(k,j);
            k = j;
        }
    }
}

队列接口

public interface Queue<E> {

    int getSize();
    boolean isEmpty();
    void enqueue(E e);
    E dequeue();
    E getFront();
}

实现的优先队列

/**
 * @Author: Cui
 * @Date: 2020/12/23
 * @Description:
 */
public class PriorityQueue<E extends Comparable<E>> implements Queue<E>{

    private MaxHeap<E> maxHeap;

    public PriorityQueue(){
        maxHeap = new MaxHeap<>();
    }

    @Override
    public int getSize() {
        return maxHeap.size();
    }

    @Override
    public boolean isEmpty() {
        return maxHeap.isEmpty();
    }

    @Override
    public void enqueue(E e) {
        maxHeap.add(e);
    }

    @Override
    public E dequeue() {
        return maxHeap.extractMax();
    }

    @Override
    public E getFront() {
        return maxHeap.findMax();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值