【数据结构与算法】堆与堆排序

堆与堆排序

1 堆的概念

  • 用于维护一个数集。
  • 堆是一个完全二叉树
  • 小根堆:每个结点都小于等于它的左右子结点(递归定义)
    • 推论:每个结点都是以其为根节点的子树的最小值
image-20230311092859309
堆是一棵完全二叉树

2 堆的性质

  • 完全二叉树的性质:
image-20230311100905346
完全二叉树的性质
条件 编号(或数量)
完全二叉树的层数 log2n
完全二叉树根节点向下走到叶子结点的最大距离 log2n
最后一个非叶子结点(向下走到叶子结点最大距离为 1 的最后一个结点) n/2
向下走到叶子结点最大距离为 2 的最后一个结点 n/4
向下走到叶子结点最大距离为 d 的最后一个结点 n/2d
向下走到叶子结点最大距离为 d 的结点个数 n/2dn/2d+1=n/2d+1
  • 小根堆的性质:由小根堆的定义可得每个结点都是以其为根节点的子树的最小值

这也是堆排序的思想,每一次将小根堆的根节点输出,然后维护剩下的节点作为一个小根堆。

3 堆的实现(小根堆为例)

  • 使用一维数组来存储堆
    • 1 号点为根结点。
    • 结点 x 的左儿子是 2x ,右儿子是 2x+1
  • 堆的基本操作:
    • down(x) 操作,将结点往下移动来维护一个小根堆
      1. 结点 i 与其左右孩子 2i2i+1 比较,如果结点 i 不是三者之中的最小值,则结点 i 要向下移动。
      2. 结点 i 与其最小的孩子 t 交换位置后,然后递归 down(t)
    • up(x) 操作,把一个结点向上移动来维护一个小根堆
      1. 结点 i 与它的父节点 i/2 比较,如果父结点大于子结点,就将结点 ii/2 交换
      2. i /= 2 直到父结点小于子结点
image-20230320145826417
down 操作
/// down 操作
void down(int h[], int size, int i)
{
    int t = i;
    if(2 * i <= size && h[i] > h[2 * i]) t = 2 * i;
    if(2 * i + 1 <= size && h[t] > h[2 * i + 1]) t = 2 * i + 1;
    if(t != i)
    {
        int temp = h[i];
        h[i] = h[t];
        h[t] = temp;
        down(h, size, t);
    }
}
image-20230320182911841
up操作
/// up 操作
void up(int h[], int size, int i)
{
    while(i / 2 && h[i / 2] > h[i])
    {
        int temp = h[i];
        h[i] = h[i / 2];
        h[i / 2] = temp;
        i /= 2;
    }
}
  • 使用基本操作 updown 就可以完成如下操作:
/// - 插入操作
/// 新增元素插在末尾,然后对他进行 up 操作
heap[++ size] = x;up(size);

/// - 最小值
/// 根节点即为最小值
heap[1];

/// - 删除最小值
/// 用堆的最后一个元素覆盖根节点,然后堆的尺寸减 1
/// 然后对根节点执行 down 操作
heap[1] = heap[size];size--;
down(1);

/// - 删除任意一个元素
/// 用堆的最后一个元素覆盖要删除的元素,然后堆的尺寸减 1
/// 然后对覆盖后的结点执行 down 操作
heap[k] = heap[size];size--;
down(k);up(k);

/// - 修改一个元素的值
/// 修改结点元素值后,对其执行 down 操作或 up 操作
/// 这里减少了一个判断,down() 和 up() 都写上,只会执行其中一个。
heap[k] = x;down(x);up(x);

4 堆排序

  • 推排序用于对一维数组的元素进行排序
  1. down 操作将一维数组转换为小根堆
    • down 只对非叶子结点操作才有意义,由完全二叉树的性质,最后一个非叶子结点的编号为 n / 2
    • 因此,从 i = n/2i = 0 的结点进行 down 操作,这样就能得到小根堆
for(int i = size / 2; i; i--) down(i);
  1. 输出最小值,然后删除最小值,循环。
while(size)
{
    printf("%d ", h[1]);
    h[1] = h[size]; size--;
    down(1);
}
  • 堆排序实现
void heap_sort(int h[], int size)
{
    for(int i = size / 2; i; i--) down(i);
    
    while(size)
	{
        printf("%d ", h[1]);
        h[1] = h[size]; size--;
        down(h, size, 1);
	}
}

C++ 使用 vector 实现小根堆模板类

#pragma once

#include <vector>
#include <stdexcept>
#include <string>
using namespace std;

template <class T>
class MinHeap
{
    /// @brief 用来存储堆的容器, 第一个位置不会使用
    vector<T> m_elems = vector<T>(1);
public:
    inline size_t size() const {
        return m_elems.size() - 1;
    }

    inline bool empty() const {
        return m_elems.size() <= 1;
    }

    inline void push(const T& elem) {
        // 1. 将新添加的元素放在堆的末尾
        m_elems.emplace_back(elem);
        // 2. 然后对末尾元素执行 up 操作
        up(m_elems.size() - 1);
    }

    inline T top() const {
        if (this->empty()) throw logic_error("The MinHeap is empty.");
        return m_elems[1];
    }

    void pop() {
        if (this->empty()) throw logic_error("The MinHeap is empty.");
        // 1. 用堆中最后一个元素覆盖根节点, 然后删除末尾的结点
        m_elems[1] = m_elems.back();
        m_elems.pop_back();
        // 2. 然后对根结点执行 down 操作
        down(1);
    }

    void erase(const int& index) {
        if (index >= m_elems.size()) throw out_of_range(to_string(index) + " is out of range! ");

        // 1. 用堆末尾的元素覆盖要删除的元素, 堆元素个数 -1
        m_elems[index] = m_elems.back();
        m_elems.pop_back();
        // 2. 然后对 index 位置的结点执行 down 操作
        down(index);
    }

private:
    void up(const int& index) {
        int tmp = index;
        // 当父结点比自己大时, 执行 up 操作
        while (tmp / 2 && m_elems[tmp / 2] > m_elems[tmp]) {
            swap(m_elems[tmp / 2], m_elems[tmp]);
            tmp /= 2;
        }
    }

    void down(const int& index) {
        int tmp = index, left = index * 2, right = index * 2 + 1;
        // 1. 求的 index, index * 2, inded * 2 + 1 三者中最小元素的下标
        if (left < m_elems.size() && m_elems[index] > m_elems[left])
            tmp = left;
        if (right < m_elems.size() && m_elems[tmp] > m_elems[right])
            tmp = right;
        // 2. 如果最小元素的下标与 index 不相等
        if (index != tmp) {
            // 交换 index 和 tmp 对应的值
            swap(m_elems[index], m_elems[tmp]);
            // 继续执行 down 操作
            down(tmp);
        }
    }
};

C++ 优先队列

  • 与一般的队列容器 queue First in,First out 不同, 优先队列 priority_queue 特点是 First in,Largest out.
  • priority_queue 其实就是堆数据结构, Largest 指的是元素的优先级最高, 通过指定元素优先级的比较规则, priority_queue 即可大根堆也可是小根堆, 还可存放非数值的其他元素.

语法

template <typename T,
        typename Container=std::vector<T>,
        typename Compare=std::less<T> >
class priority_queue{
    //......
}
  • typename T:指定存储元素的具体类型;

  • typename Container:指定 priority_queue 底层使用的基础容器,默认使用 vector 容器, 还可以是 queue

  • typename Compare : 指定容器中评定元素优先级所遵循的排序规则,默认使用std::less<T>按照元素值从大到小进行排序,还可以使用std::greater<T>按照元素值从小到大排序,但更多情况下是使用自定义的排序规则。

参考自: C++:std::greater()、std::less()、自定义比较函数的规则

对于 std::greater()、std::less() 排序和建堆的效果

  • 对于排序
    • less<T> 升序排列
    • greater<T> 降序排列
  • 对于建堆
    • less<T> 大根堆
    • greater<T> 小根堆

基本使用

#include <iostream>
#include <queue>
using namespace std;

int main() {
	vector<int> nums{ 3, 6, 2, 1, 12, 0, 4, 17, 2 };
	priority_queue<int> heap1;
	priority_queue<int, vector<int>, greater<int>> heap2(greater<int>(), nums);

	for (auto elem : nums) heap1.push(elem);


	cout << "默认为大根堆:" << endl;
	while (!heap1.empty()){
		cout << heap1.top() << " ";
		heap1.pop();
	}
	printf("\n\n");
	cout << "小根堆:" << endl;
	while (!heap2.empty()) {
		cout << heap2.top() << " ";
		heap2.pop();
	}
}
posted @   起司头_棕裤裤  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示