1. 程式人生 > >排序演算法之——優先佇列經典實現(基於二叉堆)

排序演算法之——優先佇列經典實現(基於二叉堆)

許多應用都需要處理有序的元素,但有時,我們不要求所有元素都有序,或是一定要一次就將它們排序,許多情況下,我們會收集這些元素裡的最大值或最小值。

這種情況下一個合適的資料結構應該支援兩種操作:插入元素、刪除最大元素。

優先佇列與棧和佇列類似,但它有自己的奇妙之處。

在本文中,會講解基於二叉堆的一種優先佇列的經典實現方法(程式碼沒有任何難度,主要是理解思想)。

一、關於堆

1、堆的定義:

 資料結構二叉堆能很好地實現優先佇列的操作。在二叉堆中,每個元素都要保證大於等於另外兩個位置的元素,相應的,這些位置的元素又至少要大於等於陣列中的另外兩個元素。

將所有元素畫成一顆二叉樹,就能很容易看出這種結構。

(圖示1)

2、堆的演算法:

在堆有序的過程中我們會遇到兩種情況:

某個節點的優先順序上升,我們需要由下至上恢復堆的順序。

當某個節點的優先順序下降,我們需要由上至下恢復堆的順序。

在排序演算法中,我們只通過私有輔助函式來訪問元素:

1     private void exch(int i, int j) {
2         Key temp = pq[i];
3         pq[i] = pq[j];
4         pq[j] = temp;
5     }
6 
7     private boolean less(int i, int
j) { 8 return pq[i].compareTo(pq[j]) < 0; 9 }

①、由下至上的堆的有序化(上浮)

1     private void swim(int k) {// 二叉堆
2         while (k > 1 && less(k / 2, k)) {
3             exch(k / 2, k);
4             k = k / 2;
5         }
6     }

k/2即為k節點的父節點,當k大於k/2時交換兩者,並繼續與其父節點比較,直到找到合適的位置。

②、由上至下的堆的有序化(下沉)

 1     private void sink(int k) {// 二叉堆
 2         while (2 * k <= N) {
 3             int j = 2 * k;
 4             if (j < N && less(j, j + 1)) {
 5                 j++;
 6             }
 7             if (!less(k, j)) {
 8                 break;
 9             }
10             exch(k, j);
11             k = j;
12         }
13     } 

對於二叉樹,2*k即為k的左子節點,將左右子節點進行比較,再將父節點與較大的子節點比較,如果子節點大於父節點,就將他們交換,並繼續向下比較,直到找到合適的位置。

③、調整陣列大小

如果不知道元素的個數,任意在初始化時造成空間的浪費。我們需要創造一個函式,用來調整陣列的大小。

在插入方法中,如果空間已滿,就將陣列大小擴充套件為原來的兩倍。在刪除方法中,如果元素的個數小於陣列長度的1/4,就將陣列的長度減小一半。

1     private void resize(int n) {
2         Key[] temp = (Key[]) new Comparable[n + 1];
3         for (int i = 1; i <= N; i++) {
4             temp[i] = pq[i];
5         }
6         pq = temp;
7         L = n;
8     }

有了上面的方法,我們只需在插入和刪除方法中加入判斷語句即可。

④、多叉堆(瞭解即可)

 在掌握了二叉堆的原理之後,將其改進為多叉堆只需要做幾個改動。下面直接放程式碼,有興趣的朋友可以自己動手。

 1     private void swim(int k, int d) {// d叉堆:(k+d-2)/d為d叉堆第k個節點的父節點
 2         while (k > 1 && less((k + d - 2) / d, k)) {
 3             exch((k + d - 2) / d, k);
 4             k = (k + d - 2) / d;
 5         }
 6     }
 7 
 8     private void sinkM(int k, int d) {// d叉堆
 9         while (d * k - (d - 2) <= N) {// d叉堆k節點的第一個子節點
10             int j = d * k - (d - 2);
11             int flag = k;
12             while (j <= N && j <= d * k + 1) {
13                 if (less(flag, j)) {
14                     flag = j;
15                 }
16                 j++;
17             }
18             if (flag == k) {// flag沒有改變
19                 break;
20             }
21             exch(k, flag);
22             k = flag;
23         }
24     }

二、堆排序(非降序):

(示意圖2)

堆排序的sink()方法經過修改sink(a,b)中a是被排序的元素,b為排序的最大範圍(修改之前排序的最大範圍為元素總個數)。

 1     public void sort(Comparable[] a) {//堆排序
 2         int n=N;
 3         for(int k=n/2;k>=1;k--) {
 4             sink(k,n);
 5         }
 6         while(n>1) {
 7             exch(1,n--);
 8             sink(1,n);
 9         }
10     }

1、heap construction(堆的構造)

 可以看到在for迴圈中,我們只掃描了陣列一半元素,因為我們跳過了大小為1的子堆,每次對一個節點排序時,以該節點為根節點的子堆就是有序的,所以我們最後會得到一個堆有序的二叉堆。

2、sortdown(下沉排序)

 下沉排序每次選出最大的元素放入陣列空出的位置,這有點像選擇排序,但所需的比較要小得多,因為堆提供了一種從未排序部分找到最大元素的有效方法。

三、java程式碼展示(所有程式碼)

  1 public class MaxPQ<Key extends Comparable<Key>> {
  2     private Key[] pq;
  3     private static int N = 0;// 元素個數
  4     private static int L;// 陣列長度(不包括0)
  5 
  6     public MaxPQ(int maxN) {
  7         pq = (Key[]) new Comparable[maxN + 1];
  8         L = maxN;
  9     }
 10 
 11     public boolean isEmpty() {
 12         return N == 0;
 13     }
 14 
 15     public int size() {
 16         return N;
 17     }
 18 
 19     public void insert(Key v) {// 二叉堆
 20         if (N == L) {
 21             resize(2 * N + 1);
 22             System.out.println("resize Double");
 23         }
 24         pq[++N] = v;
 25         swim(N);
 26     }
 27 
 28     public void insert(Key v, int d) {// d叉堆
 29         if (N == L) {
 30             resize(2 * N + 1);
 31             System.out.println("resize Double");
 32         }
 33         pq[++N] = v;
 34         swim(N, d);
 35     }
 36 
 37     public Key delMax() {
 38         Key max = pq[1];
 39         exch(1, N--);
 40         pq[N + 1] = null;
 41         sink(1);
 42         if (N > 0 && N == L / 4) {
 43             resize(L / 2);
 44             System.out.println("resize 1/2");
 45         }
 46         return max;
 47     }
 48 
 49     public Key delMax(int d) {
 50         if (N == 0) {
 51             System.out.println("none!");
 52         }
 53         Key max = pq[1];
 54         exch(1, N--);
 55         pq[N + 1] = null;
 56         sinkM(1, d);
 57         if (N > 0 && N == L / 4) {
 58             resize(L / 2);
 59             System.out.println("resize 1/2");
 60         }
 61         return max;
 62     }
 63 
 64     private void swim(int k) {// 二叉堆
 65         while (k > 1 && less(k / 2, k)) {
 66             exch(k / 2, k);
 67             k = k / 2;
 68         }
 69     }
 70 
 71     private void swim(int k, int d) {// d叉堆:(k+d-2)/d為d叉堆第k個節點的父節點
 72         while (k > 1 && less((k + d - 2) / d, k)) {
 73             exch((k + d - 2) / d, k);
 74             k = (k + d - 2) / d;
 75         }
 76     }
 77 
 78     private void sink(int k) {// 二叉堆
 79         while (2 * k <= N) {
 80             int j = 2 * k;
 81             if (j < N && less(j, j + 1)) {
 82                 j++;
 83             }
 84             if (!less(k, j)) {
 85                 break;
 86             }
 87             exch(k, j);
 88             k = j;
 89         }
 90     }
 91 
 92     private void sinkM(int k, int d) {// d叉堆
 93         while (d * k - (d - 2) <= N) {// d叉堆k節點的第一個子節點
 94             int j = d * k - (d - 2);
 95             int flag = k;
 96             while (j <= N && j <= d * k + 1) {
 97                 if (less(flag, j)) {
 98                     flag = j;
 99                 }
100                 j++;
101             }
102             if (flag == k) {// flag沒有改變
103                 break;
104             }
105             exch(k, flag);
106             k = flag;
107         }
108     }
109 
110     private void resize(int n) {
111         Key[] temp = (Key[]) new Comparable[n + 1];
112         for (int i = 1; i <= N; i++) {
113             temp[i] = pq[i];
114         }
115         pq = temp;
116         L = n;
117     }
118 
119     private void exch(int i, int j) {
120         Key temp = pq[i];
121         pq[i] = pq[j];
122         pq[j] = temp;
123     }
124 
125     private boolean less(int i, int j) {
126         return pq[i].compareTo(pq[j]) < 0;
127     }
128     
129     public void sort(Comparable[] a) {//堆排序
130         int n=N;
131         for(int k=n/2;k>=1;k--) {
132             sink(k,n);
133         }
134         while(n>1) {
135             exch(1,n--);
136             sink(1,n);
137         }
138     }
139 
140     private void sink(int k, int n) {//二叉樹 從k到n排序
141         while (2 * k <= n) {
142             int j = 2 * k;
143             if (j < n && less(j, j + 1)) {
144                 j++;
145             }
146             if (!less(k, j)) {
147                 break;
148             }
149             exch(k, j);
150             k = j;
151         }
152     }
153 
154     public static void main(String[] args) {
155         MaxPQ mpq = new MaxPQ<>(3);
156         mpq.insert(1);
157         mpq.insert(2);
158         mpq.insert(3);
159         mpq.insert(4);
160         mpq.insert(5);
161         mpq.insert(6);
162         mpq.insert(7);
163         mpq.insert(8);
164         mpq.insert(9);
165         mpq.insert(10);
166         mpq.insert(11);
167         mpq.sort(mpq.pq);
168         for(int i=1;i<=N;i++) {
169             System.out.println(mpq.pq[i]);
170         }
171         /*for (int i = 1; i <= 13; i++) {
172             System.out.println(mpq.delMax());
173         }*/
174     }
175 
176 }