堆与堆排序

堆与堆排序

什么是堆?

堆的逻辑结构是一棵平衡二叉树,但是在使用过程中使用数组来进行存储。

我们还可以将堆分成两种类型,大根堆小根堆

大根堆是希望大的元素处于根部的位置,树中每层结点的值随着层数的增加而减小,而小根堆则相反。

在这里插入图片描述

堆的存储

使用一维数组来存储堆,根结点与左右孩子的下标关系:根结点 x ,左孩子 2x,右孩子 2x+1

对堆的两个基本的操作:down(向下调整)up(向上调整)

down:

void down(int x) {
     int t = x;
     if(2 * x <= size && heap[2 * x] < heap[t]) t = 2 * x;
     if(2 * x + 1 <= size && heap[2 * x + 1] < heap[t]) t = 2 * x + 1;
     if(t != x){
         int temp = heap[t];
         heap[t] = heap[x];
         heap[x] = temp;
         //递归处理
         down(t);
     }
}

up:

void up(int x ) {
    //跟它的父节点比较
    while(x / 2 != 0 && heap[x / 2] > heap[x]){
      //交换heap[x] heap[x / 2]
      swap(heap[x],heap[x / 2]);
      //继续向上
      x / = 2;
   } 
}

对堆的操作

heap[N] 表示用来存储堆的数组,size表示堆中所用元素的总和。

  1. 插入一个数 heap[++size] = x; up(size);
  2. 求集合当中的最小值 heap[1]
  3. 删除最小值 heap[1] = heap[size]; size--; down(1);
  4. 删除任意一个元素 heap[k] = heap[size]; size--; down(k); up(k);
  5. 修改任意一个元素 heap[k] = x; down(k); up(k);

堆排序

例题:

acwing 838. 堆排序

输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。

输入格式

第一行包含整数 n 和 m。

第二行包含 n 个整数,表示整数数列。

输出格式

共一行,包含 m 个整数,表示整数数列中前 m 小的数。

输入样例:

5 3
4 5 1 3 2

输出样例:

1 2 3

思路:

题目中希望我们将前m个小的数输出出来,那么就可以将堆中元素进行排序,也就是将堆变为小根堆,每次都输出根位置上的数,然后再删除根位置上的数同时再进行down操作。

在使用堆之前需要对堆进行初始化操作:从 n/2的位置开始,也就是二叉树的倒数第二层的位置开始不断的向上进行down操作,这是因为进行down操作时要保证左右儿子都已经是一个堆结构,插入元素时是随机插入的,所以不能从根结点开始down,而n/2处的节点每个都是叶子结点,满足堆结构,所以从n/2的位置开始down

for (int i = n / 2; i != 0; i --) down(i);

题解:

import java.util.Scanner;

public class Main {
    static final int N = 100010;
    static int[] h = new int[N];
    static int size;
    
    private static void down(int u) {
        int t = u;
        if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
        if (u * 2  + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
        if (t != u) {
            int tmp = h[u];
            h[u] = h[t];
            h[t] = tmp;
            down(t);
        }
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(), m = sc.nextInt();
        for (int i = 1; i <= n; i++) 
            h[i] = sc.nextInt();
        size = n;
        
        // 构建堆
        for (int i = n / 2; i != 0; i --) down(i);
        
        while (m -- != 0) {
            System.out.print(h[1] + " ");
            h[1] = h[size--];
            down(1);
        }
    }
}

模拟堆

维护一个集合,初始时集合为空,支持如下几种操作:

  1. I x,插入一个数 x x x
  2. PM,输出当前集合中的最小值;
  3. DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
  4. D k,删除第 k k k 个插入的数;
  5. C k x,修改第 k k k 个插入的数,将其变为 x x x

现在要进行 N N N 次操作,对于所有第 2 2 2 个操作,输出当前集合的最小值。

输入格式

第一行包含整数 N N N

接下来 N N N 行,每行包含一个操作指令,操作指令为 I xPMDMD kC k x 中的一种。

输出格式

对于每个输出指令 PM,输出一个结果,表示当前集合中的最小值。

每个结果占一行。

数据范围

1 ≤ N ≤ 105 1≤N≤105 1N105
− 109 ≤ x ≤ 109 −109≤x≤109 109x109
数据保证合法。

输入样例:

8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

输出样例:

-10
6

代码实现:

import java.util.Scanner;

public class Main {
    static final int N = 100010;
    static int[] h = new int[N];
    static int[] ph = new int[N];  //存放第k个插入点的下标
    static int[] hp = new int[N];  //存放堆中点的插入次序
    static int size;
    
    public static void down(int x) {
        int t = x;
        if (x * 2 <= size && h[t] > h[x * 2]) t = x * 2;
        if (x * 2 + 1 <= size && h[t] > h[x * 2 + 1]) t = x * 2 + 1;
        if (t != x) {
            heapSwap(t, x);
            down(t);
        }
    }

    public static void up(int x) {
        while (x / 2 > 0 && h[x] < h[x / 2]) {
            heapSwap(x , x / 2);
            x /= 2;
        }
    }      
    
    public static void heapSwap(int u, int v){
        swap(h, u, v);
        swap(hp, u, v);
        swap(ph, hp[u], hp[v]);
    }
    
    public static void swap(int[] a, int u, int v){
        int tmp = a[u];
        a[u] = a[v];
        a[v] = tmp;
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = 0;
        while (n-- > 0) {
            String s = sc.next();
            if ("I".equals(s)) {
                int x = sc.nextInt();
                h[++size] = x;
                ph[++m] = size;
                hp[size] = m;
                up(size);
            } else if ("PM".equals(s)) {
                System.out.println(h[1]);
            } else if ("DM".equals(s)) {
                heapSwap(1, size);
                size--;
                down(1);
            } else if ("D".equals(s)) {
                int k = sc.nextInt();
                int u = ph[k];
                heapSwap(u, size--);
                up(u);
                down(u);
            } else {
                int k = sc.nextInt(), x = sc.nextInt();
                h[ph[k]] = x;
                down(ph[k]);
                up(ph[k]);
            }
        }        
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神烦狗闯入了你的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值