文章目录
堆简述
二叉堆
最大堆
中某个节点的值总是不大于其父节点的值
n叉堆
索引堆
索引堆描述
存在一个索引数组index[ ],在数据进行交换时,是索引数组index里发生交换,data数组无变化。
rev
:(reverse)反向查找,表示rev[i]
这个索引在数组中的位置
- 如数组-4-的rev[4]=9,表示index[9]中存的是4。
- 在
更新
操作change()
方法中时间复杂度为O(1)
二叉堆
函数 | 功能 or 使用场景 | 过程 |
---|---|---|
SiftUp | 向堆中添加元素 | 在叶子节点添加元素,然后向上进行调整 ,直至到达合适位置。add 过程时间复杂度O(logn) |
SiftDown | 取出堆中的最大元素 | 取出大根堆的根节点,然后索引的最后一个叶子节点移动到根节点,接下来向下调整 根节点,直至到达合适位置。extractMax 时间复杂度O(logn) 。 |
Heapify | 将任意数组整理成堆的形状 | 从索引最后一个叶子节点的父亲节点开始,依次向上层调整,直至根节点调整完毕,每个结点到达合适位置。 将n个元素逐个插入到空堆中,算法复杂度是O(logn)。 heapify 的过程,复杂度O(n) 。 |
replace | 取出一个最大元素后,在放入一个新元素 | 方式一:先extractMax ,在add ,两次O(logn)操作。方式二:直接将堆顶元素替换以后 SiftDown ,一次O(logn)操作。 |
基于数组的MaxHeap自实现
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;
public MaxHeap(int capacity) {
data = new Array<>(capacity);
}
public MaxHeap() {
data = new Array<>();
}
//heapify过程创建堆,类似一个随机存储元素的数组,调整为一个大根堆的过程
//从最后一个元素的父节点开始,依次向前进行下调,直至到根节点siftDown为止
public MaxHeap(E[] arr) {
data = new Array<>(arr);
for (int i = parent(arr.length-1); i >= 0; i--) {
siftDown(i);
}
}
//返回堆中的元素个数
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);
}
}
//看堆中的最大元素
public E findMax() {
if(data.getSize() == 0)
throw new IllegalArgumentException("Can not findMax when heap is empty.");
return data.get(0);
}
//取出堆中最大元素
public E extractMax() {
E ret = findMax();
data.swap(0, data.getSize() -1);
data.removeLast();
siftDown(0);
return ret;
}
private void siftDown(int k) {
while (leftChild(k) < data.getSize()) {
// 在此轮循环中,data[k]和data[j]交换位置
int j = leftChild(k); //左孩子索引
//存在右孩子,且值大于左孩子
if(j + 1 < data.getSize() && data.get(j+1).compareTo(data.get(j)) > 0)
// j = rightChild(k);
j ++; //加一后表示右孩子索引
//data[j] 是lefeChild 和 rightChild中的最大值
if (data.get(k).compareTo(data.get(j)) >= 0)
break;
data.swap(k, j);
k = j;
}
}
//取出堆中的最大元素,并且替换成元素e
public E replace(E e) {
E ret = findMax();
data.set(0,e);
siftDown(0);
return ret;
}
}
索引堆自实现
public class IndexMaxHeap <E extends Comparable>{
protected E[] data; //最大索引堆中的数据
protected int[] indexes; //最大索引堆中的索引
protected int[] reverse; //最大索引堆中的反向索引,reverse[i] = x 表示索引i在x的位置
protected int count; //count类似原表格中第一行0,1,...,n
protected int capacity;
//构造函数,构造一个空堆,可 容纳capacity个元素
public IndexMaxHeap(int capacity) {
data = (E[]) new Comparable[capacity+1];
indexes = new int[capacity+1];
reverse = new int[capacity+1];
count = 0;
this.capacity = capacity;
}
//返回索引堆中的元素个数
public int size() {
return count;
}
//返回一个bool值,表示索引堆中是否为空
public boolean isEmpty() {
return count == 0;
}
//向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
//传入的i对用户而言,是从0索引的
public void insert(int i, E e) {
assert count+1 < capacity; //断言,判断为True则继续进行,为False则报错
assert i+1 >= 1 && i+1 <= capacity;
//在插入一个元素前,还需要保证索引i所在的位置是没有元素的
assert !contain(i);
i += 1;
data[i] = e;
indexes[count+1] = i;
reverse[i] = count+1;
count ++;
siftUp(count);
}
//从最大索引堆中取出堆顶元素,即索引堆中所存储的最大数据
public E extractMax() {
assert count > 0;
E ret = data[indexes[1]];
swapIndexes(1, count);
reverse[indexes[count]] = 0;
count --;
siftDown(1);
return ret;
}
//从最大索引堆中取出栈顶元素的索引
public int extractMaxIndex() {
assert count > 0;
int ret = indexes[1] - 1;
swapIndexes(1, count);
reverse[indexes[count]] = 0;
count --;
siftDown(1);
return ret;
}
//获取最大索引堆的堆顶元素
public E get() {
assert count > 0;
return data[indexes[1]];
}
//获取最大索引堆中堆顶元素的索引
public int getMaxIndex() {
assert count > 0;
return indexes[1] - 1;
}
//查看索引i的位置是否存在元素
public boolean contain(int i) {
assert i+1 >= 1 && i+1 <= capacity;
return reverse[i+1] == 0;
}
//获取最大索引堆中索引为i的元素
public E getE(int i) {
assert contain(i);
return data[i+1];
}
//将最大索引堆中索引为i的元素修改为newValue
public void change(int i, E newValue) {
assert contain(i);
i += 1;
data[i] = newValue;
//找到index[j] = i,j表示data[i]在堆中的位置
//之后siftUp(j),再siftDown(j)
// for (int j = 1; j < count; j++)
// if (indexes[j] == i) {
// siftUp(j);
// siftDown(j);
// return;
// }
// 有了 reverse 之后,
// 可以非常简单的通过reverse直接定位索引i在indexes中的位置
siftUp( reverse[i] );
siftDown( reverse[i] );
}
//交换索引堆中的索引i和j
// 由于有了反向索引reverse数组,
// indexes数组发生改变以后, 相应的就需要维护reverse数组
private void swapIndexes(int i, int j) {
int t = indexes[i];
indexes[i] = indexes[j];
indexes[j] = t;
reverse[indexes[i]] = i;
reverse[indexes[j]] = j;
}
//在索引堆中,数据之间的比较根据data的大小进行比较,但实际操作的是索引
private void siftUp(int k) {
while(k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) < 0) {
swapIndexes(k, k/2);
k = k/2;
}
}
//在索引堆中,数据之间的比较根据data的大小进行比较,但实际操作的是索引
private void siftDown(int k) {
while (2*k <= count ) {
int j = 2*k;
if (j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) > 0)
j ++;
if (data[indexes[k]].compareTo(data[indexes[j]]) > 0)
break;
swapIndexes(k, j);
k = j;
}
}
}
优先队列
PriorityQueue实现的接口
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
PriorityQueue自实现
优先队列时间复杂度
入队 | 出队(取出最大元素) | |
---|---|---|
普通线性结构 | O(1) | O(n) |
顺序线性结构 | O(n) | O(1) |
堆 | O(logn) | O(logn) |
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 void enqueue(E e) {
maxHeap.add(e);
}
@Override
public E dequeue() {
return maxHeap.extractMax();
}
@Override
public E getFront() {
return maxHeap.findMax();
}
}
优先队列的用途之一
- 在N个元素中选出前M个元素(排序:
NlogN
; 优先队列:NlogM
)
底层实现的Array类
public class Array<E>{ //E:Element 名字任意
private E[] data;
private int size;
//构造函数,传入数组的容量capacity构造Array
public Array(int capacity) {
data = (E[])new Object[capacity]; //强制类型转换
size = 0;
}
//无参数的构造函数,默认数组的容量capacity = 0
public Array() {
this(10);
}
public Array(E[] arr) {
data =(E[]) new Object[arr.length]; //强转
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
size = arr.length;
}
//获取数组中元素的个数
public int getSize() {
return size;
}
//获取数组的容量
public int getCapacity() {
return data.length;
}
// 返回数组是否为空
public boolean isEmpty() {
return size==0;
}
//在第index个位置插入一个新的元素e
public void add(int index, E e) {
if (index < 0 || index > size)
throw new IllegalArgumentException("AddLast failed. Require index >= 0 and index < size.");
if (size==data.length)
resize(2 * data.length); //扩容
for (int i = size-1; i >= index ; i--)
data[i+1] = data[i];
data[index] = e;
size++;
}
//向所有元素后添加一个新元素
public void addLast(E e) {
//if (size == data.length)
// throw new IllegalArgumentException("AddLast failed. Array is full.")
//data[size] = e;
//size++; //data[size++] = e;
add(size, e);
}
//向所有元素前添加一个新元素
public void addFirst(E e) {
add(0, e);
}
// 获取index索引位置的元素
public E get(int index) {
if (index < 0 || index > size)
throw new IllegalArgumentException("AddLast failed. Require index >= 0 and index < size.");
return data[index];
}
public E getLast() {
return get(size-1);
}
public E getFirst() {
return get(0);
}
//修改index索引位置的元素为e
void set(int index, E e) {
if (index < 0 || index > size)
throw new IllegalArgumentException("AddLast failed. Require index >= 0 and index < size.");
data[index] = e;
}
//查找数组中是否有元素e
public boolean contains(E e) {
for (int i=0; i<size; i++) {
if (data[i].equals(e)) //equals()值比较
return true;
}
return false;
}
//查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(E e) {
for (int i=0; i<size;i++) {
if(data[i] == e)
return i;
}
return -1;
}
//从数组中删除index位置的元素,返回删除的元素
public E remove (int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("remove failed. Index is illegal.");
E ret = data[index];
for (int i=index+1; i<size;i++)
data[i-1] = data[i];
size--;
if (size == data.length/4)
resize(data.length / 2);
return ret;
}
//从数组中删除第一个元素,返回删除的元素
public E removeFirst() {
return remove(0);
}
// 从数组中删除最后一个元素, 返回删除的元素
public E removeLast(){
return remove(size - 1);
}
//从数组中删除元素e
public void removeElement(E e) {
int index = find(e);
if (index != -1)
remove(index);
}
public void swap(int i, int j) {
if (i<0 || i>=size || j<0 || j>= size)
throw new IllegalArgumentException("Index is illegal.");
E t = data[i];
data[i] = data[j];
data[j] = t;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Array: size=%d, capacity = %d \n", size, data.length));
res.append("[");
for (int i =0; i<size; i++) {
res.append(data[i]);
if (i != size-1)
res.append(", ");
}
res.append(']');
return res.toString();
}
private void resize(int newCapacity) {
E[] newData = (E[])new Object[newCapacity];
for (int i = 0; i<size; i++)
newData[i] = data[i];
data = newData;
}
}
Leetcode347题-优先队列
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]示例 2:
输入: nums = [1], k = 1
输出: [1]
import java.util.TreeMap;
import java.util.PriorityQueue;
class Solution {
public int[] 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);
}
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
public int compare(Integer a, Integer b){
return map.get(a) - map.get(b);
}
});
for (int key: map.keySet()) { //map.keySet()获取map全部的key值
if(pq.size() < k)
pq.add(key);
else if(map.get(key) > map.get(pq.peek())) {
pq.remove();
pq.add(key);
}
}
int [] res = new int[k];
while(!pq.isEmpty())
for (int i=0;i<k;i++)
res[i] = pq.remove();
return res;
}
}