模拟堆也是对堆的一次深入理解和一些其它操作,可以了解一下。
文章目录
前言
一、模拟堆
二、算法思路
1.结点上移
2.结点下移
3.插入一个数
4.输出当前集合的最小值
5.删除当前集合的最小值(数据保证此时的最小值唯一)
6.删除第k个插入的数
7.修改第k个插入的数,修改为x
三、代码如下
1.代码如下:
2.读入数据:
3.代码运行结果
总结
前言
模拟堆也是对堆的一次深入理解和一些其它操作,可以了解一下。
提示:以下是本篇文章正文内容,下面案例可供参考
一、模拟堆
维护一个集合,初始时集合为空,支持如下几种操作:
I x
,插入一个数 x;PM
,输出当前集合中的最小值;DM
,删除当前集合中的最小值(数据保证此时的最小值唯一);D k
,删除第 k 个插入的数;C k x
,修改第 k 个插入的数,将其变为 x;
现在要进行 N 次操作,对于所有第 22 个操作,输出当前集合的最小值。
输入格式
第一行包含整数 N。
接下来 N 行,每行包含一个操作指令,操作指令为 I x
,PM
,DM
,D k
或 C k x
中的一种。
输出格式
对于每个输出指令 PM
,输出一个结果,表示当前集合中的最小值。
每个结果占一行。
数据范围
1≤N≤100000
−1000000000≤x≤1000000000
数据保证合法。
二、算法思路
我们还是通过一维整型数组heap来存储堆的值,数组下标来表示是哪一个结点,size表示堆中结点的个数或者堆中最后一个元素的下标。这道题中我们还是以小根堆为例子。堆在数组中存储,任一结点的下标为x,那么它的左孩子在数组中存储的下标为2x,右孩子在数组中存储的下标为2x + 1。(注:数组存储结点下标从1开始)
1.结点上移
当我们插入一个新结点这个结点值比它的父结点值小或者修改某一个结点结点值比它的父节点小这两种情况我们才会让结点上移来维护小根堆的性质。
//结点上移,只需要跟父结点比较即可
public static void up(int x){
while ( x / 2 >= 1 && heap[x / 2] > heap[x]){
int temp = heap[x / 2];
heap[x / 2] = heap[x];
heap[x] = temp;
x /= 2;
}
}
我们需要判断当前下标有没有父结点即 x / 2是否满足大于等于1,并且当父结点的值比子结点的值大的时候,我们就需要将父结点和子结点进行交换,然后再判断父结点是否小于它的父节点重复上述过程。具体详情可以看https://blog.csdn.net/m0_63267251/article/details/139388919这篇博客。
2.结点下移
当我们结点值修改后比它的左孩子或者右孩子值大,然后我们从中找到值最小的那个索引,然后让父结点跟最小的结点进行交换,交换后我们再判断当前结点的值是否比它的左孩子或者右孩子值大,重复上述操作,我们要保证父节点的值要小于它的左孩子和右孩子的值。 具体详情可以看https://blog.csdn.net/m0_63267251/article/details/139388919这篇博客。
//传入结点下标
public static void down(int x){
int temp = x;
//两个if语句来找出3个结点中最小的结点的下标
if(2 * x <= size && heap[2* x] < heap[temp]){
temp = 2 * x;
}
if (2 * x + 1 <= size && heap[2*x + 1] < heap[temp]){
temp = 2 * x + 1;
}
//说明此时结点不是最小值,进行交换,再递归处理看是否还需要交换
if(temp != x){
int t = heap[temp];
heap[temp] = heap[x];
heap[x] = t;
down(temp);
}
}
3.插入一个数
插入一个数,我们直接在数组的末尾添加元素即可,而size既可以表示堆中元素的个数,也可以表示堆中元素在数组中存储的最后一个结点的下标。故我们添加元素为heap[size] = x;size++;
if (cmd.equals("I")){
x = Integer.parseInt(s[1]);
heap[++size] = x;
}
4.输出当前集合的最小值
因为我们搭建的是小根堆,故我们堆中的根结点的值就是堆中元素的最小值即heap[1];
5.删除当前集合的最小值(数据保证此时的最小值唯一)
我们删除当前集合的最小值,数组的第一个元素是很难被删除的,我们还是删除数组的最后一个元素,将根节点赋值为数组的最后一个元素即heap[1] = heap[size];size--;然后此时有可能小根堆的性质被破坏,所以我们只需要将根节点结点下移操作就可以即down(1);此时我们就完成了删除当前集合最小值的情况。
6.删除第k个插入的数
这个操作麻烦的是删除第k个插入的数,而不是删除第k个结点。因此我们需要一个一维整型数组khp,来存储第k个插入的数在堆中的下标即khp[k];我们还需要一个一维整型数组hp,来存储我们堆中的点是第几个插入的点;例如kph[j] = k表示第j个插入的点是堆中第k个结点,ph[k] = j表示堆中的第k个结点时我们第k个插入的点,这两个数组的值对应的是相反的。那么我们就需要更新一下我们的down操作和up操作。
public static void down(int x){
int temp = x;
if(2 * x <= size && heap[2 * x] < heap[temp]){
temp = 2 * x;
}
if(2 * x + 1 <= size && heap[2 * x + 1] < heap[temp]){
temp = 2 * x + 1;
}
if(temp != x){
hp_swap(temp,x);
down(temp);
}
}
//结点上移,只需要跟父结点比较即可
public static void up(int x){
while ( x / 2 >= 1 && heap[x / 2] > heap[x]){
hp_swap(x,x/2);
x /= 2;
}
}
//因为hp数组可以对应为堆中的元素下标在khp数中的关系,所以我们先交换khp,后交换hp或者先交换hp再交换khp也可以
public static void hp_swap(int a,int b){
int t = khp[hp[a]];
khp[hp[a]] = khp[hp[b]];
khp[hp[b]] = t;
t = hp[a];
hp[a] = hp[b];
hp[b] = t;
t = heap[a];
heap[a] = heap[b];
heap[b] = t;
}
对应的插入一个数、输出当前集合和最小值和删除集合最小值操作更新。
if (cmd.equals("I")){
x = Integer.parseInt(s[1]);
size++;
m++;
heap[size] = x;
khp[m] = size;
hp[size] = m;
up(size);
} else if (cmd.equals("PM")) {
pw.println(heap[1]);
} else if (cmd.equals("DM")) {
hp_swap(1,size);
size--;
down(1);
} else if (cmd.equals("D")) {
k = Integer.parseInt(s[1]);
//找到第k歌数在堆中的位置
k = khp[k];
hp_swap(k,size);
size--;
//两个操作最多执行一个
up(k);
down(k);
}
7.修改第k个插入的数,修改为x
我们将插入第k个数在堆中的坐标找到即khp[k],然后将堆中的值修改为x即heap[khp[k]] = x。然后再维护一下小根堆性质down(kph[k])、up(khp[k])即可。
if (cmd.equals("C")) {
k = Integer.parseInt(s[1]);
x = Integer.parseInt(s[2]);
k = khp[k];
heap[k] = x;
down(k);
up(k);
}
三、代码如下
1.代码如下:
import java.util.*;
import java.io.*;
public class 模拟堆 {
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int N = 100010;
//堆中的数据
static int[] heap = new int[N];
static int size = 0;
//第k个插入数在堆中的下标
static int[] khp = new int[N];
//堆中的哪一个点是我们第几个插入的点
static int[] hp = new int[N];
public static void main(String[] args) throws Exception{
Scanner sc = new Scanner(br);
int n = nextInt();
//表示第几个插入的数
int m = 0;
while (n-- > 0){
String[] s = nextLine().split(" ");
String cmd = s[0];
int x;
int k;
if (cmd.equals("I")){
x = Integer.parseInt(s[1]);
size++;
m++;
heap[size] = x;
khp[m] = size;
hp[size] = m;
up(size);
} else if (cmd.equals("PM")) {
pw.println(heap[1]);
} else if (cmd.equals("DM")) {
hp_swap(1,size);
size--;
down(1);
} else if (cmd.equals("D")) {
k = Integer.parseInt(s[1]);
//找到第k歌数在堆中的位置
k = khp[k];
hp_swap(k,size);
size--;
//两个操作最多执行一个
up(k);
down(k);
} else if (cmd.equals("C")) {
k = Integer.parseInt(s[1]);
x = Integer.parseInt(s[2]);
k = khp[k];
heap[k] = x;
down(k);
up(k);
}
}
pw.flush();
}
public static void down(int x){
int temp = x;
if(2 * x <= size && heap[2 * x] < heap[temp]){
temp = 2 * x;
}
if(2 * x + 1 <= size && heap[2 * x + 1] < heap[temp]){
temp = 2 * x + 1;
}
if(temp != x){
hp_swap(temp,x);
down(temp);
}
}
//结点上移,只需要跟父结点比较即可
public static void up(int x){
while ( x / 2 >= 1 && heap[x / 2] > heap[x]){
hp_swap(x,x/2);
x /= 2;
}
}
//因为hp数组可以对应为堆中的元素下标在khp数中的关系,所以我们要先交换khp,后交换hp或者先交换hp再交换khp也可以
public static void hp_swap(int a,int b){
int t = khp[hp[a]];
khp[hp[a]] = khp[hp[b]];
khp[hp[b]] = t;
t = hp[a];
hp[a] = hp[b];
hp[b] = t;
t = heap[a];
heap[a] = heap[b];
heap[b] = t;
}
public static int nextInt()throws Exception{
st.nextToken();
return (int)st.nval;
}
public static String nextLine()throws Exception{
return br.readLine();
}
}
2.读入数据:
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM
3.代码运行结果
-10
6
总结
上述解释了一下模拟堆中的一些基本操作知道题中各个数组的意思,比较长和难以理解,可以多看看梳理一下。