排序演算法之——優先佇列經典實現(基於二叉堆)
許多應用都需要處理有序的元素,但有時,我們不要求所有元素都有序,或是一定要一次就將它們排序,許多情況下,我們會收集這些元素裡的最大值或最小值。
這種情況下一個合適的資料結構應該支援兩種操作:插入元素、刪除最大元素。
優先佇列與棧和佇列類似,但它有自己的奇妙之處。
在本文中,會講解基於二叉堆的一種優先佇列的經典實現方法(程式碼沒有任何難度,主要是理解思想)。
一、關於堆
1、堆的定義:
資料結構二叉堆能很好地實現優先佇列的操作。在二叉堆中,每個元素都要保證大於等於另外兩個位置的元素,相應的,這些位置的元素又至少要大於等於陣列中的另外兩個元素。
將所有元素畫成一顆二叉樹,就能很容易看出這種結構。
(圖示1)
2、堆的演算法:
在堆有序的過程中我們會遇到兩種情況:
某個節點的優先順序上升,我們需要由下至上恢復堆的順序。
當某個節點的優先順序下降,我們需要由上至下恢復堆的順序。
在排序演算法中,我們只通過私有輔助函式來訪問元素:
1private void exch(int i, int j) { 2Key temp = pq[i]; 3pq[i] = pq[j]; 4pq[j] = temp; 5} 6 7private boolean less(int i, int j) { 8return pq[i].compareTo(pq[j]) < 0; 9}
①、由下至上的堆的有序化(上浮)
1private void swim(int k) {// 二叉堆 2while (k > 1 && less(k / 2, k)) { 3exch(k / 2, k); 4k = k / 2; 5} 6}
k/2即為k節點的父節點,當k大於k/2時交換兩者,並繼續與其父節點比較,直到找到合適的位置。
②、由上至下的堆的有序化(下沉)
1private void sink(int k) {// 二叉堆 2while (2 * k <= N) { 3int j = 2 * k; 4if (j < N && less(j, j + 1)) { 5j++; 6} 7if (!less(k, j)) { 8break; 9} 10exch(k, j); 11k = j; 12} 13}
對於二叉樹,2*k即為k的左子節點,將左右子節點進行比較,再將父節點與較大的子節點比較,如果子節點大於父節點,就將他們交換,並繼續向下比較,直到找到合適的位置。
③、調整陣列大小
如果不知道元素的個數,任意在初始化時造成空間的浪費。我們需要創造一個函式,用來調整陣列的大小。
在插入方法中,如果空間已滿,就將陣列大小擴充套件為原來的兩倍。在刪除方法中,如果元素的個數小於陣列長度的1/4,就將陣列的長度減小一半。
1private void resize(int n) { 2Key[] temp = (Key[]) new Comparable[n + 1]; 3for (int i = 1; i <= N; i++) { 4temp[i] = pq[i]; 5} 6pq = temp; 7L = n; 8}
有了上面的方法,我們只需在插入和刪除方法中加入判斷語句即可。
④、多叉堆(瞭解即可)
在掌握了二叉堆的原理之後,將其改進為多叉堆只需要做幾個改動。下面直接放程式碼,有興趣的朋友可以自己動手。
1private void swim(int k, int d) {// d叉堆:(k+d-2)/d為d叉堆第k個節點的父節點 2while (k > 1 && less((k + d - 2) / d, k)) { 3exch((k + d - 2) / d, k); 4k = (k + d - 2) / d; 5} 6} 7 8private void sinkM(int k, int d) {// d叉堆 9while (d * k - (d - 2) <= N) {// d叉堆k節點的第一個子節點 10int j = d * k - (d - 2); 11int flag = k; 12while (j <= N && j <= d * k + 1) { 13if (less(flag, j)) { 14flag = j; 15} 16j++; 17} 18if (flag == k) {// flag沒有改變 19break; 20} 21exch(k, flag); 22k = flag; 23} 24}
二、堆排序(非降序):
(示意圖2)
堆排序的sink()方法經過修改sink(a,b)中a是被排序的元素,b為排序的最大範圍(修改之前排序的最大範圍為元素總個數)。
1public void sort(Comparable[] a) {//堆排序 2int n=N; 3for(int k=n/2;k>=1;k--) { 4sink(k,n); 5} 6while(n>1) { 7exch(1,n--); 8sink(1,n); 9} 10}
1、heap construction(堆的構造)
可以看到在for迴圈中,我們只掃描了陣列一半元素,因為我們跳過了大小為1的子堆,每次對一個節點排序時,以該節點為根節點的子堆就是有序的,所以我們最後會得到一個堆有序的二叉堆。
2、sortdown(下沉排序)
下沉排序每次選出最大的元素放入陣列空出的位置,這有點像選擇排序,但所需的比較要小得多,因為堆提供了一種從未排序部分找到最大元素的有效方法。
三、java程式碼展示(所有程式碼)
1 public class MaxPQ<Key extends Comparable<Key>> { 2private Key[] pq; 3private static int N = 0;// 元素個數 4private static int L;// 陣列長度(不包括0) 5 6public MaxPQ(int maxN) { 7pq = (Key[]) new Comparable[maxN + 1]; 8L = maxN; 9} 10 11public boolean isEmpty() { 12return N == 0; 13} 14 15public int size() { 16return N; 17} 18 19public void insert(Key v) {// 二叉堆 20if (N == L) { 21resize(2 * N + 1); 22System.out.println("resize Double"); 23} 24pq[++N] = v; 25swim(N); 26} 27 28public void insert(Key v, int d) {// d叉堆 29if (N == L) { 30resize(2 * N + 1); 31System.out.println("resize Double"); 32} 33pq[++N] = v; 34swim(N, d); 35} 36 37public Key delMax() { 38Key max = pq[1]; 39exch(1, N--); 40pq[N + 1] = null; 41sink(1); 42if (N > 0 && N == L / 4) { 43resize(L / 2); 44System.out.println("resize 1/2"); 45} 46return max; 47} 48 49public Key delMax(int d) { 50if (N == 0) { 51System.out.println("none!"); 52} 53Key max = pq[1]; 54exch(1, N--); 55pq[N + 1] = null; 56sinkM(1, d); 57if (N > 0 && N == L / 4) { 58resize(L / 2); 59System.out.println("resize 1/2"); 60} 61return max; 62} 63 64private void swim(int k) {// 二叉堆 65while (k > 1 && less(k / 2, k)) { 66exch(k / 2, k); 67k = k / 2; 68} 69} 70 71private void swim(int k, int d) {// d叉堆:(k+d-2)/d為d叉堆第k個節點的父節點 72while (k > 1 && less((k + d - 2) / d, k)) { 73exch((k + d - 2) / d, k); 74k = (k + d - 2) / d; 75} 76} 77 78private void sink(int k) {// 二叉堆 79while (2 * k <= N) { 80int j = 2 * k; 81if (j < N && less(j, j + 1)) { 82j++; 83} 84if (!less(k, j)) { 85break; 86} 87exch(k, j); 88k = j; 89} 90} 91 92private void sinkM(int k, int d) {// d叉堆 93while (d * k - (d - 2) <= N) {// d叉堆k節點的第一個子節點 94int j = d * k - (d - 2); 95int flag = k; 96while (j <= N && j <= d * k + 1) { 97if (less(flag, j)) { 98flag = j; 99} 100j++; 101} 102if (flag == k) {// flag沒有改變 103break; 104} 105exch(k, flag); 106k = flag; 107} 108} 109 110private void resize(int n) { 111Key[] temp = (Key[]) new Comparable[n + 1]; 112for (int i = 1; i <= N; i++) { 113temp[i] = pq[i]; 114} 115pq = temp; 116L = n; 117} 118 119private void exch(int i, int j) { 120Key temp = pq[i]; 121pq[i] = pq[j]; 122pq[j] = temp; 123} 124 125private boolean less(int i, int j) { 126return pq[i].compareTo(pq[j]) < 0; 127} 128 129public void sort(Comparable[] a) {//堆排序 130int n=N; 131for(int k=n/2;k>=1;k--) { 132sink(k,n); 133} 134while(n>1) { 135exch(1,n--); 136sink(1,n); 137} 138} 139 140private void sink(int k, int n) {//二叉樹 從k到n排序 141while (2 * k <= n) { 142int j = 2 * k; 143if (j < n && less(j, j + 1)) { 144j++; 145} 146if (!less(k, j)) { 147break; 148} 149exch(k, j); 150k = j; 151} 152} 153 154public static void main(String[] args) { 155MaxPQ mpq = new MaxPQ<>(3); 156mpq.insert(1); 157mpq.insert(2); 158mpq.insert(3); 159mpq.insert(4); 160mpq.insert(5); 161mpq.insert(6); 162mpq.insert(7); 163mpq.insert(8); 164mpq.insert(9); 165mpq.insert(10); 166mpq.insert(11); 167mpq.sort(mpq.pq); 168for(int i=1;i<=N;i++) { 169System.out.println(mpq.pq[i]); 170} 171/*for (int i = 1; i <= 13; i++) { 172System.out.println(mpq.delMax()); 173}*/ 174} 175 176 }