关于堆,堆其实就是一个完全二叉树。其次,如果把这个完全二叉树,从上到下,从左到右的排序的,它可以存放在一个一维数组中,而如果一个节点的index是x的话,那么它的左子树就刚好是2x,右子树是2x+1所以因为有这层关系,也就奠定了可以用一维数组来存贮的,每次找左子树的时候就把当先节点的下标*2就可以在数组中找到左子树的值了。之后来看这个最小堆的特点,之后根据它的特点以及功能用代码实现它
1,I x 能够插入数,并会在插入的时候自动把堆中的数组动态的重组,让堆中最小的值存储在堆顶部
2,PM 可以输出当前集合中的最小值,这简单,我们写的就是最小堆,所以直接输出数组第一个值就好了。
3,DM 可以删除当前集合中最小的值,并且还要删除成功后,自动把堆中的数组动态的重组,让堆中最小的值存储在堆顶部
4,D k 可以删除指定数,比如删除第k个插入的数
5,C k x 可以修改第k个插入的数,将其变为x,
第四第五也同样要符合要求,可以自动把堆中的数组动态的重组,让堆中最小的值存储在堆顶部
首先我们既然是用数组存储的就必须要有一个足够大的数组,然后还要存储一个size变量,用于存储这个堆一共有多大,
这五个点中都有一个共同的要求就是:动把堆中的数组动态的重组,让堆中最小的值存储在堆顶部,那么怎么做才能够让数组会是以堆的样子排序,让最小的值存储在最顶端呢?
我这里是写了一共up方法和一共down方法。他们的功能就是当你在一共完美的堆中,中突然插入一个数u,那么就要判断这个u是要向上移动还是向下移动,用这个u和它的左子树和右子树都做比较,如果大于子树的话,就让它和子树交换值,并递归子树,直到它不在大于左子树和右子树,或者到底最低端,此时,这个u所在的地方也就符合一定小于父节点,一定大于子节点。而up也是相同的道理,用它和他的父节点作比较,看它是否需要向上移动。
static void down(int u){
int t = u;
//让他跟左子树的值最比较
if(u*2 <= size && h[u*2] < h[t])t = u*2;
//让他与右子树的值作比较 ,如果大于右子树,就把t存储为右子树的值
if(u*2+1 <= size && h[u*2+1] < h[t])t = u*2+1;
//如果u != t 也就是上面两个判断中,让t的值发生了变化,也就是说他是比左子树大或者右子树大的
if(u != t){
//那就将它与那个子树交换值,然后递归down(t)那个子树
int temp = h[u];
h[u] = h[t];
h[t] = temp;
down(t);
}
}
static void up(int u){
int t = u;
if(u/2 > 0 && h[u/2] > h[t]) t = u/2;
if (u != t){
int temp = h[u];
h[u] = h[t];
h[t] = temp;
up(t);
}
}
如果我们实现了这两个方法,这样在存储值到堆中,只需要直接把它放在最末尾,然后给他up(size)即可。他会根据up方法中的递归,自动给的上升到它应该在的位置。
static void insert(int x){ h[++size] = x; up(size); }
如果是删除当前数组的最小的值,那就让把最后一个数跟第一个数给交换位置,再把最后一个数给删除,把第一个数使用函数down(size)即可自动下降到它应该在的位置
static void delete(){ h[1] = h[size]; size--; down(1); }
所以关于修改第k个数或者删除第k个数也是类似的,最后就把完整的代码附上。
import java.util.Scanner;
public class 模拟堆 {
static int N = 100010;
static int h[] = new int[N];
static int size;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
while (m-- > 0){
String a= sc.next();
switch (a){
case "I":
int x = sc.nextInt();
insert(x);
break;
case "PM":
pr();
break;
case "DM":
delete();
break;
case "D":
int k = sc.nextInt();
deletek(k);
break;
default:
int k1 = sc.nextInt();
int x1 = sc.nextInt();
update(k1,x1);
}
}
}
static void down(int u){
//先把这个节点的最初的值给赋值给t,目的是看待到比较完字数后,t的值会不会发生变化。
int t = u;
//让他跟左子树的值最比较
if(u*2 <= size && h[u*2] < h[t])t = u*2;
//让他与右子树的值作比较 ,如果大于右子树,就把t存储为右子树的值
if(u*2+1 <= size && h[u*2+1] < h[t])t = u*2+1;
//如果u != t 也就是上面两个判断中,让t的值发生了变化,也就是说他是比左子树大或者右子树大的
if(u != t){
//那就将它与那个子树交换值,然后递归down(t)那个子树
int temp = h[u];
h[u] = h[t];
h[t] = temp;
down(t);
}
}
static void up(int u){
int t = u;
if(u/2 > 0 && h[u/2] > h[t]) t = u/2;
if (u != t){
int temp = h[u];
h[u] = h[t];
h[t] = temp;
up(t);
}
}
//插入一棵树
static void insert(int x){
h[++size] = x;
up(size);
}
//输出当前集合中的最小值
static void pr(){
System.out.println(h[1]);
}
//删除当前集合中的最小值
static void delete(){
h[1] = h[size];
size--;
down(1);
}
//删除第k个插入的数
static void deletek(int k){
h[k] = h[size];
size--;
down(k);up(k);
}
//修改第k个插入的数,将其变为x
static void update(int k,int x){
h[k] = x;
down(k);up(k);
}
}