一、堆的定義
二叉堆是一種特殊的二叉樹,它的每個結點都大於等於其兩個子結點,這種被稱爲最大堆,當結點都小於其兩個子結點時,被稱爲最小堆。它的存儲結構一般都選擇使用數組而不是鏈表。
二、堆的實現
觀察上圖,其實二叉堆是一種特殊的二叉樹,這種特殊之處就在於二叉堆永遠都是一棵完全二叉樹,這個特性使得我們可以選擇使用數組來實現,具體方法就是將二叉樹上的元素按層級一級一級的放入數組中,如下圖所示。那麼在數組中,父結點和左右子結點的關係是如何的呢?
通過觀察可以發現:
父結點與子結點在數組中的座標分別滿足以下關係:
leftChild= parent *2 + 1;
rightChild= parent *2 + 2;
parent = (child-1)/2;
好了,有了這個認識我們就可以開始逐步去實現了,以最大堆爲例。對於最大堆,其存儲結構選擇另一篇博文中實現的動態數組,具體實現可以移步去閱讀一下。
public class MaxHeap<T extends Comparable<T>> {
private Array<T> array;
public MaxHeap(){
this.array = new Array<>();
}
}
再定義出三個上面提到的父子結點之間關係的三個函數
public class MaxHeap<T extends Comparable<T>> {
private Array<T> array;
public MaxHeap(){
this.array = new Array<>();
}
//父親節點
private int(int child){
if(child>0)
return (child-1)/2;
else
throw new IllegalStateException(child + "don't have parent node.");
}
//左孩子節點
private int leftChild(int parent){
return parent*2+1;
}
//右孩子節點
private int rightChild(int parent){
return parent*2 + 2;
}
}
有了這個基礎,就可以考慮怎麼往堆中添加元素了。對於添加元素,需要藉助一個上浮的操作。下面以圖的形式解釋什麼是上浮。以上面的最大堆爲例,假如現在添加一個元素7,那麼怎麼添加呢?其過程如下:
- 將待添加的元素7添加到堆尾,此時其結構如下:
- 將新添加的元素進行上浮操作。所謂的上浮,就是將元素與其父元素對比,當發現其比父元素大時就交換,然後繼續與其父元素對比,知道其小於等於其父元素。在這裏,就將7與其父元素6對比,發現比6大,就交換,交換後如下圖
- 然後7與其父元素10比較,發現比10小,停止上浮,添加元素完成。
所以整個添加元素的過程比較簡單,其代碼實現如下:
public class MaxHeap<T extends Comparable<T>> {
private Array<T> array;
public MaxHeap(){
this.array = new Array<>();
}
//父親節點
public int parent(int child){
if(child>0)
return (child-1)/2;
else
throw new IllegalStateException(child + "don't have parent node.");
}
//左孩子節點
public int leftChild(int parent){
return parent*2+1;
}
//右孩子節點
public int rightChild(int parent){
return parent*2 + 2;
}
/**
* 堆中已添加的元素個數
* @return
*/
public int getSize(){
return array.getSize();
}
/**
* 往堆中添加元素
* @param elem
*/
public void add(T elem){
//將元素添加到堆底
array.addLast(elem);
//進行上升操作
shiftUp(array.getSize()-1);
}
/**
* 堆中節點上升操作
* @param index
*/
private void shiftUp(int index){
while(index > 0){
int p = parent(index);
if(array.get(p).compareTo(array.get(index))<0){ //父元素比子元素小
array.swap(index, p);
index = p;
}else{
break;
}
}
}
}
講完了添加元素,下面來講一下從堆中刪除元素。對於堆中元素的刪除,默認是刪除堆頂的元素。
刪除元素的過程如下:
- 首先,將堆頂元素與堆尾元素互換,然後刪除堆尾的元素,而堆尾的元素可以直接刪除,不需要其他的操作。
- 將堆頂的元素進行下沉操作。所謂的下沉,其過程其實也很簡單,首先比較當前元素的左右結點,選出其中較大的與父元素交換,這樣就保證了最大堆的性質。然後繼續迭代此操作,直到其大於等於其兩個子元素。
代碼實現如下:
public class MaxHeap<T extends Comparable<T>> {
private Array<T> array;
public MaxHeap(){
this.array = new Array<>();
}
//父親節點
public int parent(int child){
if(child>0)
return (child-1)/2;
else
throw new IllegalStateException(child + "don't have parent node.");
}
//左孩子節點
public int leftChild(int parent){
return parent*2+1;
}
//右孩子節點
public int rightChild(int parent){
return parent*2 + 2;
}
/**
* 堆中已添加的元素個數
* @return
*/
public int getSize(){
return array.getSize();
}
/**
* 往堆中添加元素
* @param elem
*/
public void add(T elem){
//將元素添加到堆底
array.addLast(elem);
//進行上升操作
shiftUp(array.getSize()-1);
}
/**
* 堆中節點上升操作
* @param index
*/
private void shiftUp(int index){
while(index > 0){
int p = parent(index);
if(array.get(p).compareTo(array.get(index))<0){ //父元素比子元素小
array.swap(index, p);
index = p;
}else{
break;
}
}
}
/**
* 刪除堆頂元素
* @return
*/
public T remove(){
if(!array.isEmpty()){
T res = array.get(0);
array.swap(0, array.getSize()-1); //調換堆頂和堆底元素
array.removeLast(); //刪除原來堆頂元素
shiftDown(0); //將現在堆頂元素進行下降操作
return res;
}else{
throw new IllegalStateException("MaxHeap is empty.");
}
}
/**
* 下降操作
* @param index
*/
private void shiftDown(int index){
while(leftChild(index) < array.getSize()){
int j = leftChild(index);
if(j+1 < array.getSize() && array.get(j).compareTo(array.get(j+1))<0){ //說明有右孩子,且左孩子比有右孩子小
j++;
}
//array.get(j)此時是最大值
if(array.get(index).compareTo(array.get(j))>0){ //此時元素已到達正確位置
break;
}
array.swap(j,index);
index = j;
}
}
}
三、測試
public class TestMaxHeap {
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<>();
Random random = new Random();
for(int i=0;i<10;i++){
maxHeap.add(random.nextInt(50));
}
for(int i=0;i<10;i++){
System.out.print(maxHeap.remove()+" ");
}
System.out.println();
}
}
結果無誤: