数据结构-堆

 堆其实就是二叉树,只不过父亲节点永远比左右孩子大(或者小),成为最大堆(最小堆)。

根节点永远是最大(最小)。 注意:它不是二分搜索树(根节点比左孩子大,比右孩子小)。

虽然堆是一颗树,内部可以用一个数组来表示。 将每个节点按索引位置对应到数组中。在操作时,先根据二叉树的特性,算出索引,然后操作数组中的数据。

如下图:

 上图中:给任意一个节点的索引,可以算出其父亲节点,左孩子和右孩子的索引,去操作数组中对应索引位置的数据。

下面,实现一个最大堆:

public class MaxHeap<E extends Comparable<E>> {

    private Array<E> data;

    public MaxHeap(int capacity) {
        data = new Array<E>(capacity);
    }

    public MaxHeap() {
        data = new Array<E>();
    }

    // 返回堆中的元素个数
    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 2 * index + 1;
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
    private int rightChild(int index) {
        return 2 * index + 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(parent(k), k);
            k = parent(k);
        }
    }

    // 看堆中的最大元素
    public E findMax() {
        if (data.getSize() == 0) {
            throw new IllegalArgumentException("Can not findMax when heap is empty.");
        }
        return data.getFirst();
    }

    // 取出堆中的最大元素,并且替换成元素e
    public E replace(E e){
        E ret = findMax();
        data.set(0, e);
        siftDown(0);
        return ret;
    }

    // 取出堆中最大元素
    public E extractMax() {
        E e = findMax();
        data.swap(0, data.getSize() - 1);
        data.removeLast();
        siftDown(0);
        return e;
    }

    //元素下沉
    //找出左孩子与右孩子中值大的位置,再和k比较,看看是否需要交换
    private void siftDown(int k) {
        while (leftChild(k) < data.getSize()) {
            int j = leftChild(k);
            //如果有右孩子,并且右孩子的值大,需要和右孩子做交换
            if ((j + 1) < data.getSize() && data.get(j).compareTo(data.get(j + 1)) < 0) {
                j++;
            }
            if (data.get(k).compareTo(data.get(j)) >= 0) {
                break;
            }
            data.swap(k, j);
            k = j;
        }

    }

}

 有了堆的特性,现在实现一个优先队列(不是先进先出,新添加的元素不一定在队尾,而是根据优先级-重写compareTo方法,元素位置会发生变化)

public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {

    private MaxHeap<E> maxHeap;

    public PriorityQueue(){
        maxHeap = new MaxHeap<>();
    }

    @Override
    public int getSize(){
        return maxHeap.size();
    }

    @Override
    public boolean isEmpty(){
        return maxHeap.isEmpty();
    }

    @Override
    public E getFront(){
        return maxHeap.findMax();
    }

    @Override
    public void enqueue(E e){
        maxHeap.add(e);
    }

    @Override
    public E dequeue(){
        return maxHeap.extractMax();
    }
}

下面来做一道关于topK的题目:

/**
 * 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
 * <p>
 * 示例 1: 输入: nums = [1,1,1,2,2,3], k = 2  (输出数组中,出现频率前2名的元素) 输出: [1,2]
 * <p>
 * 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/top-k-frequent-elements 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
 */
public class Solution {
    //思路: 先把数组中元素和对应的次数存入map,再遍历map将元素和次数经过比较后,存入堆中。 只存k个元素,最后打印堆中元素。

    //定义一个类表示元素和出现次数,将Freq对象存入堆中
    private class Freq implements Comparable<Freq> {

        public int e, count;

        public Freq(int e, int count) {
            this.e = e;
            this.count = count;
        }

        @Override
        //重写此方法,调换大小,存入最大堆后,实则是一个最小堆。  后续和堆元素的最小值比较时,只需要比较堆顶元素。
        public int compareTo(Freq another) {
            if (this.count < another.count) {
                return 1;
            } else if (this.count > another.count) {
                return -1;
            } else {
                return 0;
            }
        }
    }


    public List<Integer> topKFrequent(int[] nums, int k) {
        TreeMap<Integer, Integer> map = new TreeMap<>();
        for (int num : nums) {
            if (map.containsKey(num)) {
                map.put(num, map.get(num) + 1);
            } else {
                map.put(num, 1);
            }
        }

        MaxHeap<Freq> maxHeap = new MaxHeap<>();
        for (int key : map.keySet()) {
            if (maxHeap.size() < k) {
                maxHeap.add(new Freq(key, map.get(key)));
            } else if (map.get(key) > maxHeap.findMax().count) {
                maxHeap.extractMax();
                maxHeap.add(new Freq(key, map.get(key)));
            }
        }

        LinkedList<Integer> res = new LinkedList<>();
        while (!maxHeap.isEmpty()) {
            res.add(maxHeap.extractMax().e);
        }
        return res;
    }

    private static void printList(List<Integer> nums) {
        for (Integer num : nums) {
            System.out.print(num + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[] nums = {1, 1, 1, 2, 2, 3};
        int k = 2;
        printList((new Solution()).topKFrequent(nums, k));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值