《Algorithm》笔记:堆与优先队列

本文详细介绍了堆的定义、实现及算法,并基于此讲解了最大优先队列和索引优先队列的设计与实现,包括它们的时间复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址:http://brianleelxt.top/2018/08/13/heapandPQ/


《Algorithm》(Sedgewick)笔记:堆与优先队列




定义

  • 堆的每个结点都大于等于两个子结点,这称为堆有序
  • 根结点是堆有序的二叉树中的最大结点
  • 一棵大小为 NNN 的完全二叉树的高度为 ⌊lgN⌋+1\lfloor lgN\rfloor+1lgN+1

实现

用数组pq[1…N]实现(不使用pq[0]),位置 kkk 的结点的父节点位置为 ⌊k/2⌋\lfloor k/2\rfloork/2 ,两个子结点位置分别为 2k2k2k2k+12k+12k+1


图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jGgFdALL-1592197125331)(http://p5mjtapdi.bkt.clouddn.com/img/sort/heap1.PNG “堆有序完全二叉树”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3rdwoOHy-1592197125333)(http://p5mjtapdi.bkt.clouddn.com/img/sort/heap2.PNG “堆表示”)]


堆的算法

上浮
作用
  • 如果堆的有序状态因为某个结点变得比它父结点更大而打破,那么我们需要通过交换它和它的父节点来修复堆
  • 交换后继续比较,直到进入堆有序状态
代码
private void swim(int k) {
        while (k > 1 && less(k / 2, k)) {
            exch(k / 2, k);
            k = k / 2;
        }
    }
图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-82OTVkRq-1592197125334)(http://p5mjtapdi.bkt.clouddn.com/img/sort/heap3.PNG “上浮”)]

下沉
作用
  • 如果堆的有序状态因为某个结点变得比它的两个子结点或是其中之一更小了而被打破了,那么我们可以通过将它和它的两个子结点中的较大者交换。
  • 一直比较与交换,直到堆有序
代码
private void sink(int k) {
        while (2 * k <= N) {
            int j = 2 * k;
            if (j < N && less(j, j + 1))
                j++;
            if (!less(k, j))
                break;
            exch(k, j);
            k = j;
        }
    }
图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QjFls7ks-1592197125335)(http://p5mjtapdi.bkt.clouddn.com/img/sort/heap4.PNG “下沉”)]

插入元素
作用
  • 将新元素加到数组末尾
  • 增加堆的大小
  • 让这个新元素上浮到合适的位置
代码
public void insert(Key v) {
        pq[++N] = v;    //先将新元素添加在堆末尾
        swim(N);        //再上浮到合适位置
    }
图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hx1cCDAs-1592197125336)(http://p5mjtapdi.bkt.clouddn.com/img/sort/heap5.PNG “插入”)]

删除最大元素
作用
  • 从数组顶端删去最大的元素
  • 将数组的最后一个元素放到顶端
  • 减小堆的大小
  • 让这个元素下沉到合适的位置
代码
public Key delMax() {
        Key max = pq[1];    //从根节点得到最大元素
        exch(1, N--);       //将其和最后一个结点交换
        pq[N + 1] = null;   //防止对象游离
        sink(1);            //恢复堆的有序性
        return max;
    }
图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7DB0Hepf-1592197125338)(http://p5mjtapdi.bkt.clouddn.com/img/sort/heap6.PNG “删除最大元素”)]



优先队列


定义

  • 可以删除最大元素(或最小)和插入元素的数据结构
  • 分为 最大优先队列最小优先队列

API

以最大优先队列为例

public class MaxPQ<Key extends Comparable<Key>>

MaxPQ()创建一个优先队列
MaxPQ(int max)创建一个最大容量为max的优先队列
MaxPQ(Key[] a)用a[]中的元素创建一个优先队列
void Insert(Key v)向优先队列中插入一个元素
Key max()返回最大元素
Key delMax()删除并返回最大元素
boolean isEmpty()返回队列是否为空
int size()返回优先队列中的元素个数

实现

可以使用无序数组与有序数组实现,但是时间复杂度不如使用 合理

数据结构插入元素删除最大元素
有序数组NNN111
无序数组111NNN
logNlogNlogNlogNlogNlogN

代码

public class MaxPQ<Key extends Comparable<Key>> {
    private Key[] pq;   //基于堆的完全二叉树
    private int N = 0;  //存储于pq[1..N],pq[0]不使用

    public MaxPQ(int maxN) {
        pq = (Key[]) new Comparable[maxN + 1];
    }
    public boolean isEmpty() {
        return N == 0;
    }
    public int size() {
        return N;
    }
    public void insert(Key v) {
        pq[++N] = v;    //先将新元素添加在堆末尾
        swim(N);        //再上浮到合适位置
    }
    public Key delMax() {
        Key max = pq[1];    //从根节点得到最大元素
        exch(1, N--);       //将其和最后一个结点交换
        pq[N + 1] = null;   //防止对象游离
        sink(1);            //恢复堆的有序性
        return max;
    }
    public void show() {
        for (int i = 1; i <= N; i++) {
            System.out.printf(pq[i] + " ");
        }
        System.out.println();
    }
    private boolean less(int i, int j) {
        return pq[i].compareTo(pq[j]) < 0;
    }
    private void exch(int i, int j) {
        Key t = pq[i];
        pq[i] = pq[j];
        pq[j] = t;
    }
    private void swim(int k) {
        while (k > 1 && less(k / 2, k)) {
            exch(k / 2, k);
            k = k / 2;
        }
    }
    private void sink(int k) {
        while (2 * k <= N) {
            int j = 2 * k;
            if (j < N && less(j, j + 1))
                j++;
            if (!less(k, j))
                break;
            exch(k, j);
            k = j;
        }
    }
}

时间复杂度

插入元素

logNlogNlogN

删除最大元素

logNlogNlogN

理由

两种操作都需要在根结点和堆底之间移动元素,而路径长度不超过 logNlogNlogN


优先队列应用

描述

输入 NNN 个数字,找出其中最小的 MMM 个数字

思路

MMM 个数字输入时构成大小为 MMM 的基于堆的 最大 优先队列,之后的数字每来一个,将其插入优先队列,调整为堆有序后删除最大的元素。这样到最后剩下的就是最小的 MMM 个元素。

复杂度

时间复杂度O(MlogN)O(MlogN)O(MlogN)

空间复杂度O(M)O(M)O(M)

代码
public class BottomM {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        System.out.println("Find the smallest M numbers in N numbers");
        System.out.println("Input M");
        int M = in.nextInt();
        System.out.println("Input N");
        int N = in.nextInt();
        MaxPQ<Integer> pq = new MaxPQ<Integer>(M + 1);  //多一个用于重排和删除
        for (int i = 0; i < N; i++) {
            Integer num = in.nextInt();
            pq.insert(num);
            //pq.show();
            if (pq.size() > M)
                pq.delMax();
        }
        Stack<Integer> stack = new Stack<Integer>();
        while (!pq.isEmpty())
            stack.push(pq.delMax());    //从大到小入栈
        for (int i = 0; i < M; i++) {
            System.out.println(stack.pop());    //从小到大出栈
        }
    }
}

源代码

https://github.com/XutongLi/Algorithm-Learn/tree/master/src/S2_Sorting/S2_4_4_2_MaxPriorityQueue


索引优先队列

在此以 最小索引优先队列 为例

定义
  • 给每个元素一个索引
  • 允许用例引用已经进入优先队列中的元素
API

public class IndexMinPQ<Item extends Comparable<Item>>

IndexMinPQ(int maxN)创建一个最大容量为maxN的优先队列,索引的取值范围为0至maxN-1
void insert(int k, Item item)插入一个元素,将它和索引k相关联
void change(int k, Item item)将索引为k的元素设为item
boolean contains(int k)是否存在索引为k的元素
void delete(int k)删去索引k及其相关联的元素
Item min()返回最小元素
int minIndex()返回最小元素的索引
int delMin()删除最小元素并返回它的索引
boolean isEmpty()优先队列是否为空
int size()优先队列中的元素数量
代码
public class IndexMinPQ<Key extends Comparable<Key>> {
    private int N = 0;      //PQ中的元素数量
    private int[] pq;   //索引二叉堆,从1开始(pq[顺序号]=索引号)
    private int[] qp;   //逆序:qp[pq[i]]=pq[qp[i]]=i(qp[索引号]=顺序号)
    private Key[] keys; //有优先级之分的元素

    public IndexMinPQ(int maxN) {
        keys = (Key[]) new Comparable[maxN + 1];
        pq = new int[maxN + 1];
        qp = new int[maxN + 1];
        for (int i = 0; i <= maxN; i++)
            qp[i] = -1;
    }
    public boolean isEmpty() {
        return N == 0;
    }
    public int size() {
        return N;
    }
    public boolean contains(int k) {
        return qp[k] != -1;
    }
    public void show() {
        for (int i = 1; i <= N; i++) {
            System.out.printf(keys[pq[i]] + " ");
        }
        System.out.println();
    }
    public void insert(int k, Key key) {
        N++;
        qp[k] = N;
        pq[N] = k;
        keys[k] = key;
        swim(N);
    }
    public Key min() {
        return keys[pq[1]];
    }
    public int delMin() {
        int idxOfMin = pq[1];
        exch(1, N--);
        sink(1);
        keys[pq[N + 1]] = null;
        qp[pq[N + 1]] = -1;
        return idxOfMin;
    }
    public int minIndex() {
        return pq[1];
    }
    public void change(int k, Key key) {
        keys[k] = key;
        swim(qp[k]);
        sink(qp[k]);
    }
    public void delete(int k) {
        exch(k, N--);
        swim(qp[k]);
        sink(qp[k]);
        keys[pq[N + 1]] = null;
        qp[pq[N + 1]] = -1;
    }
    private boolean less(int i, int j) {
        return keys[pq[i]].compareTo(keys[pq[j]]) < 0;
    }
    private void exch(int i, int j) {
        Key t = keys[pq[i]];
        keys[pq[i]] = keys[pq[j]];
        keys[pq[j]] = t;
    }
    private void swim(int k) {
        while (k > 1 && less(k, k / 2)) {
            exch(k / 2, k);
            k = k / 2;
        }
    }
    private void sink(int k) {
        while (2 * k <= N) {
            int j = 2 * k;
            if (j < N && less(j + 1, j))
                j++;
            if (less(k, j))
                break;
            exch(k, j);
            k = j;
        }
    }
}
时间复杂度
操作比较次数的增长数量级
insert()logNlogNlogN
change()logNlog NlogN
contains()111
delete()logNlogNlogN
min()111
minIndex()111
delMin()logNlogNlogN
源代码

https://github.com/XutongLi/Algorithm-Learn/tree/master/src/S2_Sorting/S2_4_4_6_IndexMinPQ


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值