二叉堆是一個數組,它能夠被當作一個近似的徹底二叉樹,樹上的每個節點對應數組中的一個元素。除了最底層外,該樹是徹底充滿的,並且是從左向右填充。二叉堆能夠有兩種形式:最大堆和最小堆,這裏我主要講解最大堆。最大堆的定義是:堆中某個節點的值老是不大於其父節點的值。java
62 / \ 41 30 / \ / \ 28 16 22 13 / \ / 19 17 15 0 1 2 3 4 5 6 7 8 9 62 41 30 28 16 22 13 19 17 15
當咱們用二叉堆表示上面的數組的時候,咱們能夠知道
父節點:parent(i) = (i - 1)/2
左節點:left child (i) = 2 i + 1
右節點:right child(i) = 2i + 2算法
首先咱們實現一下堆的交換方法swap和父子節點的方法數組
public void swap(int i,int j){ E t = data[i]; data[i] = data[j]; data[j] = t; } // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的父親節點的索引 private int parent(int index){ if(index == 0) throw new IllegalArgumentException("index-0 doesn't have parent."); return (index - 1) / 2; } // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引 private int leftChild(int index){ return index * 2 + 1; } // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引 private int rightChild(int index){ return index * 2 + 2; }
二叉堆的核心是」添加節點」和」刪除節點」,理解這兩個算法,二叉堆也就基本掌握了。下面對它們進行介紹。dom
從最後一個節點的地方插入元素,而後和父節點比較並進行交換位置。若是堆的有序狀態由於某個節點變得比它的父節點更大而被打破,那麼咱們就須要經過交換它和它的父節點來修復堆。
好比向堆中插入52:code
62 / \ 41 30 / \ / \ 28 16 22 13 / \ / \ 19 17 15 52 在最後添加節點52,發現52大於16,因而和16交換位置 -----> 62 / \ 41 30 / \ / \ 28 52 22 13 / \ / \ 19 17 15 16 52再和本身的父節點比較,發現52大於41,再和41交換位置 -----> 62 / \ 52 30 / \ / \ 28 41 22 13 / \ / \ 19 17 15 16 當52發現小於本身的父節點的時候,中止交換,插入完成 -----> 62 / \ 52 30 / \ / \ 28 41 22 13 / \ / \ 19 17 15 16
咱們用代碼來表示上面的過程索引
// 向堆中添加元素 public void add(E e){ data.addLast(e); siftUp(data.getSize() - 1); } private void siftUp(int k){ while(k > 0 && data[parent(k)].compareTo(data[k)] < 0 ){ swap(k, parent(k)); k = parent(k); } }
咱們從數組頂端刪去最大的元素並將數組的最後一個元素放到頂端,減少堆的大小並讓這個元素下沉到合適的位置。rem
62 / \ 52 30 / \ / \ 28 41 22 13 / \ / \ 19 17 15 16 首先將最後一個元素放到頂端 16 / \ 52 30 / \ / \ 28 41 22 13 / \ / 19 17 15 而後和左右兩個子節點比較,和並和子節點中較大的節點交換位置,16和52交換 52 / \ 16 30 / \ / \ 28 41 22 13 / \ / 19 17 15 而後一直交換,16和41交換,而後發現16比它子節點15大,交換完畢 52 / \ 41 30 / \ / \ 28 16 22 13 / \ / 19 17 15
下面咱們用代碼實現一下這個過程get
// 看堆中的最大元素 public E findMax(){ if(data.getSize() == 0) throw new IllegalArgumentException("Can not findMax when heap is empty."); return data[0]; } // 取出堆中最大元素 public E extractMax(){ E ret = findMax(); swap(0, data.getSize() - 1); data[data.getSize() - 1]= null; size --; siftDown(0); return ret; } private void siftDown(int k){ while(leftChild(k) < getSize()){ int j = leftChild(k); // 在此輪循環中,data[k]和data[j]交換位置 if( j + 1 < getSize() && data[j + 1].compareTo(data[j]) > 0 ) j ++; // data[j] 是 leftChild 和 rightChild 中的最大值 if(data[k].compareTo(data[j]) >= 0 ) break; swap(k, j); k = j; } }
因爲數組的大小固定以後就不能改變,因此咱們這裏使用動態數組ArrayList來代替數組的實現。下面放出完整代碼:io
import java.util.ArrayList; import java.util.List; import java.util.Random; /** * @author luozhiyun on 2019-04-07. */ public class MaxHeap<E extends Comparable<E>> { private List<E> list; public MaxHeap(){ list = new ArrayList<>(); } public MaxHeap(int cap) { list = new ArrayList<>(cap); } public MaxHeap(E[] es) { list = new ArrayList<>(es.length); for (int i = 0; i < es.length; i++) { list.add(es[i]); } for (int i = parent(list.size()-1); i >=0; i--) { siftUp(i); } } public int size() { return list.size(); } public boolean isEmpty() { return list.isEmpty(); } private int parent(int index) { return (index - 1) / 2; } private int leftChild(int index) { return index * 2 + 1; } private int rightChild(int index) { return index * 2 + 2; } public void add(E e) { list.add(e); siftUp(list.size() - 1); } private void siftUp(int index) { while (index > 0 && list.get(parent(index)).compareTo(list.get(index)) < 0) { swap(index, parent(index)); index = parent(index); } } private void swap(int i,int j) { E e = list.get(i); list.set(i, list.get(j)); list.set(j, e); } public E findMax() { return list.get(0); } //取出堆中最大的元素 public E extractMax() { E max = findMax(); swap(0, list.size() - 1); list.remove(list.size() - 1); siftDown(0); return max; } private void siftDown(int index) { while (leftChild(index) < list.size()) { int l = leftChild(index); if (l + 1 < list.size() && list.get(l).compareTo(list.get(l + 1) )< 0) { l++; } if (list.get(index).compareTo(list.get(l)) >= 0) { break; } swap(index, l); index = l; } } public E replace(E e) { E max = findMax(); list.set(0, e); siftDown(0); return max; } public static void main(String[] args) { int n = 100000 ; MaxHeap<Integer> integerMaxHeap = new MaxHeap<>(); Random random = new Random(); for (int i = 0; i < n; i++) { integerMaxHeap.add(random.nextInt(Integer.MAX_VALUE)); } int[] ints = new int[n]; for (int i = 0; i < n; i++) { ints[i] = integerMaxHeap.extractMax(); } for (int i = 1; i < n; i++) { if (ints[i] > ints[i - 1]) { throw new IllegalArgumentException("Error"); } } System.out.println("Test MaxHeap completed;"); } }