数据结构与算法–优先队列

优先队列

概述

优先队列的产生

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

优先队列的划分

  1. 最大优先队列:每次获取并删除队列中最大的值
  2. 最小优先队列:每次获取并删除队列中最小的值

最大优先队列

实现思想

利用了大顶堆的思想,实现了每次删除队列中最大的值

API设计

image.png

API设计的思路以及优先队列的实现思想

  1. 将数据按照大顶堆的思想,依次插入,每次插入都让他进行上浮算法,使得新插入的元素在大顶堆中有序。
  2. 数据全部插入完成之后,就构成了大顶堆,这样的话,每次在堆中,最大的元素都是在第一个位置,每次都记录并删除它,删除它就和在大顶堆中删除元素一样,每次删除完都要调整大顶堆,使大顶堆有序。这样,每次删除的元素组合起来就构成了最大优先队列。
  3. less,exch,swim,sink都是为了1和2设计的
  4. 优先队列因为使用了大顶堆的思想,所以我们也采用了数组的形式来存储数据

具体方法的实现思想

  1. insert方法
  • 将数据直接插入到堆中
  • 使用上浮算法调整堆
    // 往堆中插入值
    public void insert(T t) {
        items[++N] = t;
        swim(N);//使用上浮算法,调整堆,使它有序
    }
复制代码
  1. delMax方法
  • 记录当前最大的数(堆的第一个数)
  • 交换最大的数和最后一个数的位置
  • 元素个数减一(既实现了元素个数减一,又实现了删除交换后的最后一个元素(即原先队列中最大的数))
  • 使用下沉算法,调整堆,使堆有序
    // 删除队列中最大的数,并返回这个数
    public T delMax() {
        T max = items[1];
        exchange(1,N);
        N--;
        sink(1);
        return max;
    }
复制代码
  1. 上浮算法和下沉算法,在创建堆的时候就已经说明过了,不懂的可以往看我上一篇博客

完整的API

package com.study.prority;

// 最大优先队列  --> 大顶堆的思想
public class MaxPriorityQueueAPI<T extends Comparable<T>> {
    private T[] items;//用数组来存储队列
    private int N;// 元素个数

    public MaxPriorityQueueAPI(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处的值的大小
    public boolean less(int i, int j) {
        return items[i].compareTo(items[j]) < 0;
    }

    // 交换索引i处和索引j处的值
    public void exchange(int i, int j) {
        T tmp = items[i];
        items[i] = items[j];
        items[j] = tmp;
    }

    // 往队列中插入值
    public void insert(T t) {
        items[++N] = t;
        swim(N);//使用上浮算法,调整队列,使它有序
    }

    // 删除队列中最大的数,并返回这个数
    public T delMax() {
        T max = items[1];
        exchange(1,N);
        N--;
        sink(1);
        return max;
    }

    // 上浮算法
    public void swim(int k) {
        while (k > 1) {
            if (less(k / 2, k)) {
                exchange(k / 2, k);
            }
            k = k / 2;
        }
    }

    // 下沉算法
    public void sink(int k) {
        int max;
        while (2 * k <= N) {
            // 找出子节点中较大的那个子节点
            if (2 * k + 1 <= N) {
                if (less(2 * k, 2 * k + 1)) {
                    max = 2 * k + 1;
                } else {
                    max = 2 * k;
                }
            } else {
                max = 2 * k;
            }
            // 当前结点大于子节点中最大的结点,说明k现在的位置是正确的
            if (!less(k,max)){
                break;
            }
            // 交换当前结点和子节点的位置
            exchange(k,max);
            // 变换当前结点k
            k = max;
        }
    }

}

复制代码

API测试

package com.study.prority;

public class MaxPriorityQueueTest {
    public static void main(String[] args) {
        MaxPriorityQueueAPI<String> mpq = new MaxPriorityQueueAPI<>(20);
        mpq.insert("G");
        mpq.insert("B");
        mpq.insert("A");
        mpq.insert("D");
        mpq.insert("F");
        mpq.insert("E");
        mpq.insert("C");
        System.out.println("优先队列的大小为:"+mpq.size());
        System.out.println("优先队列依次删除的最大值为:");
        while (!mpq.isEmpty()){
            String s = mpq.delMax();
            System.out.print(s+" ");
        }
    }
}
    
复制代码

结果

image.png

最小优先队列

和最大优先队列只有些许的差别:

  1. 它是小顶堆的思想实现的
  2. 每次删除最小的值

因为差别很小,他们的API实现就很相近,这里我就不再对最小优先队列做过多的阐述,直接上API

package com.study.prority;
// 最小优先队列   --小顶堆思想
public class MinPriorityQueueAPI<T extends Comparable<T>> {
    private T[] items;
    private int N;

    public MinPriorityQueueAPI(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处的值的大小
    public boolean less(int i, int j) {
        return items[i].compareTo(items[j]) < 0;
    }

    // 交换索引i处和索引j处的值
    public void exchange(int i, int j) {
        T tmp = items[i];
        items[i] = items[j];
        items[j] = tmp;
    }

    // 往队列中插入值
    public void insert(T t) {
        items[++N] = t;
        swim(N);//使用上浮算法,调整队列,使它有序
    }

    // 删除队列中最小的数,并返回这个数
    public T delMin() {
        T min = items[1];
        exchange(1,N);
        N--;
        sink(1);
        return min;
    }

    // 上浮算法
    public void swim(int k) {
        while (k > 1) {
            if (!less(k / 2, k)) {
                exchange(k / 2, k);
            }
            k = k / 2;
        }
    }

    // 下沉算法
    public void sink(int k) {
        int min;
        while (2 * k <= N) {
            // 找出子节点中较小的那个子节点
            if (2 * k + 1 <= N) {
                if (!less(2 * k, 2 * k + 1)) {
                    min = 2 * k + 1;
                } else {
                    min = 2 * k;
                }
            } else {
                min = 2 * k;
            }
            // 当前结点小于子节点中最小的结点,说明k现在的位置是正确的
            if (less(k,min)){
                break;
            }
            // 交换当前结点和子节点的位置
            exchange(k,min);
            // 变换当前结点k
            k = min;
        }
    }

}
复制代码

测试

package com.study.prority;

public class MinPriorityQueueTest {
    public static void main(String[] args) {
        MinPriorityQueueAPI<String> mpq = new MinPriorityQueueAPI<>(20);
        mpq.insert("G");
        mpq.insert("B");
        mpq.insert("A");//我是sb
        mpq.insert("D");
        mpq.insert("F");
        mpq.insert("E");
        mpq.insert("C");
        System.out.println("优先队列的大小为:"+mpq.size());
        System.out.println("优先队列依次删除的最小值为:");
        while (!mpq.isEmpty()){
            String s = mpq.delMin();
            System.out.print(s+" ");
        }
    }
}

复制代码

结果

image.png

索引优先队列

在之前实现的最大优先队列和最小优先队列,他们可以分别快速访问到队列中最大元素和最小元素,但是他们有一个缺点,就是没有办法通过索引访问已存在于优先队列中的对象,并更新它们。为了实现这个目的,在优先队列的基础上,学习一种新的数据结构,索引优先队列。接下来我们以最小索引优先队列举列。

实现思路

  1. 存储数据时,给每一个数据元素关联一个整数,例面insert(int k,T t),我们可以看做k是t关联的整数,那么我们的实现需要通过k这个值,快速获取到队列中t这个元素,此时有个k这个值需要具有唯一性。
  2. 元素顺序是随机的,并不是堆有序的,所以,为了完成这个需求,我们可以增加一个数组int[pq,来保存每个元素在items数组中的索引,pq数组需要堆有序,也就是说,pq[1]对应的数据元素items[pq[1]要小于等于pq[2]和pq[3]对应的数据元素items[pq[2]]和items[pq[3]]。
  3. 每次删除指定索引的元素,都需要对堆进行调整,这时候,调整的是pq数组,而不是items数组,那我们怎么快速获得要删除或者修改的元素的索引对应的元素在pq的位置呢?这时候我们就需要一个qp数组来存储pq的逆序了。什么是逆序呢,很简单,举个例子:pq[1]=6 =====> qp[6]=1; pq的索引作为qp的值。pq的值作为qp的索引
  4. 这样我们就可以通过这三个数组来实现索引优先队列

image.png

总结一下这三个数组的作用

  1. items:存放原始数据,不能修改
  2. pq:存放items排完序后的索引,pq的索引对应排序后的位置,pq的值对应items的索引,用来排序的
  3. qp:pq的逆序,快速找到pq[index]的位置

具体实现的API设计

image.png

代码实现

package com.study.prority;

// 索引优先队列
public class IndexPriorityQueueAPI<T extends Comparable<T>> {
    private T[] items;//用来存储元素的数组
    private int[] pq;//保存每个元素在items数组中的索引。pq数组需要堆有序
    private int[] qp;//保存pq的逆序,pq的值作为索引,pq的索引作为值 (也就是items索引不变,但是排序后的样子)
    private int N;//堆中元素的个数

    public IndexPriorityQueueAPI(int capacity) {
        this.items = (T[]) new Comparable[capacity + 1];
        this.pq = new int[capacity + 1];
        this.qp = new int[capacity + 1];
        this.N = 0;

        // 初始情况下默认队列中没有值,qp数组中每个元素设为-1,意为队列中没有值
        for (int i = 0; i < qp.length; i++) {
            qp[i] = -1;
        }
    }

    public int size() {
        return N;
    }

    public boolean isEmpty() {
        return N == 0;
    }

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

    // 判断索引i和索引j的元素的大小
    private boolean less(int i, int j) {
        return items[pq[i]].compareTo(items[pq[j]]) < 0;
    }

    // 交换索引i和索引j的元素的位置
    private void exchange(int i, int j) {
        // 修改pq数组
        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;//默认的qp数组的值都为-1,默认-1代表没有元素
    }

    // 插入元素,并关联该索引
    public void insert(int i, T t) {
        if (contains(i)) {//如果该索引已经被关联,则不让插入
            return;
        }
        N++;// 元素个数加一
        items[i] = t;//items存入新插入的值
        pq[N] = i;//pq记录新插入的值的索引
        qp[i] = N;//qp记录pq的i
        swim(N);
    }

    // 删除队列中最小的元素,并返回该元素关联的索引
    public int delMin() {
        // 获得最小索引
        int minIndex = pq[1];
        // 交换最小索引和最大索引的位置
        exchange(1, N);
        // 删除items中最小的元素
        items[minIndex] = null;
        // 删除qp中记录的索引
        qp[pq[N]]=-1;
        // 删除pq中记录的索引
        pq[N]=-1;
        // 元素个数减一
        N--;
        // 下沉调整
        sink(1);
        return minIndex;
    }
    // 删除索引i关联的元素
    public void delete(int i){
        // 找到索引i关联的元素
        int k = qp[i];
        // 交换索引i和最大索引的元素
        exchange(k,N);
        // 删除索引i关联的元素
        items[k]=null;
        // 删除qp记录的索引
        qp[pq[N]]=-1;
        // 删除pq记录的索引
        pq[N]=-1;
        // 元素个数减一
        N--;
        // 调整堆
        sink(k);
        swim(k);
    }
    // 把与索引i关联的元素修改为t
    public void changeItem(int i,T t){
        items[i]=t;// 修改items对应索引的值
        int k = qp[i];//找到索引在pq出现的位置
        // 调整堆
        sink(k);
        swim(k);
    }

    // 上浮算法
    private void swim(int k) {
        while (k > 1) {
            if (!less(k / 2, k)) {
                exchange(k / 2, k);
            }
            k = k / 2;
        }
    }

    //下沉算法
    private void sink(int k) {
        int min;
        while (2 * k <= N) {
            if (2 * k + 1 <= N) {
                if (less(2 * k, 2 * k + 1)) {
                    min = 2 * k;
                } else {
                    min = 2 * k + 1;
                }
            } else {
                min = 2 * k;
            }
            if (less(k, min)) {
                break;
            }
            exchange(k, min);
            k = min;
        }
    }

}

复制代码

测试

package com.study.prority;

public class IndexPriorityQueueTest {
    public static void main(String[] args) {
        IndexPriorityQueueAPI<String> ipq = new IndexPriorityQueueAPI<>(20);
        ipq.insert(1,"A");
        ipq.insert(2,"E");
        ipq.insert(3,"B");
        ipq.insert(4,"G");
        ipq.changeItem(2,"C");
        ipq.delete(1);//删除索引为1的元素
        while (!ipq.isEmpty()){
            int i = ipq.delMin();
            System.out.print(i+" ");
        }
    }
}

复制代码

结果

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享