优先级队列

130 阅读6分钟

大(小)根树其父节点大(小)于其子节点

image.png

大(小)根树如果是 完全二叉树 , 则称为

插入

以下都是大根堆

欲插入元素上浮至合适位置

image.png

下标从 1 开始

template<class T>
void MaxHeap<T>::insert(const T& element) {
    // 必要时数组长度增倍
    if (curSize == maxSize) reSize();

    // 寻找插入位置
    int curNode = ++curSize;
    while (curNode != 1 && (*heap)[curNode / 2] < element) {
        // 如果元素比 heap[curNode / 2] 大
        // 则 heap[curNode / 2] 变成子元素
        (*heap)[curNode] = (*heap)[curNode / 2];
        curNode /= 2;
    }

    // 最终元素被放在正确的位置上
    (*heap)[curNode] = element;
}

删除

直接删除 heap[1] 可能变成非大根堆

image.png

因此需要在删除的同时重构子树

image.png

template<class T>
void MaxHeap<T>::erase() {
    if (curSize == 0) {
        cout << "堆空";
        return;
    }

    T lastElement = (*heap)[curSize--];
    int curNode = 1, child = 2;
    while (child <= curSize) {
        // 在左右孩子中找一个最大的
        if (child < curSize && (*heap)[child] < (*heap)[child + 1]) {
            child++;
        }

        // 最后一个元素比左右孩子都大
        // 不用比了, 直接放在 heap[1]
        if (lastElement >= (*heap)[child]) break;

        // 让最大的子节点做堆顶元素
        (*heap)[curNode] = (*heap)[child];
        
        // 继续重排子节点树
        curNode = child;
        child *= 2;
    }

    (*heap)[curNode] = lastElement;
    // 注意这里只是覆盖, 并没有删除
    // 也就是 lastElement 还在原数组里
}

初始化堆

即将一个非空数组变成大根堆, 具体参考: 堆排序

template<class T>
void MaxHeap<T>::initialize(const vector<T>& array) {
    delete heap;
    curSize = array.size();
    maxSize = curSize * 2;
    heap = new vector<T>(maxSize + 1);

    // 拷贝节点
    for (int i = 1; i <= curSize; i++) {
        // 这里 array 是从下标 0 开始的
        (*heap)[i] = array[i - 1];
    }

    // 从中间开始
    for (int root = curSize / 2; root > 0; root--) {
        T rootElement = (*heap)[root];

        // 第一个子节点
        int child = 2 * root;
        while(child <= curSize) {
            // 查找最大子节点
            if (child < curSize && (*heap)[child] < (*heap)[child + 1]) {
                child++;
            }

            // 如果根节点大于子节点, 直接退出 while 循环
            if (rootElement >= (*heap)[child]) break;

            // 根节点小于子节点
            // 将根节点用子节点代替
            (*heap)[child / 2] = (*heap)[child];

            // 继续遍历子节点的子节点
            child *= 2;
        }

        (*heap)[child / 2] = rootElement;
    }
}
☃️ 完整代码
#include<iostream>
#include<vector>
using namespace std;

template<class T>
class MaxHeap {
public:
    MaxHeap(int maxHeapSize = 10);
    ~MaxHeap() { delete heap; }
    void insert(const T& element); // 插入
    void erase(); // 删除
    void initialize(const vector<T>& array); // 初始化堆
    void reSize(); // 数组长度改变
    void output() {
        for (int i = 1; i <= curSize; i++) {
            cout << (*heap)[i] << " ";
        }
        cout << "\n";
    }
private:
    int curSize, // 当前元素个数
        maxSize; // 数组最大长度
    vector<T>* heap;  // 数组
};

template<class T>
MaxHeap<T>::MaxHeap(int maxHeapSize):maxSize(maxHeapSize) {
    // 下标从 1 开始, 因此多一个元素
    heap = new vector<T>(maxHeapSize + 1);
    curSize = 0;
}

// 把 element 加入堆
template<class T>
void MaxHeap<T>::insert(const T& element) {
    // 必要时数组长度增倍
    if (curSize == maxSize) reSize();

    // 寻找插入位置
    int curNode = ++curSize;
    while (curNode != 1 && (* heap)[curNode / 2] < element) {
        // 如果元素比 heap[curNode / 2] 大
        // 则 heap[curNode / 2] 变成子元素
        (*heap)[curNode] = (*heap)[curNode / 2];
        curNode /= 2;
    }

    // 最终元素被放在正确的位置上
    (*heap)[curNode] = element;
}

// 删除堆顶(最大)元素
template<class T>
void MaxHeap<T>::erase() {
    if (curSize == 0) {
        cout << "堆空";
        return;
    }

    T lastElement = (*heap)[curSize--];
    int curNode = 1, child = 2;
    while (child <= curSize) {
        // 在左右孩子中找一个最大的
        if (child < curSize && (*heap)[child] < (*heap)[child + 1]) {
            child++;
        }

        // 最后一个元素比左右孩子都大
        // 不用比了, 直接放在 heap[1]
        if (lastElement >= (*heap)[child]) break;

        // 让最大的子节点做堆顶元素
        (*heap)[curNode] = (*heap)[child];
        
        // 继续重排子节点树
        curNode = child;
        child *= 2;
    }

    (*heap)[curNode] = lastElement;
    // 注意这里只是覆盖, 并没有删除
    // 也就是 lastElement 还在原数组里
}

// 数组转成堆
template<class T>
void MaxHeap<T>::initialize(const vector<T>& array) {
    delete heap;
    curSize = array.size();
    maxSize = curSize * 2;
    heap = new vector<T>(maxSize + 1);

    // 拷贝节点
    for (int i = 1; i <= curSize; i++) {
        // 这里 array 是从下标 0 开始的
        (*heap)[i] = array[i - 1];
    }

    // 从中间开始
    for (int root = curSize / 2; root > 0; root--) {
        T rootElement = (*heap)[root];

        // 第一个子节点
        int child = 2 * root;
        while(child <= curSize) {
            // 查找最大子节点
            if (child < curSize && (*heap)[child] < (*heap)[child + 1]) {
                child++;
            }

            // 如果根节点大于子节点, 直接退出 while 循环
            if (rootElement >= (*heap)[child]) break;

            // 根节点小于子节点
            // 将根节点用子节点代替
            (*heap)[child / 2] = (*heap)[child];

            // 继续遍历子节点的子节点
            child *= 2;
        }

        (*heap)[child / 2] = rootElement;
    }
}

// 重新设置堆大小
template<class T>
void MaxHeap<T>::reSize() {
    maxSize *= 2;
    vector<T>* temp = new vector<T>(maxSize + 1);
    for (int i = 1; i <= curSize; i++) {
        (*temp)[i] = (*heap)[i];
    }
    delete heap;
    heap = temp;
}

int main() {
    vector<int> array = { 49,38,65,97,76,13,27 };
    MaxHeap<int> h(2);

    // 插入的方式建立堆
    for (int i = 0; i < array.size(); i++) {
       h.insert(array[i]);
    }
    h.output();
    
    // 以数组的形式建立堆
    h.initialize(array);
    h.output();

    return 0;
}

小根堆

与大根堆类似, 把 > 改成 < 即可

☃️ 完整代码
// 小根堆
template<class T>
class MinHeap {
public:
    MinHeap(int maxHeapSize = 10);
    ~MinHeap() { delete heap; }
    void insert(const T& element); // 插入
    void erase(); // 删除
    void initialize(const vector<T>& array); // 初始化堆
    void reSize(); // 数组长度改变
    void output() {
        for (int i = 1; i <= curSize; i++) {
            cout << (*heap)[i] << " ";
        }
        cout << "\n";
    }
    // 返回堆顶元素
    T& top() { return (*heap)[1]; }
private:
    int curSize, // 当前元素个数
        maxSize; // 数组最大长度
    vector<T>* heap;  // 数组
};

template<class T>
MinHeap<T>::MinHeap(int maxHeapSize) :maxSize(maxHeapSize) {
    // 下标从 1 开始, 因此多一个元素
    heap = new vector<T>(maxHeapSize + 1);
    curSize = 0;
}


template<class T>
void MinHeap<T>::insert(const T& element) {
    // 必要时数组长度增倍
    if (curSize == maxSize) reSize();

    // 寻找插入位置
    int curNode = ++curSize;
    while (curNode != 1 && (*heap)[curNode / 2] > element) {
        // 如果元素比 heap[curNode / 2] 小
        // 则 heap[curNode / 2] 变成子元素
        (*heap)[curNode] = (*heap)[curNode / 2];
        curNode /= 2;
    }

    // 最终元素被放在正确的位置上
    (*heap)[curNode] = element;
}


template<class T>
void MinHeap<T>::erase() {
    if (curSize == 0) {
        cout << "堆空";
        return;
    }

    T lastElement = (*heap)[curSize--];
    int curNode = 1, child = 2;
    while (child <= curSize) {
        // 在左右孩子中找一个最小的
        if (child < curSize && (*heap)[child] >(*heap)[child + 1]) {
            child++;
        }

        // 最后一个元素比左右孩子都小
        // 不用比了, 直接放在 heap[1]
        if (lastElement <= (*heap)[child]) break;

        // 让最小的子节点做堆顶元素
        (*heap)[curNode] = (*heap)[child];

        // 继续重排子节点树
        curNode = child;
        child *= 2;
    }

    (*heap)[curNode] = lastElement;
}

// 数组转成堆
template<class T>
void MinHeap<T>::initialize(const vector<T>& array) {
    delete heap;
    curSize = array.size();
    maxSize = curSize * 2;
    heap = new vector<T>(maxSize + 1);

    // 拷贝节点
    for (int i = 1; i <= curSize; i++) {
        // 这里 array 是从下标 0 开始的
        (*heap)[i] = array[i - 1];
    }

    // 从中间开始
    for (int root = curSize / 2; root > 0; root--) {
        T rootElement = (*heap)[root];

        // 第一个子节点
        int child = 2 * root;
        while (child <= curSize) {
            // 查找最小子节点
            if (child < curSize && (*heap)[child] >(*heap)[child + 1]) {
                child++;
            }

            // 如果根节点小于子节点, 直接退出 while 循环
            if (rootElement <= (*heap)[child]) break;

            // 根节点大于子节点
            // 将根节点用子节点代替
            (*heap)[child / 2] = (*heap)[child];

            // 继续遍历子节点的子节点
            child *= 2;
        }

        (*heap)[child / 2] = rootElement;
    }
}

// 重新设置堆大小
template<class T>
void MinHeap<T>::reSize() {
    maxSize *= 2;
    vector<T>* temp = new vector<T>(maxSize + 1);
    for (int i = 1; i <= curSize; i++) {
        (*temp)[i] = (*heap)[i];
    }
    delete heap;
    heap = temp;
}

霍夫曼编码

一篇文章由 a z u x 组成, 共 1000 个字符, 若一个字符占一个字节, 则共需要 1000 字节( 8000 位)

如果用 2 位二进制编码, a=00 z=01 u=10 x=11 则仅需 2000 位即可表示 1000 个字符

加上编码表(符号个数 8 位, 符号 4×8 位, 编码 4×2 位)

因此压缩率为 20488000=3.9\frac{2048}{8000}=3.9

可变长度编码

aaxuaxz 按上述规则可以编码成 00 00 01 10 00 01 11

如果各字符出现的频率不一样, 比如 aaxuaxza x u z 出现的频率分别为 3 2 1 1

我们可以使用 a=0 x=10 u=110 z=111 来编码, 结果为 0 0 10 110 0 10 111 这样编码就少了一位

注意不能使用编码 a=0 x=01 u=10 z=11, 因为这样 11010 无法区分是 11 0 10 还是 11 01 0

构造霍夫曼树

图解

image.png

以权重 49 38 65 97 76 13 27 为例, 最终生成的霍夫曼树为:

image.png

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

// 忽略小根堆代码...

// 哈夫曼树节点
template<class T>
class HuffmanNode {
    template<class T>
    friend void createHuffmanTree(vector<HuffmanNode<T>>&, HuffmanNode<T>&);
    // 中序遍历
    template<class T>
    friend void preOrder(HuffmanNode<T>* t) {
        if (t != nullptr) {
            cout << t->weight << " ";

            preOrder(t->left);
            preOrder(t->right);
        }
    }
    // 便于输出小根树
    friend ostream& operator << (ostream& out, const HuffmanNode<T>& m) {
        out << m.weight;
        return out;
    };
public:
    HuffmanNode() { left = right = nullptr; weight = 0; };
    HuffmanNode(int weight): weight(weight) {
        left = right = nullptr;
    };
    HuffmanNode(int weight, HuffmanNode<T>* left, HuffmanNode<T>* right) {
        this->weight = weight;
        this->left = left;
        this->right = right;
    };
    operator T () const { return weight; };
    // 必须重载操作符, 否则 MinHeap 不能使用
    bool operator <(const HuffmanNode<T>& v) {
        return weight < v.weight;
    };
    bool operator <=(const HuffmanNode<T>& v) {
        return weight <= v.weight;
    };
    bool operator >(const HuffmanNode<T>& v) {
        return weight > v.weight;
    };
    bool operator >=(const HuffmanNode<T>& v) {
        return weight >= v.weight;
    };
    T Weight() { return weight; };
private:
    HuffmanNode<T>* left, *right;
    T weight;
};

// 构造霍夫曼树
template<class T>
void createHuffmanTree(vector<HuffmanNode<T>>& hNode, HuffmanNode<T>& root) {
    int n = hNode.size();

    MinHeap<HuffmanNode<T>> heap;
    // 使霍夫曼树节点构成小根树
    heap.initialize(hNode);
    
    HuffmanNode<T>* x, * y;
    for (int i = 1; i < n; i++) {
        // 从小根树中取出两个最小的树
        x = new HuffmanNode<T>(heap.top(), heap.top().left, heap.top().right);
        heap.erase();

        y = new HuffmanNode<T>(heap.top(), heap.top().left, heap.top().right);
        heap.erase();

        HuffmanNode<T> w(y->weight + x->weight, x, y);
        heap.insert(w);
    }

    root = heap.top();
}


int main() {
    // 权
    vector<int> weight = { 49,38,65,97,76,13,27 };

    // 用权 weight 生成霍夫曼节点数组
    vector<HuffmanNode<int>> hNode;
    for (int i = 0; i < weight.size(); i++) {
        hNode.push_back(HuffmanNode<int>(weight[i]));
    }

    // 霍夫曼树根节点
    HuffmanNode<int> root;
    createHuffmanTree(hNode, root);

    // 前序遍历
    preOrder(&root);

    return 0;
}

判断大根堆还是小根堆

下标从 0 开始

完整代码
#include<iostream>
#include<vector>
using namespace std;

// 判断是否是大根堆
bool isMaxHeap(const vector<int>& array) {
    int len = array.size();

    for (int i = len / 2 - 1; i > 0; i--) {
        int parentElement = array[i];

        int child = 2 * i + 1;
        // 查找最大子节点
        if (child < len - 1 && array[child] < array[child + 1]) {
            child++;
        }

        // 如果父节点小于子节点, 不是大根堆
        if (parentElement < array[child]) return false;
    }

    return true;
}

// 判断是否是小根堆
bool isMinHeap(const vector<int>& array) {
    int len = array.size();

    for (int i = len / 2 - 1; i > 0; i--) {
        int parentElement = array[i];

        int child = 2 * i + 1;
        // 查找最小子节点
        if (child < len - 1 && array[child] > array[child + 1]) {
            child++;
        }

        // 如果父节点大于子节点, 不是小根堆
        if (parentElement > array[child]) return false;
    }

    return true;
}

// 转成大根堆
void toMaxHeap(vector<int>& array) {
    int len = array.size();

    for (int i = len / 2 - 1; i >= 0; i--) {
        int parentElement = array[i];

        int child = 2 * i + 1;

        while (child < len) {
            // 节点相等时选择右沉
            if (child < len - 1 && array[child] <= array[child + 1]) {
                child++;
            }

            // 父节点大于等于子节点, 不需要右沉
            if (parentElement >= array[child]) break;

            // 子节点上浮
            array[(child - 1) / 2] = array[child];
            
            // 继续判断子节点的子节点
            child = child * 2 + 1;
        }

        array[(child - 1) / 2] = parentElement;
    }
}

// 转成小根堆
void toMinHeap(vector<int>& array) {
    int len = array.size();

    for (int i = len / 2 - 1; i >= 0; i--) {
        int parentElement = array[i];

        int child = 2 * i + 1;

        while (child < len) {
            // 节点相等时选择右沉
            if (child < len - 1 && array[child] >= array[child + 1]) {
                child++;
            }

            // 父节点小于等于子节点, 不需要右沉
            if (parentElement <= array[child]) break;

            // 子节点上浮
            array[(child - 1) / 2] = array[child];

            // 继续判断子节点的子节点
            child = child * 2 + 1;
        }

        array[(child - 1) / 2] = parentElement;
    }
}

// 输出堆
void output(vector<int>& array) {
    for (int i = 0; i < array.size() - 1; i++) {
        cout << array[i] << " ";
    }
    cout << array[array.size() - 1];
}

int main() {
    int num;
    cin >> num;

    vector<int> array(num);
    int temp;
    for (int i = 0; i < num; i++) {
        cin >> temp;
        array[i] = temp;
    }

    bool isMax = isMaxHeap(array);
    bool isMin = isMinHeap(array);

    if (isMax && isMin) {
        cout << "max min ";
    }
    else if (isMax) {
        cout << "max ";
        toMinHeap(array); // 转成小根堆
        output(array);
    }
    else if(isMin) {
        cout << "min ";
        toMaxHeap(array); // 转成大根堆
        output(array);
    }
    else {
        // 什么都不是的, 转大根堆
        toMaxHeap(array); // 转成大根堆
        output(array);
    }
}