堆
heap,一棵完全二叉樹,使用數組實現的,但具備完全二叉樹的一些性質。一般總是滿足以下性質:
- 堆中某個節點的值總是不大于或不小于其父節點的值;
- 堆總是一棵完全二叉樹。(即除了最底層,其他層的節點都被元素填滿,且最底層盡可能地從左到右填入)
堆一般分為以下兩種形式:
- 大根堆:根節點的值最大,每個節點都大于等于其子樹中所有節點的值
- 小根堆:根節點的值最小,每個節點都小于等于其子樹中所有節點的值
兩者的總體架構相同,不同的只是元素之間的比較規則
堆的入隊和出隊的時間復雜度都是O(log n)
在PriorityQueue中,最高優先級的元素總是位于隊列的前面,即堆的根節點。(默認是數字越小,優先級越高),常用方法:
- peek()方法:獲取隊列中優先級最高的元素
- poll()方法:從隊列中刪除并返回優先級最高的元素
- add()方法:向隊列中添加元素
- remove()方法:移除隊列中的某個元素(數組中從左到右的第一個)
- size()方法:返回當前堆中的元素個數
- clear()方法:清空堆中的元素,重置堆
PriorityQueue底層使用最小堆來實現,能夠自動維護堆的性質。
在實現堆的過程中,通常使用數組來存儲堆元素,并通過一些算法實現堆的插入、刪除等操作。
小根堆
數組遞歸實現小根堆
import java.util.*;public class MinHeap {private int[] heap; //用于實現堆的數組private int size; //當前數組中最后一個元素的索引private int maxSize; //總數組長度public MinHeap(int maxSize) {this.maxSize = maxSize;this.size = 0;//從索引為1的位置存儲堆中的元素heap = new int[this.maxSize + 1];heap[0] = Integer.MIN_VALUE;}// 獲取傳入元素的父元素private int parent(int pos) {return pos / 2;}// 獲取傳入元素的左子元素private int leftChild(int pos) {return 2 * pos;}// 獲取傳入元素的右子元素private int rightChild(int pos) {return (2 * pos) + 1;}//判斷是否是葉子結點private boolean isLeaf(int pos) {return pos > (size / 2) && pos <= size;}//交換位置private void swap(int pos1, int pos2) {int tmp = heap[pos1];heap[pos1] = heap[pos2];heap[pos2] = tmp;}//小根堆化,對傳入的元素作為父節點的子樹進行重構,用于在插入或刪除元素后重新調整堆以保持小根堆的性質private void minHeapify(int pos) {if (!isLeaf(pos)) {//不是葉子結點//如果父元素比左子元素或右子元素大if (heap[pos] > heap[leftChild(pos)] || heap[pos] > heap[rightChild(pos)]) {//交換父元素和兩子節點中較小的那個元素,然后重構對應的子樹if (heap[leftChild(pos)] < heap[rightChild(pos)]) {swap(pos, leftChild(pos));minHeapify(leftChild(pos));} else {swap(pos, rightChild(pos));minHeapify(rightChild(pos));}}}}//向堆中插入元素public void insert(int element) {if (size >= maxSize) {return;}heap[++size] = element;int current = size;//插入的元素比對應的父元素小,交換位置,不斷向上重構while (heap[current] < heap[parent(current)]) {swap(current, parent(current));current = parent(current);}}//獲取最小的元素,將最后一個元素放入到原最小元素的位置,然后重構public int extractMin() {int popped = heap[1];heap[1] = heap[size--];minHeapify(1);return popped;}//不斷根據父元素打印子節點的元素public void print() {for (int i = 1; i <= size / 2; i++) {System.out.print(" Parent: " + heap[i] + " Left child: " + heap[2 * i] + " Right child: " + heap[2 * i + 1]);System.out.println();}}public static void main(String[] args) {MinHeap minHeap = new MinHeap(10);minHeap.insert(5);minHeap.insert(3);minHeap.insert(17);minHeap.print();System.out.println("The Min val is " + minHeap.extractMin());}
}
數組非遞歸實現小根堆
import java.util.Arrays;public class MinHeap {private int[] heap;private int size;public MinHeap(int capacity) {heap = new int[capacity];size = 0;}public void insert(int val) {if (size == heap.length) {//如果空間不夠則將數組的長度擴充一倍heap = Arrays.copyOf(heap, size * 2);}heap[size++] = val;int i = size - 1;while (i > 0 && heap[i] < heap[(i - 1) / 2]) {int temp = heap[i];heap[i] = heap[(i - 1) / 2];heap[(i - 1) / 2] = temp;i = (i - 1) / 2;}}public int pop() {if (size == 0) {throw new IllegalStateException("Heap is empty");}int root = heap[0];heap[0] = heap[--size];int i = 0;while (i * 2 + 1 < size) {int child = i * 2 + 1;//如果右節點存在而且比左節點小,則指向右節點,否則指向左節點if (child + 1 < size && heap[child + 1] < heap[child]) {child++;}if (heap[child] < heap[i]) {int temp = heap[i];heap[i] = heap[child];heap[child] = temp;i = child;} else {break;}}return root;}public boolean isEmpty() {return size == 0;}
}
在 insert() 方法中,先將元素插入數組末尾,然后將其上移至合適位置,直到父節點小于等于該元素或者該元素上移到根節點為止。在 pop() 方法中,將根節點彈出后,將數組末尾元素放置根節點處,然后將其下移至合適位置,直到子節點大于等于該元素或者該元素下移到葉子節點為止。
非遞歸實現,因此空間復雜度較遞歸實現會小一些,但是代碼實現會更加復雜。
非遞歸實現中需要學習的還有:
拋出異常
if (size == 0) {throw new IllegalStateException("Heap is empty"); }
擴充堆大小
heap = Arrays.copyOf(heap, size * 2);
PriorityQueue實現小根堆
public static void main(String[] args) {//可以直接使用PriorityQueue實現小根堆,因為PriorityQueue內部默認就是使用小根堆實現的//PriorityQueue<Integer> minHeap = new PriorityQueue<>();//傳入了一個Comparator對象,重寫了compare方法,使得PriorityQueue中的元素按照從小到大的順序排列。PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(new Comparator<Integer>() {public int compare(Integer a, Integer b) {return a - b;}});minHeap.add(10);minHeap.add(20);minHeap.add(15);System.out.println("Smallest element: " + minHeap.peek());System.out.println("Removed element: " + minHeap.poll());System.out.println("New smallest element: " + minHeap.peek());}
小根堆的實現可以使用Java中的PriorityQueue類,只需要在創建PriorityQueue對象時傳入一個Comparator對象,實現Comparator的compare方法來定義小根堆的比較方式。
大根堆
數組遞歸實現大根堆
import java.util.*;public class MaxHeap {private int[] heap; //用于實現堆的數組private int size; //當前數組中最后一個元素的索引private int maxSize; //總數組長度public MaxHeap(int maxSize) {this.maxSize = maxSize;this.size = 0;//從索引為1的位置存儲堆中的元素heap = new int[this.maxSize + 1];heap[0] = Integer.MAX_VALUE;}// 獲取傳入元素的父元素private int parent(int pos) {return pos / 2;}// 獲取傳入元素的左子元素private int leftChild(int pos) {return 2 * pos;}// 獲取傳入元素的右子元素private int rightChild(int pos) {return (2 * pos) + 1;}//判斷是否是葉子結點private boolean isLeaf(int pos) {return pos > (size / 2) && pos <= size;}//交換位置private void swap(int pos1, int pos2) {int tmp = heap[pos1];heap[pos1] = heap[pos2];heap[pos2] = tmp;}//小根堆化,對傳入的元素作為父節點的子樹進行重構private void maxHeapify(int pos) {if (!isLeaf(pos)) {//不是葉子結點//如果父元素比左子元素或右子元素小if (heap[pos] < heap[leftChild(pos)] || heap[pos] < heap[rightChild(pos)]) {//交換父元素和兩子節點中較小的那個元素,然后重構對應的子樹if (heap[leftChild(pos)] > heap[rightChild(pos)]) {swap(pos, leftChild(pos));maxHeapify(leftChild(pos));} else {swap(pos, rightChild(pos));maxHeapify(rightChild(pos));}}}}//向堆中插入元素public void insert(int element) {if (size >= maxSize) {return;}heap[++size] = element;int current = size;//插入的元素比對應的父元素小,交換位置,不斷向上重構while (heap[current] > heap[parent(current)]) {swap(current, parent(current));current = parent(current);}}//獲取最小的元素,將最后一個元素放入到原最小元素的位置,然后重構public int extractMax() {int popped = heap[1];heap[1] = heap[size--];maxHeapify(1);return popped;}//不斷根據父元素打印子節點的元素public void print() {for (int i = 1; i <= size / 2; i++) {System.out.print(" Parent: " + heap[i] + " Left child: " + heap[2 * i] + " Right child: " + heap[2 * i + 1]);System.out.println();}}public static void main(String[] args) {MaxHeap maxHeap = new MaxHeap(10);maxHeap.insert(5);maxHeap.insert(3);maxHeap.insert(17);maxHeap.print();System.out.println("The Max val is " + maxHeap.extractMax());}
}
PriorityQueue實現大根堆
public static void main(String[] args) {// 傳入了一個Comparator對象,重寫了compare方法,使得PriorityQueue中的元素按照從大到小的順序排列。PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>() {public int compare(Integer a, Integer b) {return b - a;}});maxHeap.add(10);maxHeap.add(20);maxHeap.add(15);System.out.println("Max element: " + maxHeap.peek());System.out.println("Removed element: " + maxHeap.poll());System.out.println("New Max element: " + maxHeap.peek());}