大顶堆定义:父节点要比任意两个孩子的值要大
heapify()建堆之后:
过程:威廉姆斯建堆算法 n*log(n)
这个时间复杂度如何估算呢?
得到这样一个式子以后我们就可以开始算时间复杂度了,但是这似乎有点为难数学不好的同学,但是没有关系!我们可以用这个网站
Wolfram|Alpha:计算型智能 (wolframalpha.com)
输入:
Sum[\(40)Divide[Power[2,x],Power[2,i]]*\(40)i-1\(41)\(41),{i,1,x}]
高度为4 ==>2^4 - 4 -1 = 11 意思是交换11次就能建堆
算法实现:
这个建堆操作要非常熟练!
import java.util.Arrays;
/**
* 大顶堆
*/
public class MaxHeap {
int[] array;
int size;
public MaxHeap(int[] array){
this.array = array;
this.size = array.length;
heapify();
}
public MaxHeap(int capacity){
this.array = new int[capacity];
}
//建堆
private void heapify(){
//如何找到最后这个非叶子节点 这个有公式:size>>1-1 or (size/2-1)
for(int i = size/2-1;i>=0;i--){
down(i);
}
}
/**
* 删除堆顶元素
* @Returns:堆顶元素
*/
public int poll(){
return 0;
}
//将parent索引处的元素下潜:与两个孩子较大者交换,直到没孩子或孩子没它大
private void down(int parent){
int left = parent*2+1;
int right = left+1;
int max = parent;
if(left<size && array[left]>array[max]){
max = left;
}
if(right<size && array[right]>array[max]){
max = right;
}
if(max!=parent){//找到更大的孩子
swap(max,parent);
down(max);
}
}
//交换两个索引处的元素
private void swap(int i,int j){
int t = array[i];
array[i] = array[j];
array[j] = t;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7};
MaxHeap maxHeap = new MaxHeap(array);
System.out.println(Arrays.toString(maxHeap.array));
//[7, 5, 6, 4, 2, 1, 3]
}
}
填充剩余代码:
/**
* 删除堆顶元素
* @Returns:堆顶元素
*/
public int poll(){
int top = array[0];
swap(0,size-1);
size--;
down(0);
return top;
}
/**
* 替换堆顶元素
* @param replaced - 新元素
*/
public void replace(int replaced){
array[0] = replaced;
down(0);
}
import java.util.Arrays;
/**
* 大顶堆
*/
public class MaxHeap {
int[] array;
int size;
public MaxHeap(int[] array){
this.array = array;
this.size = array.length;
heapify();
}
public MaxHeap(int capacity){
this.array = new int[capacity];
}
//建堆
private void heapify(){
//如何找到最后这个非叶子节点 这个有公式:size>>1-1 or (size/2-1)
for(int i = size/2-1;i>=0;i--){
down(i);
}
}
/**
* 删除堆顶元素
* @return:堆顶元素
*/
public int peek(){
//当然如果这个堆里面没有数据的话 会报错
//这个可以抛异常
return array[0];
}
/**
* 删除堆顶元素
* @Returns:堆顶元素
*/
public int poll(){
int top = array[0];
swap(0,size-1);
size--;
down(0);
return top;
}
/**
* 删除指定索引处元素
* @param:index - 索引
* @return:被删除元素
*/
public int poll(int index){
//可以一下考虑 堆为空的时候,这里就先不写了
int deleted = array[index];
swap(index,size-1);
size--;
down(index);
return deleted;
}
/**
* 替换堆顶元素
* @param replaced - 新元素
*/
public void replace(int replaced){
array[0] = replaced;
down(0);
}
/**
* 堆的尾部添加元素
* @param:offered-新元素
* @return:是否添加成功
*/
public boolean offer(int offered){
if(size==array.length){
return false;
}
up(offered);
size++;
return true;
}
//将 offered元素上浮:直到offered小于父元素或到堆顶
private void up(int offered){
int child = size;
while(child>0){
int parent = (child-1)/2;
if(offered>array[parent]){
array[child] = array[parent];
}else{
break;
}
child = parent;
}
array[child]=offered;
}
//将parent索引处的元素下潜:与两个孩子较大者交换,直到没孩子或孩子没它大
private void down(int parent){
int left = parent*2+1;
int right = left+1;
int max = parent;
if(left<size && array[left]>array[max]){
max = left;
}
if(right<size && array[right]>array[max]){
max = right;
}
if(max!=parent){//找到更大的孩子
swap(max,parent);
down(max);
}
}
//交换两个索引处的元素
private void swap(int i,int j){
int t = array[i];
array[i] = array[j];
array[j] = t;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7};
MaxHeap maxHeap = new MaxHeap(array);
System.out.println(Arrays.toString(maxHeap.array));
//[7, 5, 6, 4, 2, 1, 3]
}
}
堆排序
1.heapify 建立大顶堆
2.将堆顶与堆底交换(最大元素被交换到堆底),缩小并下潜调整堆
3.重复第二步直至堆里剩一个元素
public class MaxHeap {
int[] array;
int size;
public MaxHeap(int[] array){
this.array = array;
this.size = array.length;
heapify();
}
public MaxHeap(int capacity){
this.array = new int[capacity];
}
//建堆
private void heapify(){
//如何找到最后这个非叶子节点 这个有公式:size>>1-1 or (size/2-1)
for(int i = size/2-1;i>=0;i--){
down(i);
}
}
/**
* 删除堆顶元素
* @return:堆顶元素
*/
public int peek(){
//当然如果这个堆里面没有数据的话 会报错
//这个可以抛异常
return array[0];
}
/**
* 删除堆顶元素
* @Returns:堆顶元素
*/
public int poll(){
int top = array[0];
swap(0,size-1);
size--;
down(0);
return top;
}
/**
* 删除指定索引处元素
* @param:index - 索引
* @return:被删除元素
*/
public int poll(int index){
//可以一下考虑 堆为空的时候,这里就先不写了
int deleted = array[index];
swap(index,size-1);
size--;
down(index);
return deleted;
}
/**
* 替换堆顶元素
* @param replaced - 新元素
*/
public void replace(int replaced){
array[0] = replaced;
down(0);
}
/**
* 堆的尾部添加元素
* @param:offered-新元素
* @return:是否添加成功
*/
public boolean offer(int offered){
if(size==array.length){
return false;
}
up(offered);
size++;
return true;
}
//将 offered元素上浮:直到offered小于父元素或到堆顶
private void up(int offered){
int child = size;
while(child>0){
int parent = (child-1)/2;
if(offered>array[parent]){
array[child] = array[parent];
}else{
break;
}
child = parent;
}
array[child]=offered;
}
//将parent索引处的元素下潜:与两个孩子较大者交换,直到没孩子或孩子没它大
private void down(int parent){
int left = parent*2+1;
int right = left+1;
int max = parent;
if(left<size && array[left]>array[max]){
max = left;
}
if(right<size && array[right]>array[max]){
max = right;
}
if(max!=parent){//找到更大的孩子
swap(max,parent);
down(max);
}
}
//交换两个索引处的元素
private void swap(int i,int j){
int t = array[i];
array[i] = array[j];
array[j] = t;
}
public static void main(String[] args) {
int[] array = {2,3,1,7,6,4,5};
MaxHeap heap = new MaxHeap(array);
System.out.println(Arrays.toString(heap.array));//[7, 6, 5, 3, 2, 4, 1]
while(heap.size>1){
heap.swap(0,heap.size-1);
heap.size--;
heap.down(0);
}
System.out.println(Arrays.toString(heap.array));//
//[1, 2, 3, 4, 5, 6, 7]
}
}
215. 数组中的第K个最大元素 - 力扣(LeetCode)
先来看看思路:
这里使用小顶堆来解决这个问题,为什么不是大顶堆 咱们往后看
示例一:求第k大 我们可以先把数组前两个放进小顶堆中
实例二:求第k=4大,我们可以先把数组前四个小顶堆中(当然了大顶堆也可以 但是这里用小顶堆)
如果新加进来的元素比堆中的元素逗都小,那么我们什么也不做
如果进来的元素大就替换掉堆顶元素
例子:比如实例1中第三个元素是1 所以咱们什么也不做 接着看下一个数字5,发现5比堆顶元素2大,那么就要进行替换,然后重新调整小顶堆
经过这些运算,小顶堆中就是两个最大的元素 最终返回堆顶元素即可.
public class MinHeap {
int[] array;
int size;
public MinHeap(int[] array){
this.array = array;
this.size = array.length;
heapify();
}
public MinHeap(int capacity){
this.array = new int[capacity];
}
//建堆
private void heapify(){
//如何找到最后这个非叶子节点 这个有公式:size>>1-1 or (size/2-1)
for(int i = size/2-1;i>=0;i--){
down(i);
}
}
/**
* 删除堆顶元素
* @return:堆顶元素
*/
public int peek(){
//当然如果这个堆里面没有数据的话 会报错
//这个可以抛异常
return array[0];
}
/**
* 删除堆顶元素
* @Returns:堆顶元素
*/
public int poll(){
int top = array[0];
swap(0,size-1);
size--;
down(0);
return top;
}
/**
* 删除指定索引处元素
* @param:index - 索引
* @return:被删除元素
*/
public int poll(int index){
//可以一下考虑 堆为空的时候,这里就先不写了
int deleted = array[index];
swap(index,size-1);
size--;
down(index);
return deleted;
}
/**
* 替换堆顶元素
* @param replaced - 新元素
*/
public void replace(int replaced){
array[0] = replaced;
down(0);
}
/**
* 堆的尾部添加元素
* @param:offered-新元素
* @return:是否添加成功
*/
public boolean offer(int offered){
if(size==array.length){
return false;
}
up(offered);
size++;
return true;
}
//将 offered元素上浮:直到offered小于父元素或到堆顶
private void up(int offered){
int child = size;
while(child>0){
int parent = (child-1)/2;
if(offered<array[parent]){
array[child] = array[parent];
}else{
break;
}
child = parent;
}
array[child]=offered;
}
//将parent索引处的元素下潜:与两个孩子较大者交换,直到没孩子或孩子没它大
private void down(int parent){
int left = parent*2+1;
int right = left+1;
int min = parent;
if(left<size && array[left]<array[min]){
min = left;
}
if(right<size && array[right]<array[min]){
min = right;
}
if(min!=parent){//找到更大的孩子
swap(min,parent);
down(min);
}
}
//交换两个索引处的元素
private void swap(int i,int j){
int t = array[i];
array[i] = array[j];
array[j] = t;
}
}
/**
* 求数组中第K大的元素
* 解题思路:
* 1.向小顶堆放入前k个元素
* 2.剩余元素
*
* 若<=堆顶元素,则略过
* 若>堆顶元素,则替换堆顶元素
*
* 3.这样小顶堆始终保留的是到目前为止,第K大的元素
* 4.循环结束,堆顶元素即为第K大元素
*/
public class E02Leetcode215 {
public int findthLargest(int[] numbers,int k){
MinHeap heap = new MinHeap(k);
for (int i = 0; i < k; i++) {
heap.offer(numbers[i]);
}
for (int i = k; i < numbers.length; i++) {
if(numbers[i] > heap.peek()){
heap.replace(numbers[i]);
}
}
return heap.peek();
}
public static void main(String[] args){
//应为5
System.out.println(new E02Leetcode215().findthLargest(new int[]{3,2,1,5,6,4},2));
//应为4
System.out.println(new E02Leetcode215().findthLargest(new int[]{3,2,3,1,2,4,5,5,6},4));
}
}
703. 数据流中的第 K 大元素 - 力扣(LeetCode)
跟上面那倒题目的区别是:上面的数据是固定的 而这题反之
在数组中用堆排序并不是最优的选择因为有更优秀的算法.但是对于这道题目,最好的选择就是使用堆了.
public class MinHeap {
int[] array;
int size;
public MinHeap(int[] array){
this.array = array;
this.size = array.length;
heapify();
}
public MinHeap(int capacity){
this.array = new int[capacity];
}
//建堆
private void heapify(){
//如何找到最后这个非叶子节点 这个有公式:size>>1-1 or (size/2-1)
for(int i = size/2-1;i>=0;i--){
down(i);
}
}
/**
* 删除堆顶元素
* @return:堆顶元素
*/
public int peek(){
//当然如果这个堆里面没有数据的话 会报错
//这个可以抛异常
return array[0];
}
/**
* 删除堆顶元素
* @Returns:堆顶元素
*/
public int poll(){
int top = array[0];
swap(0,size-1);
size--;
down(0);
return top;
}
/**
* 删除指定索引处元素
* @param:index - 索引
* @return:被删除元素
*/
public int poll(int index){
//可以一下考虑 堆为空的时候,这里就先不写了
int deleted = array[index];
swap(index,size-1);
size--;
down(index);
return deleted;
}
/**
* 替换堆顶元素
* @param replaced - 新元素
*/
public void replace(int replaced){
array[0] = replaced;
down(0);
}
/**
* 堆的尾部添加元素
* @param:offered-新元素
* @return:是否添加成功
*/
public boolean offer(int offered){
if(size==array.length){
return false;
}
up(offered);
size++;
return true;
}
//将 offered元素上浮:直到offered小于父元素或到堆顶
private void up(int offered){
int child = size;
while(child>0){
int parent = (child-1)/2;
if(offered<array[parent]){
array[child] = array[parent];
}else{
break;
}
child = parent;
}
array[child]=offered;
}
//将parent索引处的元素下潜:与两个孩子较大者交换,直到没孩子或孩子没它大
private void down(int parent){
int left = parent*2+1;
int right = left+1;
int min = parent;
if(left<size && array[left]<array[min]){
min = left;
}
if(right<size && array[right]<array[min]){
min = right;
}
if(min!=parent){//找到更大的孩子
swap(min,parent);
down(min);
}
}
public boolean isFull(){
return size==array.length;
}
//交换两个索引处的元素
private void swap(int i,int j){
int t = array[i];
array[i] = array[j];
array[j] = t;
}
}
public class E03Leetcode703 {
//将小顶堆MinHeap作为成员变量
private MinHeap heap;
public E03Leetcode703(int k,int[] nums){
heap = new MinHeap(k);
for(int num:nums){
add(num);
}
}
//此方法会被不断调用,模拟数据流中新来的元素
public int add(int val){
if(!heap.isFull()){
heap.offer(val);
}
//这样写有些测试样例过不了 因为这是一个流 有可能数组中刚刚开始凑不满三个
//所以我们可以在小顶堆实现ifFull()
else if(val>heap.peek()){
heap.replace(val);
}
return heap.peek();
}
public static void main(String[] args){
E03Leetcode703 test = new E03Leetcode703(3,new int[]{4,5,8,2});
//小顶堆 4 5 8
//跟上一题思路类似
//因为k = 3 所以 4 5 8添加
System.out.println(test.add(3));//[4 5 8] ==>4
System.out.println(test.add(5));//[5,5,8] ==> 5
System.out.println(test.add(10));//[5,8,10] ==> 5
System.out.println(test.add(9));// [8 9 10] ==> 8
System.out.println(test.add(4));// [8 9 10] ==> 8
}
}
295. 数据流的中位数 - 力扣(LeetCode)
可能你会想把数据排序然后就可以求中位数了,但是这是数据流,数据不断变化,这样效率很低
1 2 3 7 8 9 思路: 我们不是说非要把所有的数据收集起来然后重头到尾排个序,我们可以把所有数据分成两部分 一部分是较小的数据 大概占一半 一部分是较大的数据 从小的里面挑大的 大的里面挑小的 s s s g g g 3 7 大顶堆 小顶堆 所以可以用两个堆来解决
import java.util.Arrays;
/**
* <h3>数据流的中位数</h3>
*/
public class E04Leetcode295_1 {
/**
* 为了保证两边数据量的平衡
* <ul>
* <li>两边个数一样时,左边个数加一</li>
* <li>两边个数不一样时,右边个数加一</li>
* </ul>
* 但是, 随便一个数能直接加入吗?
* <ul>
* <li>左边个数加一时, 把新元素加在右边,弹出右边最小的加入左边</li>
* <li>右边个数加一时, 把新元素加在左边,弹出左边最小的加入右边</li>
* </ul>
*/
public void addNum(int num) {
if (left.size() == right.size()) {
right.offer(num);
left.offer(right.poll());
} else {
left.offer(num);
right.offer(left.poll());
}
}
private Heap left = new Heap(10, true);
private Heap right = new Heap(10, false);
@Override
public String toString() {
int size = left.size;
int[] left = new int[size];
for (int i = 0; i < left.length; i++) {
left[size - 1 - i] = this.left.array[i];
}
int[] right = Arrays.copyOf(this.right.array, this.right.size);
return Arrays.toString(left) + " <-> " + Arrays.toString(right);
}
/**
* <ul>
* <li>两边数据一致, 左右各取堆顶元素求平均</li>
* <li>左边多一个, 取左边堆顶元素</li>
* </ul>
*/
public double findMedian() {
if (left.size() == right.size()) {
return (left.peek() + right.peek()) / 2.0;
} else {
return left.peek();
}
}
public static void main(String[] args) {
E04Leetcode295_1 test = new E04Leetcode295_1();
test.addNum(1);
test.addNum(2);
test.addNum(3);
test.addNum(7);
test.addNum(8);
test.addNum(9);
System.out.println(test);
System.out.println(test.findMedian());
test.addNum(10);
System.out.println(test);
System.out.println(test.findMedian());
test.addNum(4);
System.out.println(test);
System.out.println(test.findMedian());
}
}
/**
* 可以扩容的heap max用于指定是大顶堆还是小顶堆
*/
public class Heap {
int[] array;
int size;
boolean max;
public int size() {
return size;
}
public Heap(int capacity, boolean max) {
this.array = new int[capacity];
this.max = max;
}
/**
* 获取堆顶元素
*
* @return 堆顶元素
*/
public int peek() {
return array[0];
}
/**
* 删除堆顶元素
*
* @return 堆顶元素
*/
public int poll() {
int top = array[0];
swap(0, size - 1);
size--;
down(0);
return top;
}
/**
* 删除指定索引处元素
*
* @param index 索引
* @return 被删除元素
*/
public int poll(int index) {
int deleted = array[index];
swap(index, size - 1);
size--;
down(index);
return deleted;
}
/**
* 替换堆顶元素
*
* @param replaced 新元素
*/
public void replace(int replaced) {
array[0] = replaced;
down(0);
}
/**
* 堆的尾部添加元素
*
* @param offered 新元素
*/
public void offer(int offered) {
if (size == array.length) {
grow();
}
up(offered);
size++;
}
private void grow() {
int capacity = size + (size >> 1);
int[] newArray = new int[capacity];
System.arraycopy(array, 0,
newArray, 0, size);
array = newArray;
}
// 将 offered 元素上浮: 直至 offered 小于父元素或到堆顶
private void up(int offered) {
int child = size;
while (child > 0) {
int parent = (child - 1) / 2;
boolean cmp = max ? offered > array[parent] : offered < array[parent];
if (cmp) {
array[child] = array[parent];
} else {
break;
}
child = parent;
}
array[child] = offered;
}
public Heap(int[] array, boolean max) {
this.array = array;
this.size = array.length;
this.max = max;
heapify();
}
// 建堆
private void heapify() {
// 如何找到最后这个非叶子节点 size / 2 - 1
for (int i = size / 2 - 1; i >= 0; i--) {
down(i);
}
}
// 将 parent 索引处的元素下潜: 与两个孩子较大者交换, 直至没孩子或孩子没它大
private void down(int parent) {
int left = parent * 2 + 1;
int right = left + 1;
int min = parent;
if (left < size && (max ? array[left] > array[min] : array[left] < array[min])) {
min = left;
}
if (right < size && (max ? array[right] > array[min] : array[right] < array[min])) {
min = right;
}
if (min != parent) { // 找到了更大的孩子
swap(min, parent);
down(min);
}
}
// 交换两个索引处的元素
private void swap(int i, int j) {
int t = array[i];
array[i] = array[j];
array[j] = t;
}
public Object getSize() {
return size;
}
}
-----
import java.util.Comparator;
import java.util.PriorityQueue;
/*
当然我们也可以不用自己定义的heap 在java中也有一个功能类似的类PriorityQueue 优先级队列
*/
public class E04Leetcode295 {
/**
* 为了保证两边数据量的平衡
*
* 两边个数一样时,左边个数+1
* 两边个数不一样时,右边个数+1
*
* 但是,随便一个数直接加入吗?
*
* 左边个数+1时,应该挑右边最小的加入
* 右边个数+1时,应该挑左边最大的加入
*
*/
public void addNum(int num){
if(left.size()==right.size()){
right.offer(num);
left.offer(right.poll());
}else{
left.offer(num);
right.offer(left.poll());
}
}
/**
* 两边数据一致,左右各取堆顶元素平均
* 左边多一个,取左边堆顶元素
*/
public double findMedian(){
if(left.size()== right.size()){
return (left.peek()+right.peek())/2.0;
}
else {
return left.peek();
}
}
//大顶堆
private PriorityQueue<Integer> left =new PriorityQueue<>(
(a,b)->Integer.compare(b,a)//-1 b<a 0 a==b 1 b>a
);
//比较器
//小顶堆
private PriorityQueue<Integer> right =new PriorityQueue<>(
(a,b)->Integer.compare(a,b)//省略不写也一样 compare
//-1 a<b 0 a==b 1 a>b
);
public static void main(String[] args) {
//这里就不去看源码了 以上浮为例,大概的实现逻辑
Comparator<Integer>cmp = (a,b)->Integer.compare(a,b);//小顶堆比较器compare -1 a<b,0 a==b,1 a>b
// Comparator<Integer>cmp = (a,b)->Integer.compare(b,a);//小顶堆比较器compare -1 b<a,0 a==b,1 b>a
int a = 10;//父元素值
int b =5;//新增元素值
if(cmp.compare(a,b)>0){
System.out.println("上浮");
}else{
System.out.println("停止上浮");
}
}
}
class MedianFinder {
PriorityQueue<Integer> queMin;
PriorityQueue<Integer> queMax;
public MedianFinder() {
queMin = new PriorityQueue<Integer>((a, b) -> (b - a));
queMax = new PriorityQueue<Integer>((a, b) -> (a - b));
}
public void addNum(int num) {
if (queMin.isEmpty() || num <= queMin.peek()) {
queMin.offer(num);
if (queMax.size() + 1 < queMin.size()) {
queMax.offer(queMin.poll());
}
} else {
queMax.offer(num);
if (queMax.size() > queMin.size()) {
queMin.offer(queMax.poll());
}
}
}
public double findMedian() {
if (queMin.size() > queMax.size()) {
return queMin.peek();
}
return (queMin.peek() + queMax.peek()) / 2.0;
}
}