堆
大(小)根树其父节点大(小)于其子节点
大(小)根树如果是 完全二叉树 , 则称为 堆
插入
以下都是大根堆
欲插入元素上浮至合适位置
下标从 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] 可能变成非大根堆
因此需要在删除的同时重构子树
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 位)
因此压缩率为
可变长度编码
aaxuaxz 按上述规则可以编码成 00 00 01 10 00 01 11
如果各字符出现的频率不一样, 比如 aaxuaxz 中 a x u z 出现的频率分别为 3 2 1 1
我们可以使用 a=0 x=10 u=110 z=111 来编码, 结果为 0 0 10 110 0 10 111 这样编码就少了一位
注意不能使用编码
a=0x=01u=10z=11, 因为这样11010无法区分是11010还是11010了
构造霍夫曼树
以权重 49 38 65 97 76 13 27 为例, 最终生成的霍夫曼树为:
#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);
}
}