【12】优先级队列的介绍和实现

文章介绍了优先级队列的概念,它不同于普通队列按先进先出的规则,而是按优先级取出数据。文章提供了三个不同的实现方式:基于无序数组、有序数组和堆的优先级队列,分别分析了它们在添加和获取数据时的特点和实现方法。
摘要由CSDN通过智能技术生成

目录

1、介绍

2、优先级队列的接口定义

2.1、队列基础接口

3、基于无序数组实现优先级队列

4、基于有序数组实现优先级队列

5、基于堆实现优先级队列


1、介绍

优先级队列是指在获取队列中的数据时,按照每一个数据的优先级进行获取,不按照先进先出的规则获取了。那么每一个数据都必须有一个相应的优先级。

优先级队列添加数据和普通队列没有区别,但是在获取数据时就不是获取队列头部的数据了,而是获取优先级最高的数据。

2、优先级队列的接口定义

2.1、队列基础接口

和普通队列相比,优先级队列就是在满足普通队列的所有需求。

/**
 * 队列基础接口
 *
 * @author zjj_admin
 */
public interface Queue<E> {
​
​
    /**
     * 向队列的尾部插入数据
     *
     * @param value 添加数据
     * @return 是否添加成功
     */
    boolean offer(E value);
​
    /**
     * 从队列的头部拉取一个数据,并从队列中移除
     *
     * @return 返回头部数据,没有数据时返回 null
     */
    E poll();
​
    /**
     * 获取队列头部数据,不移除数据
     *
     * @return
     */
    E peek();
​
    /**
     * 判断队列是否为空
     *
     * @return 队列是否为空
     */
    boolean isEmpty();
​
    /**
     * 队列是否已经满了
     *
     * @return 队列是否已满
     */
    boolean isFull();
​
    /**
     * 获取队列中元素的个数
     *
     * @return 元素个数
     */
    int size();
​
​
}

2.2、优先级接口定义

/**
 * @author zjj_admin
 */
public interface Priority {
​
    /**
     * 优先级,越大说明优先级越高
     *
     * @return
     */
    int priority();
}

3、基于无序数组实现优先级队列

使用无序数组,在数组中存储的数据是没有顺序的,所以在获取队列元素时需要手动找到优先级最大的元素。

在添加数据时比较方便。但是在获取数据时复杂一些。

/**
 * 优先级队列添加数据和普通队列没有区别,但是在获取数据时就不是获取队列头部的数据了,而是获取优先级最高的数据。
 * <p>
 * 基于无序数组实现优先级队列
 * 使用无序数组,在添加数据时比较方便。但是在获取数据时复杂一些。
 *
 * @author zjj_admin
 */
public class ArrayPriorityQueue1<E extends Priority> implements Queue<E> {
​
​
    private final Priority[] array;
    private int size;
​
    public ArrayPriorityQueue1(int capacity) {
        size = 0;
        array = new Priority[capacity];
    }
​
    /**
     * 添加数据
     *
     * @param value 添加数据
     * @return
     */
    @Override
    public boolean offer(E value) {
        if (isFull()) {
            return false;
        }
        //向数组的尾部添加一个数据即可
        array[size] = value;
        size++;
        return true;
    }
​
​
    /**
     * 移除并返回优先级最高的一个数据
     *
     * @return
     */
    @Override
    public E poll() {
        if (isEmpty()) {
            return null;
        }
        //获取优先级最大的元素下标
        int max = selectMaxPriority();
        @SuppressWarnings("all")
        E value = (E) array[max];
        //从索引 max + 1 开始,将后面的数据整体向前移动 1 位
        System.arraycopy(array, max + 1, array, max, size - max - 1);
        size--;
        return value;
    }
​
    /**
     * 获取优先级最大的元素下标
     *
     * @return
     */
    private int selectMaxPriority() {
        int max = Integer.MIN_VALUE;
        int maxIndex = 0;
        for (int i = 1; i < size; i++) {
            int priority = array[i].priority();
            if (priority > max) {
                max = priority;
                maxIndex = i;
            }
        }
        return maxIndex;
    }
​
​
    /**
     * 返回优先级最高的一个数据
     *
     * @return
     */
    @Override
    public E peek() {
        if (isEmpty()) {
            return null;
        }
        //获取优先级最大的元素下标
        int max = selectMaxPriority();
        @SuppressWarnings("all")
        E value = (E) array[max];
        return value;
    }
​
    @Override
    public boolean isEmpty() {
        return size == 0;
    }
​
    @Override
    public boolean isFull() {
        return size == array.length;
    }
​
    @Override
    public int size() {
        return size;
    }
}

4、基于有序数组实现优先级队列

使用有序数组,优先级高的数据存放在数组的最后,每一次获取数据时直接那数组中最后一个数据即可。

这样在获取数据时比较简单,进而添加数据时就比较复杂了。需要将插入数据存放在合适的位置。

/**
 * <p>
 * 基于有序数组实现优先级队列
 * 使用有序数组,优先级高的数据存放在数组的最后,每一次获取数据时直接那数组中最后一个数据即可。
 * 这样在获取数据时比较简单,进而添加数据时就比较复杂了。需要将插入数据存放在合适的位置。
 *
 * @author zjj_admin
 */
public class ArrayPriorityQueue2<E extends Priority> implements Queue<E> {
​
​
    private final Priority[] array;
    private int size;
​
    public ArrayPriorityQueue2(int capacity) {
        size = 0;
        array = new Priority[capacity];
    }
​
    /**
     * 添加数据
     *
     * @param value 添加数据
     * @return
     */
    @Override
    public boolean offer(E value) {
        if (isFull()) {
            return false;
        }
        // 从后向前找,获取第一个优先级小于插入数据的下标索引
        int target = getTargetIndex(value.priority());
        if (target == 0) {
            // 当 target = 0 时,将所有的数据整体后移动一位
            System.arraycopy(array, 0, array, 1, size);
            array[0] = value;
        } else {
            //从索引 target + 1 开始,将后面的数据整体向后移动一位
            // 这里也可以采用插入排序算法进行修改
            System.arraycopy(array, target + 1, array, target + 2, size - target - 1);
            array[target + 1] = value;
        }
        size++;
        return true;
    }
​
    /**
     * 从后向前找,获取第一个优先级小于插入数据的下标索引
     *
     * @return
     */
    private int getTargetIndex(int priority) {
        for (int i = size - 1; i >= 0; i--) {
            if (array[i].priority() < priority) {
                return i;
            }
        }
        // 最后就返第一个即可
        return 0;
    }
​
​
    /**
     * 移除并返回优先级最高的一个数据
     *
     * @return
     */
    @Override
    public E poll() {
        if (isEmpty()) {
            return null;
        }
        //直接获取最后一个数据即可
        @SuppressWarnings("all")
        E value = (E) array[size - 1];
        //删除元素
        size--;
        return value;
    }
​
​
    /**
     * 返回优先级最高的一个数据
     *
     * @return
     */
    @Override
    public E peek() {
        if (isEmpty()) {
            return null;
        }
        //直接获取最后一个数据即可
        @SuppressWarnings("all")
        E value = (E) array[size - 1];
        return value;
    }
​
    @Override
    public boolean isEmpty() {
        return size == 0;
    }
​
    @Override
    public boolean isFull() {
        return size == array.length;
    }
​
    @Override
    public int size() {
        return size;
    }
}

5、基于堆实现优先级队列

计算机科学中,堆是一种基于树的数据结构,通常用完全二叉树实现。堆的特性如下

  • 在大顶堆中,任意节点 C 与它的父节点 P 符合 P.value >= C.value

  • 而小顶堆中,任意节点 C 与它的父节点 P 符合 P.value <= C.value

  • 最顶层的节点(没有父亲)称之为 root 根节点

优先级队列使用大根堆更加合适,即每一个节点的值不小于孩子节点。因为优先级队列每一个获取都是获取优先级最高的数据,所以使用大根堆更加方便。

/**
 * 堆一般看做一个完全二叉树
 * 优先级队列使用大根堆更加合适,即每一个节点的值不小于孩子节点。
 * 因为优先级队列每一个获取都是获取优先级最高的数据,所以使用大根堆更加方便。
 *
 * @author zjj_admin
 */
public class HeapPriorityQueue<E extends Priority> implements Queue<E> {
​
​
    private final E[] array;
    private int size;
​
    @SuppressWarnings("all")
    public HeapPriorityQueue(int capacity) {
        size = 0;
        array = (E[]) new Priority[capacity];
    }
​
    /**
     * 添加数据
     * 步骤:
     * 1、将新添加的元素存在完全二叉树的最后一个节点
     * 2、调整堆的结构让其满足大根堆的特点
     *
     * @param value 添加数据
     * @return
     */
    @Override
    public boolean offer(E value) {
        if (isFull()) {
            return false;
        }
        if (size == 0) {
            array[size] = value;
            size++;
            return true;
        }
        //获取添加节点的索引
        int addIndex = size;
        size++;
        //获取父节点的索引
        int parentIndex = (addIndex - 1) / 2;
        //对堆的结构进行调整,让其满足大根堆的特性。
        while (addIndex > 0 && array[parentIndex].priority() < value.priority()) {
            array[addIndex] = array[parentIndex];
            addIndex = parentIndex;
            parentIndex = (addIndex - 1) / 2;
        }
        array[addIndex] = value;
​
        return true;
    }
​
​
    /**
     * 移除并返回优先级最高的一个数据
     * 步骤:
     * 1、获取第一个数据,第一个数据及时需要移除的数据。
     * 2、将第一个数据和最后一个数据进行交换,让需要移除的数据放在数组的最后面。
     * 3、将堆的最后一个数据移除
     * 4、将根节点按照大根堆的特点调整位置,让其满足大根堆。
     *
     * @return
     */
    @SuppressWarnings("all")
    @Override
    public E poll() {
        if (isEmpty()) {
            return null;
        }
        //获取需要移除的位置
        E removed = array[0];
        //将数组的长度减少一个单位
        size--;
        //将最后一个放在第一个位置
        array[0] = array[size];
        //将最后一个数据设置为 null ,方便垃圾回收
        array[size] = null;
        //调整,是指满足大根堆的条件,做下潜操作
        down(0);
        return removed;
    }
​
    /**
     * 下潜操作,知道满足大根堆的条件
     *
     * @param index
     */
    private void down(int index) {
        if (2 * index + 1 < size && array[index].priority() < array[2 * index + 1].priority()) {
            // 交换位置
            exchange(index, 2 * index + 1);
            // 做下潜操作
            down(2 * index + 1);
        } else if (2 * index + 2 < size && array[index].priority() < array[2 * index + 2].priority()) {
            // 交换位置
            exchange(index, 2 * index + 2);
            // 做下潜操作
            down(2 * index + 2);
        }
    }
​
    /**
     * 交换索引 i 和索引 j
     *
     * @param i
     * @param j
     */
    private void exchange(int i, int j) {
        E temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
​
​
    /**
     * 返回优先级最高的一个数据
     *
     * @return
     */
    @SuppressWarnings("all")
    @Override
    public E peek() {
        if (isEmpty()) {
            return null;
        }
        return (E) array[0];
    }
​
    @Override
    public boolean isEmpty() {
        return size == 0;
    }
​
    @Override
    public boolean isFull() {
        return size == array.length;
    }
​
    @Override
    public int size() {
        return size;
    }
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值