数据结构-二叉堆
表示形式
堆是一个数组,可以被看成是一个近似的完全二叉树。表示堆的数组A包含两个属性:A.length表示元素个数,A.heap-size表示有多少个堆元素存储在数组中。
也就是说,虽然A[1...A.length]可能都存有数据,但是只有A[1...A.heap-size]中存放的是堆的有效元素(0 <= A.heap-size <= A.length)。
数组元素下标从0开始,可以使用宏来定义左右子节点和父节点:
#define LEFT(idx) (((idx)<<1)+1)
#define RIGHT(idx) (((idx)<<1)+2)
#define PARENT(idx) (((idx)-1)>>1)
这种表示的是二叉堆。
二叉堆可以分两种形式:最大堆、最小堆。
其中,最大堆指:除了根节点外,所有节点 i 都要满足 A[PARENT(i)] >= A[i],堆中最大元素在根节点,并且,任一子树中,该子树包含的所有节点的值,都不大于该子树根节点的值。
最小堆相反,指:除根节点外,所有节点 i 都要满足 A[PARENT(i)] <= A[i],堆中最小元素在根节点,并且,任一子树中,该子树包含的所有节点的值,都不小于该子树根节点的值。
维护堆的性质
堆的操作分为“下沉”和“上浮”
以最大堆为例,下沉方法是:假设某个节点的左右子树都是最大堆了,拿该节点的值和左右子树值对比,取最大的一个(比如是左节点)和该节点交换,这样,这个节点的值就是这个树中最大的了。但是,这样可能破坏左子树的堆性质,于是,继续对左子树根节点执行“下沉”操作。
“上浮”方法是:假设下标小于该节点的元素,已经构成了最大堆,调节此元素时,与父节点比较,如果大于父节点的值,交换,这样父节点大于此节点的值了。但是,这样可能破坏了祖先节点的堆性质,所以,继续对祖先节点执行“上浮”操作
// 下沉
void sink(int* arr, int index, int heapsize)
{
// 左子树节点超出堆长度范围
if (LEFT(index) > heapsize-1)
{
return;
}
int maxidx = -1;
// 右子树节点超出堆长度范围,或者左节点的值比右节点的值大
if (RIGHT(index) > heapsize-1 || (arr[LEFT(index)] > arr[RIGHT(index)]))
{
maxidx = LEFT(index);
}
else
{
maxidx = RIGHT(index);
}
if (maxidx != -1 && arr[maxidx] > arr[index])
{
int tmp = arr[maxidx];
arr[maxidx] = arr[index];
arr[index] = tmp;
sink(arr, maxidx, heapsize);
}
}
// 上浮
void upward(int* arr, int index, int heapsize)
{
if (0 == index)
{
return;
}
if (arr[PARENT(index)] < arr[index])
{
int tmp = arr[index];
arr[index] = arr[PARENT(index)];
arr[PARENT(index)] = tmp;
upward(arr, PARENT(index), heapsize);
}
}
建堆
把一个普通数组调整成为一个堆的过程。有了下沉和上浮的方法,我们可以对每个元素执行这样的操作,实现建堆。
void adjust_by_sink(int* arr, int len)
{
// 下沉法,从最后一个非叶子节点开始,到根节点,依次执行下沉
for (int i = PARENT(len-1); i >= 0; i--)
{
sink(arr, i, len);
}
}
void adjust_by_upward(int* arr, int len)
{
// 上浮法,从根节点到最后一个节点,依次执行上浮
for (int i = 0; i < len; i++)
{
upward(arr, i, len);
}
}
插入元素
把元素插入最后一个位置,然后,让这个节点上浮,调整为一个堆
int insert(int* arr, int value, int& heapsize)
{
if (heapsize > ARRLEN)
{
return -1;
}
arr[heapsize] = value;
upward(arr, heapsize, heapsize+1);
heapsize++;
return 0;
}
删除元素
堆的删除元素,一般指删除第一个元素,因为第一个是需要的最大值或者最小值。
通过把最后一个值赋值给根节点,然后,把根节点下沉,再次调整为一个新的堆
void del(int* arr, int& heapsize)
{
if (heapsize <= 0)
{
return;
}
arr[0] = arr[heapsize-1];
heapsize--;
sink(arr, 0, heapsize);
}