数据结构之优先队列(堆)-java

数据结构之优先队列(堆)-java

什么是优先队列?

队列是先进先出的数据结构,优先队列的出队可以出最小值或者最大值。

简单提一下,表list、队列、栈都可以由数组实现。当长度变长超出数组长时,新建一个两倍长的数组。

优先的队列的基本情况

1.优先队列的主要操作 优先队列是元素的容器,每个元素有一个相关的键值;

  • insert(key, data):插入键值为key的数据到优先队列中,元素以其key进行排序;
  • deleteMin/deleteMax:删除并返回最小/最大键值的元素;
  • getMinimum/getMaximum:返回最小/最大剑指的元素,但不删除它;

2.优先队列的辅助操作

  • 第k最小/第k最大:返回优先队列中键值为第k个最小/最大的元素;
  • 大小(size):返回优先队列中的元素个数;
  • 堆排序(Heap Sort):基于键值的优先级将优先队列中的元素进行排序;

3.优先队列的辅助操作

  • 数据压缩:赫夫曼编码算法;
  • 最短路径算法:Dijkstra算法;
  • 最小生成树算法:Prim算法;
  • 事件驱动仿真:顾客排队算法;
  • 选择问题:查找第k个最小元素;

优先的队列的实现–堆

优先队列可以由无序数组、无序链表、树等实现,使用堆是最优解。

什么是堆

堆是一颗具有特定性质的二叉树,堆的基本要求就是堆中所有结点的值必须大于或等于(或小于或等于)其孩子结点的值,这也称为堆的性质;堆还有另一个性质,就是当 h > 0 时,所有叶子结点都处于第 h 或 h - 1 层(其中 h 为树的高度,完全二叉树),也就是说,堆应该是一颗完全二叉树;

堆的实现

数组

用数组来表示堆不仅不会浪费空间还具有一定的优势:

  • 每个结点的左孩子为下标i的2倍:left child(i) = i * 2;每个结点的右孩子为下标i的2倍加1:right child(i) = i * 2 + 1
  • 每个结点的父亲结点为下标的二分之一:parent(i) = i / 2,注意这里是整数除,2和3除以2都为1;
public class MaxHeap<E extends Comparable<E>> {
    private Array<E> data;
    public MaxHeap(int capacity){ data = new Array<>(capacity); }
    public MaxHeap(){ data = new Array<>(); }
    // 返回堆中的元素个数    
    public int size(){ return data.getSize(); }
    // 返回一个布尔值, 表示堆中是否为空    
    public boolean isEmpty(){ return data.isEmpty(); }
    // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引    
    private int parent(int index){
        if(index == 0)
            throw new IllegalArgumentException("index-0 doesn't have parent.");
        return (index - 1) / 2;
    }
    // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引    
private int leftChild(int index){ return index * 2 + 1; }
    // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引    
private int rightChild(int index){ return index * 2 + 2; }
}
// 向堆中添加元素
public void add(E e){
    data.addLast(e);
    siftUp(data.getSize() - 1);
}
private void siftUp(int k){

    while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0 ){
        data.swap(k, parent(k));
        k = parent(k);
    }
}

优先队列的声明

/ 默认实现了一个最小堆。
Queue<Integer> priorityQueue = new PriorityQueue<>(); 

// 实现最大堆
Queue<Integer> priorityQueue = new PriorityQueue<>((a, b) -> b - a);

在PriorityQueue提供的构造方法中,可以使用自定义的排序方法,也可以使用元素自带的Comparable排序,PriorityQueue要求在默认排序的时候,需要元素对象拥有Comparable功能。

private static Comparator<ListNode>  comp =new Comparator<ListNode>() {	
	@Override
	public int compare(ListNode o1, ListNode o2) {
		// TODO Auto-generated method stub
		return o1.val-o2.val;
	}
};

public static ListNode mergeKLists(List<ListNode> lists){
	PriorityQueue<ListNode> queue=new PriorityQueue<ListNode>(lists.size(),comp);
	}

或者

PriorityQueue<ListNode> pq = new PriorityQueue<>(new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val-o2.val;
            }  
        });

刷题

合并K个排序链表

合并k个排序链表,并且返回合并后的排序链表。

思路,把所有链表放到一个优先队列,然后将优先队列序列化成链表。

代码

public ListNode mergeKLists(ListNode[] lists) {
    if (lists == null || lists.length == 0) return null;

    PriorityQueue<ListNode> q = new PriorityQueue<ListNode>(new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val-o2.val;
            }  
        });
    for (int i = 0; i < lists.length; i++) {
        if (lists[i] != null) {
            q.add(lists[i]);
        }
    }

    ListNode dummy = new ListNode(0);
    ListNode tail = dummy;

    while (!q.isEmpty()) {
        tail.next = q.poll();
        tail = tail.next;
        if (tail.next != null) {
            q.add(tail.next);
        }
    }

    return dummy.next;
}

数组中的第K个最大元素

public int findKthLargest(int[] nums, int k) {

    // 正确性判断    
    if (0 == nums.length || null == nums || k <= 0 || k > nums.length) {
        return -1;
    }

    // 构造优先队列,默认为最小堆,传入自定义的比较器转换成最大堆 
    PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);
    for (Integer num : nums) {
        pq.add(num);
    }
    for (int i = 0; i < k - 1; i++) {
        pq.remove();
    }
    return pq.peek();
}

数据流中位数

数字是不断进入数组的,在每次添加一个新的数进入数组的同时返回当前新数组的中位数。

中位数的定义:

  • 这里的中位数不等同于数学定义里的中位数
  • 中位数是排序后数组的中间值,如果有数组中有n个数,则中位数为A[(n-1)/2]。
  • 比如:数组A=[1,2,3]的中位数是2,数组A=[1,19]的中位数是1。

挑战

时间复杂度为O(nlogn)

方法2:一个最小堆,一个最大堆
思路:
//求数据流的中位数,给定的数组是代表数据流,每增加一个数,动态计算当前数据集合中的中位数
1.初始化两个堆,最大堆和最小堆。最大堆放数据中较小的那部分数据,最小堆放数据中较大的那部分数据,
2.每次增加一个元素时,都先加载到最大堆,然后判断当前集合中元素的个数,
2.1如果是奇数,比较最大堆堆顶元素和最小堆堆顶元素大小
2.1.1 如果最大堆堆顶元素 > 最小堆堆顶元素, 说明最小部分中有大于最大部分的元素存在,需要交换两个堆的堆顶元素
2.1.2 如果最大堆堆顶元素 <= 最小堆堆顶元素,说明正常,无需其他操作
2.2如果是偶数,从最大堆中分一个最大值给最小堆,保持两个堆的个数相差<=1
3.count++

public class Solution {
/**
\* @param nums: A list of integers
\* @return: the median of numbers
*/
private PriorityQueue<Integer> maxheap,minheap;
private int count=0;
public int[] medianII(int[] nums) {
    maxheap=new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2.compareTo(o1);
        }
    });
    minheap=new PriorityQueue<>();
    int len=nums.length;
    int []result=new int[len];
    for(int i=0;i<len;i++){
        addNums(nums[i]);
        result[i]=getMedian();
    }
    return result;
}

public void addNums(int x){
    maxheap.offer(x);
    //数组中有奇数个元素时
    if(count%2==0){
        if(minheap.isEmpty()){
            count++;
            return;
        }else{
            if(maxheap.peek()>minheap.peek()){
                int maxNum=maxheap.poll();
                int minNum=minheap.poll();
                maxheap.offer(minNum);
                minheap.offer(maxNum);
            }
        }
    }else{
        minheap.offer(maxheap.poll());
    }
    count++;
}
public int getMedian(){
    return maxheap.peek();
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值