最大堆_二叉堆:
最小堆_二叉堆
堆的特征:
add:
这个过程的时间复杂度是根树的高度有关
是logn级别
add代码:
private void elementsNullCheck(E e){
if(e==null){
throw new IllegalArgumentException("element must not be null");
}
}
@Override
public void add(E element) {
elementsNullCheck(element);
//检查容量是否够再添加一个元素
ensureCapacity(size+1);
elements[size++]=element;
siftUp(size-1);
}
/**
*当添加之后 我们要上溢最后一个元素
* 即是索引为size-1处的元素
*/
private void siftUp(int index){
E indexelement=elements[index];
while (index>0){
int parentindex=(index-1)/2;
E parentelment=elements[parentindex];
if(compare(parentelment,indexelement)>=0){
return;
}
//交换
E temp=elements[index];
elements[index]=elements[parentindex];
elements[parentindex]=temp;
//重新赋值index
index=parentindex;
}
}
add优化:
也即是对上溢操作的优化:
/**
* 优化swift:
* 到循环结束的时候再把最开始的时候元素加上 避免了大量的交换
*/
private void siftUpWell(int index){
E indexelememt=elements[index];
while (index>0){
int parentindex=(index-1)/2;
E parentelement=elements[parentindex];
if(compare(parentelement,indexelememt)>=0){
break;
}
elements[index]=elements[parentindex];
index=parentindex;
}
elements[index]=indexelememt;
}
删除:每一次都是删除堆顶的元素
以下这两段代码等价:
第二段进行了优化。。。。。。。。。。。
关键:
关键:
找到第一个叶子节点的索引
这个值也就是非叶子节点的数量。。。。。并且我们默认是向下取整 直接带入公式即可
remove代码:
@Override
public E remove() {
emptyCheck();
E root=elements[0];
/**
* 1、用最后一个节点元素覆盖顶点 之后删除最后一个节点元素
*/
root=elements[size-1];
elements[size-1]=null;
size--;
siftDown(0);
return root;
}
/**
*当我们进行交换之后 把最后一个元素值交换到堆顶
* 很有可能已经违背了我们的大顶堆的原则:父节点的元素值大于子节点(们)的元素值
* 另外给定几个公式:
* 1.假设有n个节点 那么非叶子节点的个数为floor(n/2)
* 2.假设一个节点索引为i 那么左子:2*i+1 右子:2*i+1+1 父节点:(i-1)/2
*/
private void siftDown(int index) {
E element=elements[0];
int half=size>>1;//非叶子节点的数量
while (index<half){
//默认是左边的子节点与父节点比较 那么这里childindex默认为左子的索引下标
int childindex=2*index+1;
E childelement=elements[childindex];
int childindexright=2*index+1+1;//即是childindexleft+1
//右子节点与左进行比较一下
//1.判断右子的索引范围
// 2.如果相等那么就默认取左边即可
if(childindexright<half&&compare(elements[childindexright],childelement)>0){
childelement=elements[childindexright];
childindex=childindexright;
}
if(compare(childelement,element)<=0){
break;//退出循环
}
index=childindex;
}
elements[index]=element;
}
repalce:
删除一个堆顶元素并且再增加一个元素
操作:直接用这个增加的元素代替堆顶元素
然后再把这个元素进行下滤操作
/**
*删除堆顶元素并且增加一个元素
* 方法:把这个增加的元素直接代替堆顶元素
* 然后把堆顶元素直接进行下溢操作
*/
@Override
public E replace(Object element){
E root=null;
if(size==0){
elements[0]= (E) element;
size++;
} else {
root=elements[0];
elements[0]= (E) element;
siftDown(0);
}
return root;
}
1.
这种操作每一次都把一部分的元素变成最大堆
如图画红圈的都是每一次变化之后对应的最大堆的形式。。。。
总结:每完成一次上滤操作 就会让最大堆变得大一点
最终让整个堆都变成最大堆。。。
这两种做法的效率对比:
由图可见:
自上而下的上滤操作 下面的基数比较大
因此:进行的操作比较多,,,,,,
因此后者更优。。。。。。。。。。。
总结:
前者时间复杂度为:O(nlogn) 自上而下的上滤类似于添加add
后者为O(n) 这种自下而上的下滤操作类似于remove删除操作
代码:
private E[] elements;
public BinaryHeap(Comparator<E> comparator,E[] elements){
super(comparator);//调用父类的比较器即可_super表示调用父类的属性操作
if(elements==null||elements.length==0){
this.elements=(E[])new Object[DEFAULT_CAPACITY];
} else {
int capacity=DEFAULT_CAPACITY>elements.length?DEFAULT_CAPACITY:elements.length;
this.elements=(E[])new Object[capacity];
/**
* 进行深拷贝。。。。。
*/
for (int i = 0; i < elements.length; i++) {
this.elements[i]=elements[i];
}
heapify();
}
}
private void heapify() {
/* //自上而下的上滤
for (int i = 0; i < elements.length; i++) {
siftUp(i);
}*/
//自下而上的下滤效率较高
for (int i =(size>>1-1); i>=0; i--) {
siftDown(i);
}
}
细节:
当我们外部new一个对象时 传参数传过去一个数组到我们的构造器那里
我们要进行深拷贝。。。。
因为有可能外部进行修改数组的内容会改变
深拷贝会在内存中申请另外一块独立的空间 避免被外部操作而修改
测试类:::::::::::::::::::::::::如下::—leomessi—
如何搞出小顶堆?
重写这个比较器即可,,,,,,,匿名内部类:::
Top K问题:
代码:
private E[] elements;
public BinaryHeap(Comparator<E> comparator,E[] elements){
super(comparator);//调用父类的比较器即可_super表示调用父类的属性操作
if(elements==null||elements.length==0){
this.elements=(E[])new Object[DEFAULT_CAPACITY];
} else {
int capacity=DEFAULT_CAPACITY>elements.length?DEFAULT_CAPACITY:elements.length;
this.elements=(E[])new Object[capacity];
/**
* 进行深拷贝。。。。。
*/
for (int i = 0; i < elements.length; i++) {
this.elements[i]=elements[i];
}
heapify();
}
}
private void heapify() {
/* //自上而下的上滤
for (int i = 0; i < elements.length; i++) {
siftUp(i);
}*/
//自下而上的下滤效率较高
for (int i =(size>>1-1); i>=0; i--) {
siftDown(i);
}
}
思路:
1.首先先模拟出一个小顶堆 测试类中重写比较策略即可。。。。
2.把前k个元素放入这个小顶堆中
3.运用heap.get() 每一次拿出堆顶的元素与从第k+1个元素开始进行比较
如果第k+1个元素开始 一旦这个新来的元素大于堆顶元素
那么进行replace操作
何为replace?
把堆顶元素替换删除并且下滤堆顶元素
4.每一次的操作都会使这个堆的元素进一步的整体范围增大。。。。
5.最终在堆中的k个元素就是我们要找的k个元素。。。。。
时间复杂度:
O(nlogk)相对于快速排序的O(nlogn)更优秀了多。。。。