二叉堆
二叉堆的定義
-
二叉堆是一顆完全二叉樹
完全二叉樹:把元素一層一層放,如果每一層都放滿,沒有空的葉子節點,就是滿二叉樹,如果有空的葉子節點,那就是完全二叉樹。
-
二叉樹中每一個節點的值總是小於父節點的值(最大堆)。如下圖所示便是一個最大二叉
用數組表示表示堆
如上二叉堆我們可以用這樣一個數組表示,將堆中的元素按順序依次放入數組中,這樣的話,要表示一個節點的父子關係不如定義成樹結構,可以用指針方便的表示。但其實我們觀察可以發現每一個節點他的父子關係與他在數組中的索引是有一定關係的:
節點的父元素索引:(index - 1) / 2
節點的左孩子索引:2 * index + 1
節點的右孩子索引:2 * index + 2
向堆中添加元素
向二叉堆中插入元素的過程如上如圖所示,首先插入80,發現破壞了最大堆的性質,因此將80和父元素交換位置,交換後,80和父元素71相比,需要繼續交換位置,直到滿足最大堆的性質。這個過程叫元素的上浮
代碼實現:
/**
* 向堆中添加元素
*
* @param e
*/
public void add(E e) {
data.addLast(e);
siftUp(data.getSize() - 1);
}
/**
* 堆中元素的上浮
*
* @param index 要上浮元素的索引
*/
public void siftUp(int index) {
while (index > 0 && data.get(index).compareTo
(data.get(getParentIndex(index))) > 0) {
data.swap(index, getParentIndex(index));
index = getParentIndex(index);
}
}
/**
* 交換元素
* @param i
* @param j
*/
public void swap(int i, int j) {
if (i < 0 || i > size) {
throw new IllegalArgumentException("index is illegal");
}
E e = data[i];
data[i] = data[j];
data[j] = e;
}
從堆中刪除元素
刪除元素的過程如圖所示:首先刪除堆頂元素,然後將最後一個元素挪到堆頂,這個時候破壞了堆的性質,就開始執行下沉操作,讓堆頂元素和孩子比較,將較大的孩子上移,堆頂元素下沉,直到符合堆的條件爲止。
/**
* 取出堆中最大元素
*
* @return
*/
public E extractMax() {
E maxItem = findMax();
data.swap(0, data.getSize() - 1);
data.removeLast();
siftDown(0);
return maxItem;
}
/**
* 堆中元素的下沉
*
* @param index
*/
private void siftDown(int index) {
int childIndex = 0;
while (getLeftChildIndex(index) < data.getSize()) {
childIndex = getLeftChildIndex(index);
// 找到兩個孩子中最大的節點
if (childIndex + 1 < data.getSize() &&
data.get(childIndex + 1).compareTo(data.get(childIndex)) > 0) {
childIndex = getRightChildIndex(index);
}
if (data.get(index).compareTo(data.get(childIndex)) >= 0) {
break;
}
data.swap(index, childIndex);
index = childIndex;
}
}
最小堆的實現,基於以上代碼在交換元素的實現有優化
/**
* 最小堆
*
* @author HXY
* @param <E>
*/
public class MinHeap<E extends Comparable<E>> {
private static final int DEFAULT_CAPACITY = 10;
private int size;
private E[] array;
/**
* 構造函數,構造指定容量的最小堆
*/
public MinHeap(int capacity) {
array = (E[]) new Comparable[capacity];
size = 0;
}
/**
* 構造函數,構造容量爲10的最小堆
*/
public MinHeap() {
this(DEFAULT_CAPACITY);
}
/**
* 構造函數,將一個數組轉化爲最小堆
*
* @param items 數組
*/
public MinHeap(E[] items) {
this(items.length);
for (int i = 1; i <= items.length; i++) {
array[i] = items[i];
}
buildHeap();
size = items.length;
}
private void buildHeap() {
for (int i = size / 2; i > 0; i--) {
siftDown(i);
}
}
/**
* 是否爲空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 查找最小元素
*
* @return 堆頂元素
*/
public E findMin() {
if (isEmpty()) {
throw new IllegalArgumentException("Can not find min because the heap is empty.");
}
return array[0];
}
private void enlargeArray(int newCapacity) {
E[] newData = (E[]) new Comparable[newCapacity];
for (int i = 0; i < array.length; i++) {
newData[i] = array[i];
}
array = newData;
}
/**
* 添加元素
*
* @param e
*/
public void insert(E e) {
if (array.length - 1 == size) {
enlargeArray(2 * array.length + 1);
}
// 如果使用交換的方法,需要三次賦值,一個元素上浮d層,就需要3d次賦值,我們這個方法優化可以只用d+1次賦值
int hole = ++size;
for (array[0] = e; e.compareTo(array[hole/2]) < 0; hole /= 2) {
array[hole] = array[hole/2];
}
array[hole] = e;
}
/**
* 刪除堆頂元素
*
* @return 堆頂元素
*/
public E deleteMin() {
E minItem = findMin();
array[1] = array[size--];
siftDown(1);
return minItem;
}
/**
* 下沉操作
*
* @param hole 開始下沉的位置
*/
private void siftDown(int hole) {
int child = 0;
E tmp = array[hole];
for (; hole * 2 <= size; hole = child) {
child = hole * 2;
if (child != size
&& array[child + 1].compareTo(array[child]) < 0) {
child++;
}
if (array[child].compareTo(tmp) < 0) {
array[hole] = array[child];
} else {
break;
}
}
array[hole] = tmp;
}
}