xiaobenchi

导航

优先队列

优先队列

二叉堆

本节将二叉堆之称为堆,就像二叉查找树一样,堆也有两个性质,即结构性和堆序性。恰似AVL树,对堆的一次操作可能破坏这两个性质中的一个,因此,堆的一次操作必须到堆的所有性质都被满足才能终止。

结构性质

​ 堆是一棵完全被填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入,这样的树称为完全二叉树(complete binary tree)。

一棵完全二叉树

​ 图 1 一棵完全二叉树

​ 容易证明,一棵高为h的完全二叉树有2h到2(h+1)-1个节点。这意味着完全二叉树的高是O(logN)。因为完全二叉树的规律,所以它可以用一个数组表示而不需要使用链。

​ 对于数组中任一位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元(2i+1)中,它的父亲则在位置[i/2]上。因此这里不仅不需要链,而且遍历该树所需要的操作极简单,在大部分计算机上运行可能非常快。这种实现方法的唯一问题在于,最大的堆大小需要事先估计,但一般这并不成问题(而且如果需要,我们可以重新调整大小)。

优先队列的应用

选择问题

​ 假设只考虑找出第k个最小的元素。该算法很简单,我们将N个元素读入一个数组。然后对该数组应用buildHeap算法。最后,执行K次deleteMin操作。从该堆最后提取的元素就是我们的答案。显然,只要改变堆序性质,也可以找出第k个最大元素。

什么是优先队列

​ 普通的队列都是先进先出的形式,元素在队尾加入,在队头删除。优先队列中,元素被赋予优先级,具有最高优先级的元素先出。通常采用二叉堆结构实现。

​ 优先队列和堆一样,有两种形式:最大优先队列和最小优先队列。

​ 对于每次的push和pop操作,队列都会动态的调整

​ Java中的PriorityQueue类实现优先队列

常用方法

public boolean add(E e); //在队尾添加元素
public E peek(); //取出队头元素
public boolean remove(Object o); //删除指定元素
public boolean contains(Object o); //检查是否包含元素
public Object[] toArray(); //将队列转换为数组
public int size(); //返回元素个数
public void clear(); //清空队列
public E poll(); //返回并清空队头元素
public boolean isEmpty(); //判断队列是否为空

最小优先队列

public class test{
    
    @test
    public void test(){
        int[] nums = new int[]{12,3,8,5,16,30,9};
        Queue<Integer> queue = new PriorityQueue<>();
        for(int num:nums){
            queue.add(num);
        }
        System.out.println(Arrays.toString(queue.toArray()));
        while(!queue.isEmpty()){
            System.out.print(queue.poll() + " ");
        }
    }
}

最大优先队列

public class test{
    
    @Test
    public void test(){
        int[] nums = new int[] {12,3,8,5,16,30,9};
        Queue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>()){
            @override
            public int compare(Integer o1,Integer o2){
                return o2 - o1;
            }
        });
        for(int num:nums){
            queue.add(num);
        }
        System.out.println(Arrays.toString(queue.toArray()));
        while(!queue.isEmpty()){
            System.out.print(queue.poll() + " ");
        }
        }
    }
}

堆排序

​ 堆排序的基本思想,将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就成为最大值。然后将剩余的n-1个元素重新构造成一个堆,这样就会得到n个元素的次小值。如此反复执行,就能得到一个有序序列。

步骤:

  • 将无序序列构建成一个堆,根据升降需求选择大顶堆或者小顶堆;
  • 将堆元素与末尾元素交换,将最大元素沉到数组末端;
  • 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+步骤交换,直到整个序列有序。

代码实现:

import java.util.Arrays;

public class HeapSort{
    public static void main(String[] args){
        int[] arr = {7,6,7,11,5,12,3,0,1};
        System.out.println("排序前:" + Arrays.toString(arr));
        sort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }
    
    public static void sort(int[] arr){
        //1.构建大顶堆
        for(int i = arr.length/2 - 1; i >=0; i--){
            //从第一个非叶子节点从下至上,从右至左调整结构
            adjustHeap(arr,i,arr.length);
        }
        //2.调整堆结构+交换堆顶元素与末尾元素
        for(int j = arr.length-1; j > 0; j--){
            swap(arr,0,j); //交换堆顶元素与末尾元素
            adjustHeap(arr,o,j);//重新对堆进行调整
        }
    }
    /*
     调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
    */
    public static void adjustHeap(int[] arr, int i, int length){
        int temp = arr[i];//先取出当前元素i
        for(int k = i*2 +1; k < length; k = k*2+1){//从i节点的左子节点开始,也就是2i+1处开始
            if(k+1 < length && arr[k] < arr[k+1]){//如果左子节点小于右子节点,k指向右子节点
                k++;
            }
            if(arr[k] > temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                arr[i] = arr[k];
                i = k;
            }else{
                break;
            }    
        }
        arr[i] = temp;
    }
    
    //交换元素
    public static void swap(int[] arr,int a, int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}

posted on 2022-05-10 15:56  小迟在努力  阅读(47)  评论(0)    收藏  举报