慾望以提升熱忱,毅力以磨平高山。
什麼是堆?
堆(heap)是計算機科學中一類特殊的數據結構的統稱。堆通常是一個可被看做一棵樹的數組對象。這裏介紹的是最爲常見的二叉堆。
二叉堆(Binary Heap)是一種特殊的堆,二叉堆是完全二元樹並且總是滿足下列性質:
- 堆中某個節點的值總是不大於或不小於其父節點的值;
- 堆總是一棵完全二叉樹。
二叉堆有兩種:最大堆和最小堆。
- 最大堆:父結點的鍵值總是大於或等於任何一個子節點的鍵值;
- 最小堆:父結點的鍵值總是小於或等於任何一個子節點的鍵值。
二叉堆的數組表示法
如果對於一個二叉堆從上到下、從左至右依次從1開始編號,並將每個節點的編號作爲數組的下標,那麼就能得到一個用數組表示的二叉堆。採用數組表示法有以下優點:
- 存儲密度高
- 能夠輕易獲得某個節點的雙親和左右孩子結點
根據二叉樹的性質:下標爲 i(i>1)處節點的雙親節點下標爲 i/2(整除),下標爲 i 處節點的做孩子節點下標爲 2 * i ;右孩子節點下標爲 2 * i + 1。
爲了充分利用數組空間,我們可以從 0 開始編號,那麼下標爲 i(i>0)處節點的雙親節點下標爲 ( i - 1 ) / 2(整除),下標爲 i 處節點的做孩子節點下標爲 2 * i + 1 ;右孩子節點下標爲 2 * i + 2。
大頂堆實現
基於以前實現的動態數組,這裏直接複用SeqList實現大頂堆
數據結構:線性數據結構
大頂堆的向上調整siftUp和向下調整siftDown
siftUp
向大頂堆中添加元素時,由於我們是基於數組實現的,因此可直接在數組末尾添加該元素。但是添加完元素後的堆並不一定滿足大頂堆的性質,因此還需要進行大頂堆的調整(siftUp)。
下面展示了添加元素的過程:
在數組尾部添加52
向上調整,52 大於 16
向上調整,52 大於 41
52小於62 因此不需要再調整,此時就滿足大頂堆的性質了。
向大頂堆中添加元素:
//從index處向上調整
private void siftUp(int index) {
//index > 0 並且 index處的值大於雙親的值 則進行向上調整爲大頂堆
while (index > 0 && data.getIndexOf(index).compareTo(data.getIndexOf(parent(index))) > 0) {
data.swap(index, parent(index));
index = parent(index);
}
}
//向大頂堆中添加元素
public void add(E e){
data.addLast(e);
siftUp(data.getSize() - 1);
}
由於需要交換數組中的值,因此在SeqList中添加swap方法:
//交換下標爲 i 和 j 下標的值
public void swap(int i, int j) {
if (i < 0 || i >= size) {
throw new IllegalArgumentException(" index is illegal !");
}
if (j < 0 || j >= size) {
throw new IllegalArgumentException(" index is illegal !");
}
T temp = data[i];
data[i] = data[j];
data[j] = temp;
}
siftDown
需要取出大頂堆的最大元素時,我們可以將數組中最後一個元素放到堆頂,然後進行堆的向下調整。下面展示了取出大頂堆的最大元素的過程:
將數組最後一個元素放到堆頂。
如果待調整的元素小於它的孩子節點中的最大元素,那麼就和最大元素交換。
16 小於 52 ,因此 16 和 52 發生了交換。
16 小於41 所以交換。
16 大於 15 因此不用再向下調整了,此時的堆滿足大頂堆的性質。
取出最大元素
//從index處向下調整大頂堆
private void siftDown(int index) {
while (index * 2 + 1 < data.getSize()) {
int maxIndex = leftChild(index);
if (maxIndex + 1 < data.getSize() && data.getIndexOf(rightChild(index)).compareTo(data.getIndexOf(leftChild(index))) > 0) {
maxIndex = rightChild(index);
}//此時maxIndex爲孩子元素中最大的元素
//若index處的元素小於maIndex的元素則交換 否則結束
if (data.getIndexOf(index).compareTo(data.getIndexOf(maxIndex)) < 0) {
data.swap(index, maxIndex);
index = maxIndex;
}else {
break;
}
}
}
//取出最大元素
public E extractMax() {
E ret = getMax();
//將最後一個元素放到堆頂 向下調整大頂堆
data.set(0, data.removeLast());
siftDown(0);
return ret;
}
//取出最大元素,並用e替換
public E replace(E e){
E ret = getMax();
data.set(0, e);
siftDown(0);
return ret;
}
將數組轉化爲大頂堆Heapify
將數組轉化爲大頂堆可以在遍歷數組的時候調用上面的add方法,但是如果在原來數組上進行調整可以減少近半的次數,因爲我們可以從第一個非葉子結點開始向下調整。
//構造方法:Heapify 將一個數組轉化爲大頂堆
public MaxBinaryHeap(E[] arr) {
data = new SeqList<E>(arr);
//從第一個非葉子結點開始,依次向下調整大頂堆
for (int i = parent(arr.length - 1); i >=0 ; i--) {
siftDown(i);
}
}
同時由於我們底層是用的自己實現的SeqList,因此還需在SeqList添加構造方法:
//將數組轉化爲動態數組
public SeqList(T[] arr) {
data = (T[]) new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
size = arr.length;
}
完整代碼
到此,二叉大頂堆基本就實現了,需要注意的是,在實現大頂堆的相關操作時,對以前實現的SeqList增添了一個swap方法和一個構造方法。
MaxBinaryHeap
package cn.boom.heap;
public class MaxBinaryHeap<E extends Comparable<E>> {
private SeqList<E> data;
public MaxBinaryHeap(int capacity) {
this.data = new SeqList<E>(capacity);
}
public MaxBinaryHeap() {
this.data = new SeqList<E>();
}
//Heapify 將一個數組轉化爲大頂堆
public MaxBinaryHeap(E[] arr) {
data = new SeqList<E>(arr);
//從第一個非葉子結點開始,依次向下調整大頂堆
for (int i = parent(arr.length - 1); i >=0 ; i--) {
siftDown(i);
}
}
//獲取元素個數
public int getSize() {
return data.getSize();
}
//非空判斷
public boolean isEmpty() {
return data.isEmpty();
}
//獲取參數下標對應的雙親下標
private int parent(int index) {
if (index < 0 || index >= data.getSize()) {
throw new IllegalArgumentException("index = " + index + " , index is illegal !");
}
if (index == 0) {
throw new IllegalArgumentException(" index = 0 , no parent !");
}
return ( index - 1 ) / 2;
}
//獲取參數下標對應的左孩子下標
private int leftChild(int index) {
if (index < 0 || index >= data.getSize()) {
throw new IllegalArgumentException("index = " + index + " , index is illegal !");
}
if ( 2 * index + 1 >= data.getSize() ) {
throw new IllegalArgumentException(" index = " + index +" , no leftChild !");
}
return 2 * index + 1;
}
//獲取參數下標對應的右孩子下標
private int rightChild(int index) {
if (index < 0 || index >= data.getSize()) {
throw new IllegalArgumentException("index = " + index + " , index is illegal !");
}
if ( 2 * index + 2 >= data.getSize() ) {
throw new IllegalArgumentException(" index = " + index +" , no rightChild !");
}
return 2 * index + 2;
}
//從index處向上調整
private void siftUp(int index) {
//index > 0 並且 index處的值大於雙親的值 則進行向上調整爲大頂堆
while (index > 0 && data.getIndexOf(index).compareTo(data.getIndexOf(parent(index))) > 0) {
data.swap(index, parent(index));
index = parent(index);
}
}
//向大頂堆中添加元素
public void add(E e){
data.addLast(e);
siftUp(data.getSize() - 1);
}
//獲取最大元素
public E getMax() {
if (data.getSize() == 0) {
throw new IllegalArgumentException("No data , No MaxValue ! ");
}
return data.getIndexOf(0);
}
//從index處向下調整大頂堆
private void siftDown(int index) {
while (index * 2 + 1 < data.getSize()) {
int maxIndex = leftChild(index);
if (maxIndex + 1 < data.getSize() && data.getIndexOf(rightChild(index)).compareTo(data.getIndexOf(leftChild(index))) > 0) {
maxIndex = rightChild(index);
}//此時maxIndex爲孩子元素中最大的元素
//若index處的元素小於maIndex的元素則交換 否則結束
if (data.getIndexOf(index).compareTo(data.getIndexOf(maxIndex)) < 0) {
data.swap(index, maxIndex);
index = maxIndex;
}else {
break;
}
}
}
//取出最大元素
public E extractMax() {
E ret = getMax();
//將最後一個元素放到堆頂 向下調整大頂堆
data.set(0, data.removeLast());
siftDown(0);
return ret;
}
//取出最大元素,並用e替換
public E replace(E e){
E ret = getMax();
data.set(0, e);
siftDown(0);
return ret;
}
@Override
public String toString() {
return "MaxBinaryHeap{" +
"data=" + data +
'}';
}
}
修改後的SeqList
package cn.boom.heap;
public class SeqList<T> {
private T[] data;
private int size;
//無參構造
public SeqList(){
data = (T[]) new Object[10];
size = 0;
}
//帶參構造 :參數 容量
public SeqList(int capacity) {
data = (T[]) new Object[capacity];
size = 0;
}
public SeqList(T[] arr) {
data = (T[]) new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
size = arr.length;
}
/**
* 獲取真實長度(數據個數)
* @return size
*/
public int getSize(){
return this.size;
}
/**
* 獲取index索引位置的元素
* @param index
* @return data[index]
* @throws IllegalArgumentException 參數不合法異常
*/
public T getIndexOf(int index) throws IllegalArgumentException{
//參數合法性校驗
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Illegal Index !");
}
return this.data[index];
}
//修改值
public void set(int index, T elem) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Illegal Index !");
}
data[index] = elem;
}
/**
* 按值查找 返回elem第一次出現的下標 未找到返回-1
* @param elem
* @return index
*/
public int locationElem(T elem){
for (int i = 0; i < this.size; i++) {
if (elem.equals(this.data[i])) {
return i;
}
}
return -1;
}
//是否存在元素
public boolean contains(T elem){
return locationElem(elem) != -1;
}
//表是否爲空
public boolean isEmpty(){
return (size == 0);
}
//表是否爲空
public boolean isFull(){
return (size == data.length);
}
//獲取容量
public int getCapacity(){
return data.length;
}
//在 index處插入一個元素
public void add(int index, T elem) {
//參數合法性校驗
if (index < 0 || index > size) {
throw new IllegalArgumentException("Illegal Index ! index is " + index);
}
if (isFull()) {
updateCapacity(data.length * 2);
}
//在 index 後的數據後移
for (int i = size; i > index; i--) {
this.data[i] = this.data[i - 1];
}
this.data[index] = elem;
size++;
}
//在數組首部添加元素
public void addFirst(T elem) {
add(0, elem);
}
//在數組尾部添加元素
public void addLast( T elem){
add(this.size,elem);
}
//更新數組容量
public void updateCapacity(int newCapacity) {
T[] newArray = (T[]) new Object[newCapacity];
for (int i = 0; i < size; i++) { // copy原數組中的數據
newArray[i] = data[i];
}
this.data = newArray;
}
//刪除下標爲index的元素並返回
public T remove(int index){
if (index < 0 || index > size) {
throw new IllegalArgumentException("Illegal Index ! index is " + index);
}
T elem = data[index];
for (int i = index; i < size - 1; i++) {
data[i] = data[i + 1];
}
size--;
if (size < data.length / 2) { //縮容
updateCapacity(data.length / 2);
}
return elem;
}
//刪除第一個元素
public T removeFirst() {
return remove(0);
}
//刪除最後一個元素
public T removeLast() {
return remove(size - 1);
}
//交換下標爲 i 和 j 下標的值
public void swap(int i, int j) {
if (i < 0 || i >= size) {
throw new IllegalArgumentException(" index is illegal !");
}
if (j < 0 || j >= size) {
throw new IllegalArgumentException(" index is illegal !");
}
T temp = data[i];
data[i] = data[j];
data[j] = temp;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("SeqList{");
res.append(" data=[");
for (int i = 0; i < this.size; i++) {
res.append(this.data[i].toString());
if (i != size - 1) {
res.append(',');
}
}
res.append("], size=" + size + ", capacity=" + getCapacity() + " }");
return res.toString();
}
}
基於二叉堆實現優先隊列
普通的隊列是一種先進先出的數據結構,元素在隊列尾追加,而從隊列頭刪除。在優先隊列中,元素被賦予優先級。當訪問元素時,具有最高優先級的元素最先刪除。優先隊列具有最高級先出 (first in, largest out)的行爲特徵。
由於二叉大頂堆的堆頂是堆中最大的元素,基於這個特點,我們很方便的就能基於二叉堆來實現優先隊列這種數據結構。
package cn.boom.queue;
/**
* 優先隊列
* @param <E>
*/
public class priorityQueue<E extends Comparable<E>> implements Queue<E> {
private MaxBinaryHeap<E> maxBinaryHeap;
public priorityQueue() {
this.maxBinaryHeap = new MaxBinaryHeap<E>();
}
@Override
public int getSize() {
return maxBinaryHeap.getSize();
}
@Override
public boolean isEmpty() {
return maxBinaryHeap.isEmpty();
}
@Override
public void enQueue(E e) {
maxBinaryHeap.add(e);
}
@Override
public E deQueue() {
return maxBinaryHeap.extractMax();
}
@Override
public E getFront() {
return maxBinaryHeap.getMax();
}
}