学习总结-自《数据结构与算法》
1 堆的定义
- 是一个完全二叉数 (二叉数相关的具体概念可看链接: 二叉树.)
- 堆中的每个节点都必须大于或小于其子树所有节点的值。
-
每个节点大于其子树节点的堆叫做大顶堆
-
每个节点小于其子树节点的堆叫做小顶堆
2 堆的相关操作
2.1插入元素
步骤:(大顶锥的情况下)
(1)在堆数组尾部插入
(2)然后依次将插入节点与其父子节点进行大小比较。如果比父节点还要大,就跟父节点交换位置。一直循环比较父节点大小。知道到达根节点。(即自下向上堆化)
public class BinaryHeapTest {
private int[] a;//模拟大顶锥 //a[0]为空不放置数.a[1]为第一个数
private int n;//堆中允许存储的最大容量
private int count;//当前的最大容量
public BinaryHeapTest(int n) {
this.n =n;
a = new int[n+1];
count =0;
}
//往堆中添加一个元素(自下向上堆化)
public void insert(int value) {
if(count >= n) {//容量已满,返回
return;
}
count++;
a[count] =value;
int i =count;
while(i >1 || a[i] <= a[i/2]) {//退出条件:当前节点为父节点或者当前节点小于父节点
swap(a,i,i/2);//交换两个元素
i = i/2;
}
}
//交换两个元素
private void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] =tmp;
}
}
2.2 删除顶点元素
步骤:
(1)将顶点元素与最后一个元素交换,然后删除最后一个元素
(2)将顶点元素与其左右两个子节点进行大小比较。于较大的元素交换。然后继续跟两个子节点进行比较。直到达到叶子节点
//删除顶点元素
/**
* 将数组的最后一个元素移到堆顶。删除最后一个元素。
* 然后自上向下堆化
*/
public void removeMax() {
a[1] = a[count];
count--;
//退出条件:到达最后一个元素,或者 当前节点大于左右子节点
int i =1;
int next =1;
while(true) {
if(2*i<=n && a[i] < a[2*i]) next = 2*i;
if(2*i<=n && a[next] <a[2*i+1]) next =2*i+1;
if(next ==i) break;//说明当前节点大于左右子节点,不需要再堆化,直接退出
swap(a,i,next);
i =next;
}
}
3 堆的应用
3.1堆排序
- 步骤一:建堆
//建堆
public static void buildHeap(int[] a,int n) {
//从非叶子节点开始堆化
for(int i= n/2;i>=0;i--) {
heapify(a,i,n);
}
}
private static void heapify(int[] a, int i, int n) {
//自上往下堆化
while(true) {
int maxpoint = i;
if(2*i <=n && a[i] < a[2*i]) maxpoint = 2*i;
if(2*i+1<=n && a[maxpoint] <a[2*i+1]) maxpoint = 2*i+1;
if(maxpoint == i) break;
swap(a,i,maxpoint);
i = maxpoint;
}
}
//交换两个元素
private static void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] =tmp;
}
- 步骤二:排序
思路:将顶点元素于最后一个元素交换。然后在index =1和index =n-1之间进行堆化。循环n次后,数组就是从小到大排序好了
public static void sort(int[] a,int n) {
//1 建堆
buildHeap(a,n);
//2 删除堆顶元素(即排序)
int k = n;
while(k >1) {
swap(a,1,k);
k--;
heapify(a,1,k);
}
}
3.2 求topK
求最大前K的数据,用小顶锥。维护一个容量为K的堆。因为小顶锥的堆顶元素小于所有子节点。所以顶堆元素就是第K大的元素
求最小前K的数据,用大顶锥。维护一个容量为K的堆。因为大顶堆的堆顶元素大于所有子节点。所有堆顶元素就是第K小的元素。
(可通过leetcord 703题练习)
//计算排序后的第k大的元素(手写小顶锥)
/**
* 思路:
* 1.构建一个容量为k的堆
* 2.插入元素
* (1)如果堆中已经有k的元素,且插入的元素比堆顶元素小,不做操作
* (2)如果堆中已经有k的元素,且插入的元素比堆顶元素大,替换堆顶元素,重新堆化
* (3)如果堆中不满k个元素,则直接插入,堆化。
* @author ldd
*
*/
class KthLargest {
int[] a;
int count;
int n;
public KthLargest(int k, int[] nums) {//假设nums的长度>=k
n =k;
a = new int[k+1];
for(int i =0;i<nums.length;i++) {
insert(nums[i]);
}
//如果count >= n
if(count >=n) {
for(int i =count;i<nums.length;i++) {
add(nums[i]);
}
}
}
//往堆中添加一个元素(自下向上堆化)
public void insert(int value) {
if(count >= n) {//容量已满,返回
return;
}
count++;
a[count] =value;
int i =count;
while(i >1 && a[i] < a[i/2]) {//退出条件:当前节点为父节点或者当前节点大于父节点
swap(a,i,i/2);//交换两个元素
i = i/2;
}
}
//交换两个元素
private void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] =tmp;
}
public int add(int val) {
//堆化
//如果还未被填满
if(count <n) {
insert(val);
}else if(val>a[1]) {//如果堆满了并且新插入的元素大于堆顶元素,替换堆顶元素
a[1] =val;
heapify(1,n+1);
}
return a[1];
}
private void heapify(int i, int n) {
//自上往下堆化
while(true) {
int minpoint = i;
if(2*i <n && a[i] > a[2*i]) minpoint = 2*i;
if(2*i+1<n && a[minpoint] > a[2*i+1]) minpoint = 2*i+1;
if(minpoint == i) break;
swap(a,i,minpoint);
i = minpoint;
}
}
}