算法笔记-排序03:优先队列

作者: 不可思议的Mark | 来源:发表于2016-09-06 19:21 被阅读251次
为什么需要优先队列

我们并不一是一直都需要所有的元素全部有序。很多情况下我们会选择收集一些元素,然后处理其中键最大的元素,然后再收集更多的元素,再处理其中键值最大的元素。例如:你拥有一台可以同时运行多个程序的电脑,这是通过为每一个应用程序的事件分配一个优先级,并总是先处理优先级最高的事件。例如手机来电事件和你正在玩的手机游戏事件,绝大多数手机都会给手机来电事件分配一个更高的优先级,优先处理,所以你的游戏被打断,只能先处理来电。
这种情况下,需要支持以下两种操作的数据结构:
1.删除最大元素。
2.插入元素。
这种数据结构叫做优先队列。

API

优先队列是一种抽象的数据类型,它表示了一组值和对这些值的操作,它的抽象层是我们能够方便的将用例和实现隔离开来。

public class  MaxPQ<Key extends Comparable<Key>>
                   MaxPQ()                              创建一个优先队列
                   MaxPQ(int max)                       创建一个初始容量为max的优先队列
                   MaxPQ(Key[] a)                       用a[]中的元素创建一个优先队列
             void  Insert(Key v)                        向优先队列中插入一个元素
              Key  max()                                返回最大元素
          boolean  isEmpty()                            返回队列是否为空
              int  size()                               返回优先队列中元素的个数


这份API可以有很多种实现,比如有序数组,无序数组,链表,但它们在最坏的情况下插入元素和删除元素两个操作需要线性时间来完成,但是基于数据结构"堆"的实现能够保证这两种操作都能更快的执行:

来自《Algorithms Fourth Edition》一书的截图,侵删
堆的定义
堆有序:当一棵二叉树的每个节点都大于等于它的两个字节点时,它被称为堆有序。
来自《Algorithms Fourth Edition》一书的截图,侵删

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

来自《Algorithms Fourth Edition》一书的截图,侵删
二叉堆(简称堆):二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按层级存储。

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

基于堆的优先队列的代码实现
public class MaxPQ <Key extends Comparable<Key>>{
    private Key[] pq; //堆有序的完全二叉树
    private int N = 0; //数据存储在pq[1...N]中,pq[0]没有使用
    
    public MaxPQ(int maxN){
        pq = (Key[]) new Comparable[maxN+1];
    }
    
    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];  //从根节点得到最大的元素
        exchange(1,N--); //将其和最后一个节点交换
        pq[N+1] = null; //防止对象游离
        sink(1); //恢复堆的有序性
        return max;
    }
    
    private boolean less(int i, int j){
        return pq[i].compareTo(pq[j]) < 0;
    }
    
    private void exchange(int i, int j){
        Key t = pq[i];
        pq[i] = pq[j];
        pq[j] = t;
    }
    
    private void swim(int k){
        while (k>1 && less(k/2, k)){
            exchange(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;
            exchange(k,j);
            k = j;
        }
    }   
}

其中较为重要的是swim()和sink()方法,swim()方法在插入元素的时候调用,当新元素加入的时候,需要比较它与父节点的大小,如果新节点比父节点大,就要将其与父节点的元素交换,sink()方法在删除最大元素的时候调用,当删除了最大元素的时候,就数组最后一个元素放到第一个位置,然后将其与字节点比较,如果它比字节点小,就要将其下放,直到它被放到了合适的位置。
由代码实现我们很容易就可以得到对于一个含有N个元素的基于堆的优先队列,插入元素只需要不超过lgN+1次比较,而删除最大元素的操作需要不超过2lgN次比较。

堆排序

堆排序的思想:构造一个堆,然后依次从堆中拿出最大的元素,排序也就完成了。
实现代码:

public class Heap {
    
    private static Comparable[] a;
    
    public static void sort(Comparable[] array){
        
        a = new Comparable[array.length+1];
        
        for (int i = 0; i < array.length; i++) a[i+1] = array[i];
        
        int N = array.length;
        
        for (int k = N/2; k >= 1; k--){
            sink(k,N);
        }
        
        while (N>1){
            exchange(1, N--);
            sink(1,N);
        }
        
        for (int i = 0; i < array.length; i++) array[i] = a[i+1];
    }
    
    public static void main(String[] args){
        Integer[] a = {0,9,8,7,6,5,6,5,6,98,7,6,5,3};
        sort(a);
        for (int i = 0; i < a.length;i++){
            System.out.println(a[i]);
        }
    }
    
    private static boolean less(int i, int j){
        return a[i].compareTo(a[j]) < 0;
    }
    
    private static void exchange(int i, int j){
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    
    private static void swim(int k){
        while (k>1 && less(k/2, k)){
            exchange(k/2,k);
            k = k/2;
        }
    }
    
    private static void sink(int k, int N){
        while (2*k <= N){
            int j = 2*k;
            if (j<N && less(j,j+1)) j++;
            if (!less(k,j)) break;
            exchange(k,j);
            k = j;
        }
    }
}

在排序方法中,先创建一个比原数组多一个元素的数组,然后将原数组复制到新数组的1~N位上,开始构造一个二叉堆,构造完二叉堆后,每一次都将最大的元素(index为1)与后面的元素交换(1和N换,1和N-1换,1和N-2换 etc.)在换的过程中不断调用下沉方法保证堆有序,这样就能将数组从小到大排序,然后将元素复制回原数组,排序就完成了。

相关文章

  • 算法笔记-排序03:优先队列

    为什么需要优先队列 我们并不一是一直都需要所有的元素全部有序。很多情况下我们会选择收集一些元素,然后处理其中键最大...

  • MySql性能(9)- mysql的order by的工作原理

    全字段排序 rowid排序 全字段排序和rowid排序3.1 联合索引优化3.2 覆盖索引优化 优先队列算法 优化...

  • 排序算法

    什么是算法 书籍推荐 《数据结构与算法分析》 表、栈和队列 树 散列(hash) 优先队列(堆) 排序 定义 问题...

  • web开发需要知道的几个算法

    算法分类 快速排序算法 深度优先算法 广度优先算法 堆排序算法 归并排序算法

  • 堆排序学习总结

    本文摘抄总结于《算法》 我们可以把任意优先队列变成一种排序方法。而优先队列有多种实现方式,如无序数组实现的最小优先...

  • 算法读书笔记-排序算法-优先队列

    优先队列是一种抽象数据类型,它表示了一组值和对这些值的操作,它可以让我们每次从中取出权重最大的值. 优先队列的实现...

  • 《算法》-排序[优先队列(堆排序)]

    优先队列(堆排序) 优先队列:最重要的操作就是删除最大元素和插入元素 堆排序:堆排序对于记录较少的文件效果一般,对...

  • 堆排序

    优先队列可以用于花费 时间的排序,基于该想法的算法叫作堆排序(heapsort),该算法的运行时间很优秀,然后,在...

  • 一步一步学习数据结构和算法 (三) 堆和堆排序

    堆和堆排序 堆排序 堆和优先队列 普通队列: 先进先出; 后进后出. 优先队列: 出队顺序和入队顺序无关, 和优先...

  • 《算法》笔记 6 - 优先队列与堆排序

    优先队列初级实现二叉堆堆的有序化由下至上的堆有序化由上至下的堆有序化基于堆的优先队列 堆排序 优先队列 许多情况下...

网友评论

    本文标题:算法笔记-排序03:优先队列

    本文链接:https://www.haomeiwen.com/subject/ajslettx.html