讲堆不得不提的就是优先队列。
什么是优先队列?
普通队列:先进先出,后进后出
优先队列:出队顺序和入队顺序无关;和优先级相关
如何实现优先队列?
由上图可知,用堆来实现优先队列是一个理想的方法。
这里采用二叉堆(最大堆)的定义:
- 所有节点的值都不大于其父节点的值
- 二叉堆是一个完全二叉树
采用数组来存储二叉堆
由二叉堆的定义可以得出结论:对一颗完全二叉树按层序遍历编号,根节点的编号为1, 假设父节点的编号为 i , 则任何子节点的编号满足:
- left child = i * 2
- right child = i * 2 + 1
用C++语言一步一步实现最大堆
初始化:
#include <iostream>
#include <algorithm>
#include <string>
#include <ctime>
#include <cmath>
#include <cassert>
using namespace std;
//MaxHeap implements
template <typename Item>
class MaxHeap{
private:
Item* data;
int count;//堆的大小
int capacity;
public:
MaxHeap(int capacity) {
data = new Item[capacity + 1];
count = 0;
this->capacity = capacity;
}
~MaxHeap() {
//释放内存空间
delete [] data;
}
//return the size of maxheap
int size() {
return count;
}
int isEmpty() {
return count == 0;
}
void print() {
for(int i = 0; i <= this->count; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
MaxHeap<int> maxheap = MaxHeap<int>(100);
cout << maxheap.size() << endl;
srand(time(NULL));
return 0;
}
插入一个值:
首先将插入值放在数组的最后面,然后将值与父节点比较,如果父节点的值比插入值大,则当前位置就是合适位置;如果父节点的值比插入值小,则当前位置不合适,将当前值与父节点值交换,然后再向上比较。直到父节点的值比插入值大或者已到根节点。
void shiftUp(int k) {
while(k > 1 && data[k/2] < data[k]) {
swap(data[k / 2], data[k]);
k /= 2;
}
}
//插入一个值
void insert(Item item) {
assert(count + 1 <= capacity );
data[count + 1] = item;
count++;
shiftUp(count);
}
推出一个值:
在最大堆中推出一个值,即最大值,也就是二叉树的根节点。
可以先将数组中的最后一个元素赋值给第一个元素,由于我们堆有一个对的大小属性维护,不用删除最后一个元素的值。
然后由根节点一次向下比较左右两个字节点的大小,如果左字节点小于右字节点,那么将右子节点与父节点交换,否则与左字节点交换。
void shiftDown(int k) {
while( 2 * k <= count) {
int j = 2 * k;//在次轮循环中,data[k]和data[j]交换位置
if( j + 1 <= count && data[j + 1] > data[j])
j++;
if(data[k] >= data[j])
break;
swap(data[j], data[k]);
k = j;
}
}
Item extractMax() {
assert(count > 0);
Item ret = data[1];
swap(data[1], data[count]);
count--;
shiftDown(1);
return ret;
}
索引堆
当我们要建立堆的数据结构过于复杂时,交换元素困难,我们可以考虑使用索引堆,即采用一个indexes数组存储二叉最大堆的索引值,data数组存储值,每次只交换索引,而不交换值。这里给出一种实现方式。
template <typename Item>
class IndexMaxHeap{
private:
int *indexes;
Item* data;
int count;//堆的大小
int capacity;
void shiftUp(int k) {
while(k > 1 && data[indexes[k/2]] < data[indexes[k]]) {
swap(indexes[k / 2], indexes[k]);
k /= 2;
}
}
void shiftDown(int k) {
while( 2 * k <= count) {
int j = 2 * k;//在次轮循环中,data[k]和data[j]交换位置
if( j + 1 <= count && data[indexes[j + 1]] > data[indexes[j]])
j++;
if(data[indexes[k]] >= data[indexes[j]])
break;
swap(indexes[j], indexes[k]);
k = j;
}
}
public:
IndexMaxHeap(int capacity) {
data = new Item[capacity + 1];
indexes = new int[capacity + 1];
count = 0;
this->capacity = capacity;
}
~IndexMaxHeap() {
//释放内存空间
delete [] data;
delete [] indexes;
}
//return the size of maxheap
int size() {
return count;
}
int isEmpty() {
return count == 0;
}
//插入一个值
//用户以为i通常以为是从0开始索引的
void insert(int i, Item item) {
assert(count + 1 <= capacity );
assert(i + 1 >= 1 && i + 1 <= capacity);
i++;
data[i] = item;
indexes[count + 1] = i;
count++;
shiftUp(count);
}
void print() {
for(int i = 0; i <= this->count; i++) {
cout << data[i] << " ";
}
cout << endl;
}
//返回最大的元素
Item extractMax() {
assert(count > 0);
Item ret = data[indexes[1]];
swap(indexes[1], indexes[count]);
count--;
shiftDown(1);
return ret;
}
//返回最大元素的索引
int extractMaxIndex() {
assert(count > 0);
int ret = indexes[1] - 1;
swap(indexes[1], indexes[count]);
count--;
shiftDown(1);
return ret;
}
//由索引来取值
Item getItem(int i) {
return data[i + 1];
}
void change(int i, Item newItem) {
i++;
data[i] = newItem;
// 找到indexes[j] = i, j表示data[i]在堆中的位置
//时间复杂度 n + logn, 即O(n)
for(int j = 0; j <= count; j++)
if(indexes[j] == i) {
data[i] = newItem;
shiftDown(j);
shiftUp(j);
}
}
};