堆,堆排序
堆是一棵特殊的完全二叉树,每个节点总是大于等于或小于等于其孩子节点,是最高效的优先级队列。
大跟堆每个节点均大于等于其孩子节点的堆,根节点为最大值,小根堆同理。
堆排序的原理则是先将数据建成堆,每次取出堆顶元素按顺序放置,即可完成排序。
堆的实现
堆类的声明:
typedef int HPDatatype;
class heap { //以大根堆为例
private:
HPDatatype* hpData;
int _size;
int capacity;
void expand(); //扩容,默认扩容至当前2倍
public:
heap(int k = 0); //构造函数
~heap(); //析构函数
HPDatatype top(); //获取堆顶
void push(HPDatatype x); //推入数据
void pop(); //取出堆顶
bool empty(); //判断堆是否为空
int size(); //获取堆的大小
static void AdjustUp(int * _data, int x); //向上调整
static void AdjustDown(int* _data, int x, int n); //向下调整
};
向上向下调整定义为静态函数,堆排序时可直接调用
构造函数:
heap::heap(int k) { //k值仅为预先开辟堆空间,堆中数据量为0
if (!k) k = 1;
_size = 0;
capacity = k;
hpData = (HPDatatype*)malloc(k * sizeof HPDatatype);
if (hpData == nullptr) {
perror("malloc fail");
return;
}
}
析构函数:
heap::~heap() {
assert(hpData);
free(hpData);
}
养成好习惯,避免内存泄露!
插入函数
堆的插入操作为每次现将要插入数据添加到数组末尾,再从该位置向上调整保证堆的性质,最多调整次数为树的高度即 l o g n logn logn,所以插入操作的时间复杂度为 O ( l o g n ) O(logn) O(logn)。
void heap::push(HPDatatype x) {
assert(hpData);
if (_size == capacity)
expand();
hpData[_size] = x;
AdjustUp(hpData, _size++);
}
删除函数
堆的删除操作可以直接将堆顶元素放到数组最后一个元素交换,再从堆顶开始向下调整,与插入操作类似,时间复杂度为 O ( l o g n ) O(logn) O(logn)。
void heap::pop() {
assert(hpData);
assert(!empty());
_size--;
swap(hpData[0], hpData[_size]);
AdjustDown(hpData, 0, _size);
}
上下调整函数
void heap::AdjustUp(int * _data, int x) {
assert(_data);
while (x > 0 && _data[(x - 1) >> 1] < _data[x]) {
swap(_data[(x - 1) >> 1], _data[x]);
x = (x - 1) >> 1;
}
}
void heap::AdjustDown(int* _data, int x, int n) {
assert(_data);
int child = (x << 1) + 1;
while (child < n) {
if (child + 1 < n && _data[child] < _data[child + 1])
child++;
if (_data[x] < _data[child])
swap(_data[x], _data[child]);
else
break;
x = child;
child = (x << 1) + 1;
}
}
其他函数
HPDatatype heap::top() {
assert(hpData);
if (empty())
return -1;
return hpData[0];
}
int heap::size() {
assert(hpData);
return _size;
}
bool heap::empty() {
assert(hpData);
return !_size;
}
void heap::expand() { //仅内部扩容时使用
assert(hpData);
HPDatatype* rlc = (HPDatatype*)realloc(hpData, sizeof HPDatatype * capacity * 2);
if (rlc == nullptr) {
perror("realloc fail");
return;
}
hpData = rlc;
capacity *= 2;
}
堆排序
以排升序为例,先直接在原数据空间中向上调整建堆,有两种建堆方法,
1. 从前往后依次插入元素(push操作),时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
2. 从后往前依次向下调整,时间复杂度 O ( n ) O(n) O(n)。
建堆完成后依次取出堆顶元素并从堆顶向下调整,所以总体时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
void HeapSort(int* st, int* ed) {
int n = ed - st;
for (int i = (n - 2) >> 1; i >= 0; i--) {
heap::AdjustDown(st, i, n);
}
for (int i = n - 1; i > 0; i--) {
swap(st[0], st[i]);
heap::AdjustDown(st, 0, i);
}
}
总结
综上所述,堆的各种操作的时间复杂度都不超过O (nlogn),所以堆是一种高效的数据结构。