E extractMax() - 提取堆中最大的元素
堆中最大的元素:
堆中最大的元素就是堆顶的元素; 从二叉树的角度看,堆中最大的元素就是二叉树的根节点; 从数组的角度看,堆中最大的元素就是数组中索引为0的元素; 提取堆中最大的元素:
找到堆中最大的元素并返回很简单,但提取涉及到要把最大的元素从堆中删除(即逻辑上删除二叉树的根,物理上删除数组索引为0的元素),就要重组剩余元素的结构,使之仍然能够满足堆的定义; 具体删除最大堆中的堆顶元素的做法如下:
让堆顶元素和堆中最后一个元素互换位置; 删除堆中最后一个元素,即刚换过来的原堆顶元素; 下沉新堆顶元素,使整个二叉树重新满足堆的定义;
// 看堆中的最大元素
public E findMax(){
if(data.getSize() == 0)
throw new IllegalArgumentException("Can not findMax when heap is empty.");
return data.get(0);
}
// 取出堆中最大元素
public E extractMax(){
E ret = findMax();
data.swap(0, data.getSize() - 1);
data.removeLast();
siftDown(0);
return ret;
}
void siftDown(int k) - 下沉新的堆顶元素
下沉的终止条件,即k指向的元素已经没有可下沉的空间了,这个状态为:k的左孩子的索引已经大于等于size,size从索引的角度看,指向下一个码元素的位置;当堆中只有一个元素,size==1,size指向堆顶元素的左孩子,堆顶元素左孩子的索引也是1,下一个堆中元素的码放位置是堆顶元素左孩子的位置,说明堆顶元素没有左孩子,更没有右孩子,这就是k没有下沉空间的状态了,k但凡还有个左孩子(就是有孩子,k还没到最底层),它就还有下沉的空间 ; 用一个指针j指向k的左孩子; 看 k 是否还有右孩子,并且 k 的右孩子比 k 的左孩子大,那么让 j 指向 k 的右孩子,否则 j 还指向 k 的左孩子,此时 j 指向 k 的疑似下沉位置 ; k 和其疑似下沉位置 做比较,如果 k 不比其疑似下沉位置 的元素小,则不需要下沉,跳出循环,下沉动作结束; 如果 k 比其疑似下沉位置 的元素小,开始下沉动作,交换 k 和其疑似下沉位置 的元素; 下沉完成以后,让 k 重新追上待下沉元素;
private void siftDown(int k){
while(leftChild(k) < data.getSize()){
int j = leftChild(k);
// 如果k有右孩子,并且右孩子比左孩子大
if( j + 1 < data.getSize() &&
data.get(j + 1).compareTo(data.get(j)) > 0 )
j ++; // j 指向 leftChild 和 rightChild 中更大的那一个
// 如果不需要下沉,跳出循环,下沉动作结束
if(data.get(k).compareTo(data.get(j)) >= 0 )
break;
// k 和 j 交换位置,待下沉元素和左右孩子中更大的那个交换位置
data.swap(k, j);
// k 重新追上待下沉的元素
k = j;
}
}