优先队列-java语言版

177 阅读3分钟

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在某些情况下,我们可能需要找出 队列中的最大值或者最小值,例如使用一个队列保存计算机的任务,一般情况下计算机的任务都是有优先级的,我们需要在这些计算机的任务中找出优先级最高的任务先执行,执行完毕后就需要把这个任务从队列中移除。普通的 队列要完成这样的功能,需要每次遍历队列中的所有元素,比较并找出最大值,效率不是很高,这个时候,我们就可以使用一种特殊的队列来完成这种需求,优先队列。

优先队列001.jpg

优先队列按照其作用不同,可以分为以下两种:

最大优先队列: 可以获取并删除队列中最大的值

最小优先队列: 可以获取并删除队列中最小的值

-- 最大优先队列:

我们之前学习过堆,而堆这种结构是可以方便的删除最大的值,所以,接下来我们可以基于堆区实现最大优先队 列。

-- API设计:

最大优先队列API设计.jpg

-- 代码:

/**
 * 最大优先队列
 */
public class MaxPriorityQueue<T extends Comparable<T>> {
    // 存储堆中的元素
    private T[] items;
    // 记录堆中元素的个数
    private int N;

    // 构造方法
    public MaxPriorityQueue(int capacity) {
        this.items = (T[]) new Comparable[capacity+1];
        this.N = 0;
    }

    // 获取队列中元素的个数
    public int size() {
        return N;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return N == 0;
    }

    // 判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i,int j) {
        return items[i].compareTo(items[j]) < 0;
    }

    // 交换堆中索引i处和索引j处的值
    private void exch(int i,int j) {
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }

    // 往堆中插入一个元素
    public void insert(T t) {
        items[++N] = t;
        // 使用上浮算法
        swim(N);
    }

    // 删除堆中最大的元素,并返回这个最大的元素
    public T delMax() {
        // 保留要删除的元素,用于返回
        T max = items[1];
        // 交换索引1处和索引N处的值
        exch(1,N);
        // 删除堆中最后一个元素
        items[N] = null;
        N--;
        // 使用下次算法
        sink(1);
        return max;
    }

    // 使用上浮算法,使得索引k处的元素在堆中处于正确的位置
    private void swim(int k) {
        // 如果已经到了根结点,就不再需要循环了
        while (k > 1) {
            // 比较当前结点大于其父结点的值,则交换位置
            if(less(k/2,k)) {
                exch(k/2,k);
            }
            // 更换k的位置
            k = k / 2;
        }
    }

    // 使用下沉算法,使得索引k处的元素在堆中处于正确的位置
    private void sink(int k) {
        // 如果当前结点已经没有子结点,循环结束
        while (2 * k <= N) {
            // 找到子结点中,较大的那个值,先假定左子结点为较大的那个值
            int max = 2 * k;
            if(2 * k + 1 <= N) { // 如果存在右子结点
                if(less(2 * k,2 * k + 1)) {
                    max = 2 * k + 1;
                }
            }
            // 如果当前结点,比子节点中的较大值小,跳出循环
            if(!less(k,max)) {
                break;
            }
            // 交换当前索引和比较大子结点的位置
            exch(k,max);
            k = max;
        }
    }
}

-- 测试代码:

public class MaxPriorityQueueTest {
    public static void main(String[] args) throws Exception {
        String[] arr = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"};
        MaxPriorityQueue<String> maxpq = new MaxPriorityQueue<>(20);
        for (String s : arr) {
            maxpq.insert(s);
        }
        System.out.println(maxpq.size());
        String del;
        while(!maxpq.isEmpty()){
            del = maxpq.delMax();
            System.out.print(del+",");
        }
    }
}

-- 运行效果图:

最大优先队列运行效果图.jpg


-- 最小优先队列:

最小优先队列实现起来也比较简单,我们同样也可以基于堆来完成最小优先队列。 我们前面学习堆的时候,堆中存放数据元素的数组要满足都满足如下特性:

1.最大的元素放在数组的索引1处。

2.每个结点的数据总是大于等于它的两个子结点的数据。

最大堆.jpg

其实我们之前实现的堆可以把它叫做最大堆,我们可以用相反的思想实现最小堆,让堆中存放数据元素的数组满足 如下特性:

1.最小的元素放在数组的索引1处。

2.每个结点的数据总是小于等于它的两个子结点的数据。

最小堆.jpg

-- API设计:

最大优先队列API设计.jpg

-- 代码:

/**
 * 最小优先队列
 */
public class MinPriorityQueue<T extends Comparable<T>> {
    // 存储堆中的元素
    private T[] items;
    // 记录堆中元素的个数
    private int N;

    // 构造方法
    public MinPriorityQueue(int capacity) {
        items = (T[]) new Comparable[capacity+1];
        N = 0;
    }

    // 获取堆中元素个数
    public int size() {
        return N;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return N == 0;
    }

    // 判断堆中索引i处的元素是否小于索引j中的元素
    private boolean less(int i,int j) {
        return items[i].compareTo(items[j]) < 0;
    }

    // 交换堆中索引i和索引j处的值
    private void exch(int i,int j) {
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }

    // 往堆中插入元素
    public void insert(T t) {
        items[++N] = t;
        // 使用上浮算法
        swim(N);
    }

    // 删除堆中最小的元素,并返回
    public T delMin() {
        // 保留要删除的元素用于返回
        T min = items[1];
        // 交换索引1处和索引N的元素
        exch(1,N);
        // 删除索引N处的元素
        items[N] = null;
        N--;
        // 使用下沉算法
        sink(1);
        return min;
    }

    // 使用上浮算法,使得索引k处的元素在堆中处于正确的位置
    private void swim(int k) {
        // 如果已经到达了根结点,就不需要循环了
        while (k > 1) {
            // 如果当前结点,已经小于其父结点交换其位置
            if(less(k,k/ 2)) {
                exch(k,k/2);
            }
            // 变换k的位置
            k = k / 2;
        }
    }

    // 使用下沉算法,使得索引k处的元素在堆中处于正确的位置
    private void sink(int k) {
        // 如果当前结点没有子结点啦,循环就不需要执行了
        while (2 * k <= N) {
            // 找出子结点中较小的那个元素
            int min = 2 * k;
            if(2 * k + 1 <= N && less(2 * k + 1,2 * k)) {
                min = 2 * k + 1;
            }
            // 如果当前结点,已经不小于子结点中较小的那个值,则跳出循环
            if(less(k,min)) {
                break;
            }
            // 交换索引k和索引min
            exch(k,min);
            // 变换索引k的位置
            k = min;
        }
    }
}

-- 测试代码:

public class MinPriorityQueueTest {
    public static void main(String[] args) throws Exception {
        String[] arr = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};
        MinPriorityQueue<String> minpq = new MinPriorityQueue<>(20);
        for (String s : arr) {
            minpq.insert(s);
        }
        System.out.println(minpq.size());
        String del;
        while(!minpq.isEmpty()){
            del = minpq.delMin();
            System.out.print(del+",");
        }
    }
}

-- 运行效果图:

最小堆运行效果.jpg

--索引优先队列

索引优先队列.jpg

实现思想,用items作为内存保存值,其中索引是键用于关联里面的值。pq数组存放的是items中的索引,让堆有序,也就是说pq就是真实的堆,items只是保存值的内存空间。qp数组保存items索引(关联值的键)在堆中的位置,这样就可以快速定位items索引(关联值的键)在堆中的位置。 最终效果如下图:

索引最小优先队列.jpg

-- 代码:

public class IndexMinPriorityQueue <T extends Comparable<T>> {
    // 存储堆中的元素
    private T[] items;
    // 存储items中的元素(关联的键),堆有序
    private int[] pq;
    // 存储item索引(关联的键),在堆中的位置
    private int[] qp;
    // 堆中元素的个数
    private int N;

    // 构造方法
    public IndexMinPriorityQueue(int capacity) {
        items = (T[]) new Comparable[capacity+1];
        pq = new int[capacity+1];
        qp = new int[capacity+1];
        // 默认情况下堆中不存储任何值
        for(int i = 0;i < qp.length;i++) {
            qp[i] = -1;
        }
    }

    // 获取队列中元素的个数
    public int size() {
        return N;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return N == 0;
    }

    // 判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i,int j) {
        return items[pq[i]].compareTo(items[pq[j]]) < 0;
    }

    // 交换i索引和j索引处的值
    private void exch(int i,int j) {
        int tmp = pq[i];
        pq[i] = pq[j];
        pq[j] = tmp;

        // 跟新qp数组中的值
        qp[pq[i]] = i;
        qp[pq[j]] = j;
    }

    // 判断k对应的元素是否存在
    public boolean contains(int k) {
        return qp[k] != -1;
    }

    // 最小元素关联的索引
    public int minIndex() {
        return pq[1];
    }

    // 往队列中插入一个元素
    public void insert(int i,T t) {
        // 如果i索引已经关联过,则不让插入
        if(contains(i)) {
            throw new RuntimeException("该索引已经存在");
        }

        // 将元素放到内存中
        items[i] = t;
        // 将关联索引放到堆中
        pq[++N] = i;
        // 记录关联索引在堆中的位置
        qp[i] = N;
        // 使用上浮算法
        swim(N);
    }

    // 删除堆中最小的元素,并返回该元素关联的索引
    public int delMin() {
        // 找到items中最小元素的索引
        int minIndex = pq[1];
        // 交换索引1和索引N处的值
        exch(1,N);
        // 删除qp中pq[N]处的值
        qp[pq[N]] = -1;
        // 删除pq索引N处的值
        pq[N] = -1;
        // 删除items的最小元素
        items[minIndex] = null;
        // 个数-1
        N--;
        // 使用下沉算法,让堆有序
        sink(1);
        return minIndex;
    }

    // 删除索引i关联的元素
    public void delete(int i) {
        // 找出i在pq中的索引
        int k = qp[i];
        // 交换索引k和索引N位置处的值
        exch(k,N);
        // 删除qp索引pq[N]处的值
        qp[pq[N]] = -1;
        // 删除pq索引N处的值
        pq[N] = -1;
        // 删除items中索引i处的值
        items[i] = null;
        // 对pq做下沉算法
        sink(k);
        // 对pq做上浮算法
        swim(k);
    }

    // 把索引i处关联的元素修改为t
    public void changeItem(int i,T t) {
        items[i] = t;
        // 找到索引i在pq中的位置
        int k = qp[i];
        // 对pq做下沉算法
        sink(k);
        // 对pq做上浮算法
        swim(k);
    }

    // 使用上浮算法,使得索引k处的元素在堆中处于一个正确的位置
    private void swim(int k) {
        // 如果到达根结点,则结束上浮
        while (k > 1) {
            // 比较当前结点和父结点,如果当前结点比父结点小,则交换位置
            if(less(k,k/2)) {
                exch(k,k / 2);
            }
            k = k / 2;
        }
    }

    // 使用下沉算法,使得索引k处的元素在堆中处于一个正确的位置
    private void sink(int k) {
        // 如果当前结点已经没有子结点,结束下沉
        while (2 * k <= N) {
            // 找到较小的子结点
            int min = 2 * k;
            if(2 * k + 1 <= N && less(2 * k + 1,2 * k)) {
                min = 2 * k + 1;
            }

            // 如果当前结点比较小的子结点小,结束下沉
            if(less(k,min)) {
                break;
            }
            exch(k,min);
            k = min;
        }
    }
}

-- 测试代码:

public class IndexMinPriorityQueueTest {
    public static void main(String[] args) {
        String[] arr = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};
        IndexMinPriorityQueue<String> indexMinPQ = new IndexMinPriorityQueue<>(20);
        //插入
        for (int i = 0; i < arr.length; i++) {
            indexMinPQ.insert(i,arr[i]);
        }
        System.out.println(indexMinPQ.size());
        //获取最小值的索引
        System.out.println(indexMinPQ.minIndex());
        //测试修改
        indexMinPQ.changeItem(0,"Z");
        int minIndex=-1;
        while(!indexMinPQ.isEmpty()){
            minIndex = indexMinPQ.delMin();
            System.out.print(minIndex+",");
        }
    }
}

-- 运行效果:

运行效果.jpg

@ 以上内容属于个人笔记