【alg4-排序】优先队列

许多应用程序都需要处理有序的元素,但不一定要求它们全部有序,或是不一定要一次就将它们排序。很多情况下我们会收集一些元素,处理当前键值最大的元素,然后再收集更多的元素,再处理当前键值最大的元素,如此这般。

在这种情况下,一个合适的数据结构应该支持两种操作:删除最大元素插入元素。这种数据类型叫做优先队列

优先队列的一些重要的应用场景包括模拟系统,其中事件的键即为发生的时间,而系统需要按照时间顺序处理所有事件;任务调度,其中键值对应的优先级决定了应该首先执行哪些任务;数值计算,键值代表计算错误,而我们需要按照键值指定的顺序来修正它们。

通过插入一列元素然后一个个地删掉其中最小的元素,我们可以用优先队列实现排序算法。

泛型优先队列的API

public class MaxPQ<Key extends Comparable<Key>>

API功能
MaxPQ()创建一个优先队列
MaxPQ(int max)创建一个初始容量为max的优先队列
MaxPQ(Key[] a)用a[]中的元素创建一个优先队列
void insert(Key v)向优先队列中插入一个元素
Key max()返回最大元素
Key delMax()删除并返回最大元素
boolean isEmpty()返回队列是否为空
int size()返回优先队列中的元素数量

为了使用用例代码更加清晰,我们会在适当的地方使用另一个类MinPQ。它和MaxPQ类似,只是含有一个delMin()方法来删除并返回队列中键值最小的那个元素。只需改变一下less()比较的方法即可。

优先队列的调用实例

为了展示优先队列的抽象模型的价值,考虑以下问题:输入N个字符串,每个字符串都对应着一个整数,你的任务就是从中找出最大的(或是最小的)M个整数(及其关联的字符串)。

解决这个问题的一种方法是将输入排序然后从中找出M个最大的元素,但我们已经说明输入将会非常庞大。
另一种方法是将每个新的输入和已知的M个最大元素比较,但除非M较小,否则这种比较的代价会非常高昂。

从N个输入中找到最大的M个元素所需成本:

示例时间空间
排序算法的用例NlogNN
调用初级实现的优先队列NMM
调用基于堆实现的优先队列NlogMM
初级实现
数组实现(无序)

要实现删除最大元素,我们可以添加一段类似于选择排序的内循环的代码,将最大元素和边界元素交换然后删除它。

Transaction类:

package section2_1.priorityqueue;

public class Transaction implements Comparable<Transaction> {

    private String who;
    private double amount;

    public Transaction(String transaction) {
        String[] s = transaction.split(" ");
        who = s[0];
        amount = Double.parseDouble(s[1]);
    }

    @Override
    public int compareTo(Transaction o) {
        if (this.amount < o.amount) return -1;
        else if (this.amount == o.amount) return 0;
        else return 1;
    }

    @Override
    public String toString() {
        return "Transaction{" +
                "who='" + who + '\'' +
                ", amount=" + amount +
                '}';
    }
}

MinPQ类:

package section2_1.priorityqueue;

public class MinPQ<key extends Comparable<key>> {

    private key[] array;
    private int idx;

    public MinPQ(int max) {
        array = (key[]) new Comparable[max];
        idx = 0;
    }

    public void insert(key v) {
        array[idx++] = v;
    }

    public key delMin() {
        int min = 0;
        for (int i=1;i < idx;i++) {
            if (array[i].compareTo(array[min]) < 0) min = i;
        }
        idx--;
        key temp = array[min];
        array[min] = array[idx];
        array[idx] = temp;
        return temp;
    }

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

    int size() {
        return idx;
    }
}

TopM类:

package section2_1.priorityqueue;

public class TopM {

    public static void main(String[] args) {
        int M = 5;
        Transaction[] transactions = new Transaction[]{
                new Transaction("Turing 644.08"),
                new Transaction("vonNeumann 4121.85"),
                new Transaction("Dijkstra 2678.40"),
                new Transaction("vonNeumann 4409.74"),
                new Transaction("Dijkstra 837.42"),
                new Transaction("Hoare 3229.27"),
                new Transaction("vonNeumann 4732.35"),
                new Transaction("Hoare 4381.21"),
                new Transaction("Turing 66.10"),
                new Transaction("Thompson 4747.08"),
                new Transaction("Turing 2156.86"),
                new Transaction("Hoare 1025.70"),
                new Transaction("vonNeumann 2520.97"),
                new Transaction("Dijkstra 708.95"),
                new Transaction("Turing 3532.36"),
                new Transaction("Hoare 4050.20"),
        };
        MinPQ<Transaction> pq = new MinPQ<>(M+1);
        int idx = 0;
        while (idx < transactions.length) {
            pq.insert(transactions[idx]);
            if (pq.size() > M) {
                pq.delMin();
            }
            idx++;
        }
        while (!pq.isEmpty()) {
            System.out.println(pq.delMin());
        }
    }

}

另外,还有数组实现(有序)链表表示法

使用无序序列是解决这个问题的惰性方法,我们仅在必要的时候才会采取行动(找出最大的元素);使用有序序列则是解决问题的积极方法,因为我们会尽可能未雨绸缪(在插入元素时就保持列表有序),使后续操作更高效。

优先队列的各种实现在最坏情况下运行时间的增长数量级:

数据结构插入元素删除最大元素
有序数组N1
无序数组1N
logNlogN
理想情况11
堆的定义

数据结构二叉堆能够很好地实现优先队列的基本操作。在二叉堆的数组中,每个元素都要保证大于等于另两个特定位置的元素。

当一棵二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序

相应地,在堆有序的二叉树中,每个结点都小于等于它的父结点(如果有的话)。从任意结点向上,我们都能得到一列非递减的元素;从任意结点向下,我们都能得到一列非递增的元素。

根结点是堆有序的二叉树中的最大结点。

二叉堆表示法

完全二叉树只用数组而不需要指针就可以表示。具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6,7,以此类推。

二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级存储(不使用数组的第一个位置)。

在一个堆中,位置k的结点的父结点的位置为k/2,而它的两个子结点的位置则分别为2k和2k+1。这样在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:a[k]向上一层就令k等于k/2,向下一层则令k等于2k或2k+1。

用数组(堆)实现的完全二叉树的结构是很严格的,但它的灵活性已经足以让我们高效地实现优先队列。用它们我们将能实现对数级别的插入元素和删除最大元素的操作。

一棵大小为N的完全二叉树的高度为lgN。

堆的算法

堆的操作会首先进行一些简单的改动,打破堆的状态,然后再遍历堆并按照要求将堆的状态恢复。我们称这个过程叫做堆的有序化。

在有序化的过程中我们会遇到两种情况。当某个结点的优先级上升(或是在堆底加入一个新的元素)时,我们需要由下至上恢复堆的顺序。当某个结点的优先级下降(例如,将根节点替换为一个较小的元素)时,我们需要由上至下恢复堆的顺序。

由下至上的堆有序化(上浮)

如果堆的有序状态因为某个结点变得比它的父结点更大而被打破,那么我们就需要通过交换它和它的父结点来修复堆。将这个结点不断向上移动直到我们遇到了一个更大的父结点。
在这里插入图片描述

由上至下的堆有序化(下沉)

如果堆的有序状态因为某个结点变得比它的两个子结点或是其中之一更小了而被打破了,那么我们可以通过将它和它的两个子结点中的较大者交换来恢复堆。交换可能会在子结点处继续打破堆的有序状态,因此我们需要不断地用相同的方式将其修复,将结点向下移动直到它的子结点都比它更小或是到达了堆的底部。
在这里插入图片描述

插入元素。我们将新元素加到数组末尾,增加堆的大小并让这个新元素上浮到合适的位置。
删除最大元素。我们从数组顶端删去最大的元素并将数组的最后一个元素放到顶端,减小堆的大小并让这个元素下沉到合适的位置。

在这里插入图片描述

基于堆的优先队列
package section2_1.priorityqueue;

public class MaxPQ<Key extends Comparable<Key>> {

    private Key[] pq;
    private int N = 0;

    public MaxPQ(int max) {
        pq = (Key[]) new Comparable[max+1];
    }

    private boolean less(int i, int j) {
        return pq[i].compareTo(pq[j]) < 0;
    }

    private void exch(int i, int j) {
        Key temp = pq[i];
        pq[i] = pq[j];
        pq[j] = temp;
    }

    private void swim(int k) {
        while (k > 1 && less(k/2,k)) {
            exch(k/2,k);
            k = k / 2;
        }
    }

    private void sink(int k) {
        while (2*k <= N) {
            int j = 2 * k;
            if (j < N && less(j,j+1)) j++;
            if (!less(k,j)) break;
            exch(k,j);
            k = j;
        }
    }

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

    public int size() {
        return N;
    }

    public void insert(Key v) {
        pq[++N] = v;
        swim(N);
    }

    public Key delMax() {
        Key max = pq[1];
        exch(1,N--);
        pq[N+1] = null;
        sink(1);
        return max;
    }

    public void show() {
        for (int i=1;i<=size();i++) {
            System.out.print(pq[i]+" ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        MaxPQ<String> mpq = new MaxPQ<>(10);
        mpq.insert("P");
        mpq.show();
        mpq.insert("Q");
        mpq.show();
        mpq.insert("E");
        mpq.show();
        mpq.delMax();
        mpq.show();
        mpq.insert("X");
        mpq.show();
        mpq.insert("A");
        mpq.show();
        mpq.insert("M");
        mpq.show();
        mpq.delMax();
        mpq.show();
        mpq.insert("P");
        mpq.show();
        mpq.insert("L");
        mpq.show();
        mpq.insert("E");
        mpq.show();
        mpq.delMax();
        mpq.show();
    }

}

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
stk-alg-server 是指"算法服务端"。在计算机科学中,算法是一种解决问题的方法和步骤,而算法服务端则是提供算法计算能力的服务器。 stk-alg-server 提供了各种算法,可以根据客户的需求来执行不同的算法操作。它可以处理各种类型的数据,如数值、文本、图像等,以及不同领域的问题,如机器学习、数据挖掘、图像识别等。 算法服务端的工作流程一般包括以下几个步骤: 1. 接收请求:算法服务端通过网络接口接收客户端发送的请求。请求可以包括要处理的数据和所需的算法类型。 2. 数据处理:对接收到的数据进行预处理,例如数据清洗、标准化等操作,以确保数据的准确性和一致性。 3. 算法计算:根据客户端请求的算法类型,使用相应的算法对数据进行计算和处理。常见的算法排序算法、搜索算法、聚类算法等。 4. 结果返回:将算法计算得到的结果返回给客户端。结果可以是计算得到的数值、分析报告、图像等形式。 5. 错误处理:在处理请求的过程中,可能会遇到错误或异常情况。算法服务端需要捕获并处理这些错误,以保证系统的稳定性和可靠性。 stk-alg-server 的应用非常广泛。它可以用于科学研究、工程设计、金融分析等各种领域。客户可以通过调用算法服务端的接口,利用其强大的计算能力和丰富的算法库,来解决复杂的问题和提升工作效率。同时,算法服务端还可以进行算法的优化和更新,以满足不断变化的客户需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值