一、優先隊列
1.簡單介紹
優先隊列是一種抽象的數據結構,它與我們生活中的許多場景息息相關。比如我們的電腦或者手機,很多時候我們后臺會運行多個程序,當程序過多導致內存急劇減少時,如果沒有相關權限的設置,處理器會優先關閉掛在后臺比較久的,然后繼續往后臺加入程序。和基本的數據結構隊列和棧類似,不過優先隊列對元素的操作是有優先級的,和上面描述的一樣,它的核心是刪除最大元素和插入元素。
2.基本實現
優先隊列的實現有很多方式,常見的初級實現有基于有序數組和無序數組的,以及基于鏈表的,都能達到優先隊列的核心操作。但這些實現平均效率不高。這里就重點描述下基于堆的優先隊列的實現。
二、二叉堆
1.簡單介紹
二叉堆是一組能夠用堆有序的完全二叉樹排序的元素,并在數組中按層級儲存(不使用數組的第一個元素)。且根結點是堆有序的二叉樹中最大的節點。在一個二叉堆中,如果一個節點的位置為k,則它的父結點的位置為k/2,左右子節點的位置分別為2k、2k+1。
2.基本實現
①比較和交換
這個比較簡單,這里就不多說了,直接上代碼
// 比較兩個元素大小
private boolean less(int i,int j) {
return pq[i].compareTo(pq[j]) < 0;
}
// 交換兩個元素
private void exch(int i,int j) {
Key t = pq[i];
pq[i] = pq[j];
pq[j] = t;
}
②插入和刪除最大元素
插入元素就直接將元素增加到堆的末尾,然后進行上浮操作,使二叉堆有序。
刪除最大元素就直接從二叉堆頂端刪除,然后進行下沉操作。
所以接下來2個將堆有序化的操作是比較重要的,因為不管是刪除最大元素還是插入元素后,都要繼續保持二叉堆的有序性。
③由下至上的堆的有序化
這個也稱上浮。如果二叉堆的有序化因為某個結點變得比它的父結點更加大而打破,就需要進行不斷的上浮操作,也就是把當前結點與父結點交換位置直到二叉堆有序為止。看看下面的代碼
private void swim(int k) {
while (k > 1&&less(k/2,k)) {
exch(k/2,k);
k = k/2;
}
}
這里循環的條件判斷了只有當父結點比子結點更加小時交換才會繼續進行。
④由上至下的堆有序化
這個也稱下沉。與上浮類似,如果二叉堆的有序化因為某個結點變得比它的某個子結點更加小而打破,就需要進行不斷的下沉操作,把當前結點與其子結點中較大的部分進行交換(注意:二叉堆的左右結點是不需要按大小來排序的,但都比父結點小)。下面來看看代碼
private void sink(int k) {
while (2*k <= N) {
int j = 2*k;
if (j < N&&less(j,j+1)) {
j++;
}
if (!less(k,j)) break;
exch(k,j);
k = j;
}
}
這里注意一下第一個判斷,如果當前結點的左結點比右結點小,就進行++操作,這樣j就變成了右結點。繼續往下,如果當前結點比j結點大,就結束循環。否則就以j結點為當前結點繼續進行下一輪循環。
最后附上基于堆的優先隊列的全部代碼
/*
* 基于堆的優先隊列
*/
public class MaxPQ<Key extends Comparable<Key>> {
private Key[] pq; //基于堆的完全二叉樹
private int N = 0; // 在數組中儲存的范圍是[1...N] 不包括pq[0]
public MaxPQ(int MaxN) {
pq = (Key[]) new Comparable[MaxN+1];
}
// 判斷數組是否為空
public boolean isEmpty() {
return N == 0;
}
// 數組大小
public int size() {
return N;
}
// 插入元素
public void insert(Key v) {
pq[++N] = v;
swim(N);
}
// 刪除最大元素
public Key delMax() {
Key max = pq[1];
exch(1,N--);
pq[N+1] = null; //防止對象游離
sink(1);
return max;
}
// 上浮操作
private void swim(int k) {
while (k > 1&&less(k/2,k)) {
exch(k/2,k);
k = k/2;
}
}
// 下沉操作
private void sink(int k) {
while (2*k <= N) {
int j = 2*k;
if (j < N&&less(j,j+1)) {
j++;
}
if (!less(k,j)) break;
exch(k,j);
k = j;
}
}
// 比較兩個元素大小
private boolean less(int i,int j) {
return pq[i].compareTo(pq[j]) < 0;
}
// 交換兩個元素
private void exch(int i,int j) {
Key t = pq[i];
pq[i] = pq[j];
pq[j] = t;
}
}
然后看看優先隊列的各種實現的算法復雜度
數據結構 | 插入元素 | 刪除最大元素 |
---|---|---|
有序數組 | N | 1 |
無序數組 | 1 | N |
堆 | logN | logN |
相信對排序算法了解的朋友,可以看出基于堆的優先隊列就等同于堆排序,這里就不多說了,最后說一句題外話,不管算法與數據結構有關的學習資料多么全,關鍵還是在于自己的多思考,多花時間,行動是理想最高貴的表達,梅花香自苦寒來,太急于求成只會得不償失,這句話也是給我自己的一個提醒。