許多應用程式都需要處理有序的元素,但不一定要求它們全部有序,或是不一定要一次就將它們排序。這種情況下,乙個合適的資料結構應該支援兩種操作:刪除最大元素和插入元素。這種資料結構叫做優先佇列。
通過插入一列元素然後乙個個地刪掉其中最小的元素,我們可以用優先佇列實現排序演算法。一種名為堆排序的重要排序演算法也來自基於堆的優先佇列的實現。
可以很容易想到,我們可以維護乙個陣列,插入操作可以直接將元素插到陣列末尾,刪除操作可以遍歷整個陣列,找到最大的然後刪除,並將末尾元素插入到刪除位置。
同樣很容易想到,在插入的時候直接將較大的元素向右移動一格,然後將元素插入,就像插入排序一樣。刪除操作則直接將陣列末尾的元素刪除。
使用無序序列是解決這個問題的惰性方法,我們僅在必要的時候才會採取行動;使用有序序列則是解決問題的積極方法,因為我們會未雨綢繆,使後續操作更高效。這兩種方法的兩個操作之一在最壞的情況下都需要線性時間來完成。
在二叉堆的陣列中,每個元素都要保證大於等於另兩個特定位置的元素,相應地,這些位置的元素又至少要大於等於陣列中的另兩個元素。
當一棵二叉樹的每個結點都大於等於它的兩個子結點時,它被稱為堆有序如果用指標來表示堆有序的二叉樹,那麼每個元素都需要三個指標來找到它的上下結點。完全二叉樹只用陣列而不需要指標就可以表示,並在陣列中按照層級儲存(不使用陣列的第乙個位置),位置 k 的結點的父結點為 ⌊k/
2⌋
\lfloor k / 2 \rfloor
⌊k/2
⌋ ,而它的兩個子節點的位置則分別是 2k 和 2k+1 。通過使用堆,我們可以實現對數級別的插入元素和刪除最大元素的操作。
堆的操作會首先進行一些簡單的改動,打破堆的狀態,然後再遍歷堆並按照要求將堆的狀態恢復,我們把這個過程叫做堆的有序化。在有序化的過程中我們會遇到兩個情況,當某個結點的優先順序上公升(或是在堆底加入乙個新的元素),我們需要由下至上恢復堆,當某個結點的優先順序下降(將根結點替換為乙個較小的元素),我們需要由上至下恢復堆。
**:
void
swim
(int k)
}
**:
void
sink
(int k)
}
swim() 表示乙個很有能力的新人加入組織並被逐級提公升(將能力不夠的上級踩在腳下),直到他遇到了乙個更強的領導。sink() 則類似於整個社團的領導退休並被外來者取代之後,如果他的下屬比他更厲害,他們的角色就會交換,這種交換會持續直到他的能力比其下屬都要強為止。
插入元素:我們將新元素加到陣列末尾,增加堆的大小並讓這個新元素上浮到合適的位置。
刪除最大元素:我們從陣列頂端刪除最大的元素,並將陣列的最後乙個元素放到頂端,減小堆的大小並讓這個元素下沉到合適的位置。
優先佇列由乙個基於堆的完全二叉樹表示。
**:
class
maxpq
void
swim
(int k)
}void
sink
(int k)
}void
sink
(int k,
int n)
}public
:maxpq
(int maxn)
maxpq
(std::vector<
int> pq)
bool
isempty()
intsize()
void
insert
(int v)
intdelmax()
void
sort()}};
對於乙個含有 n 個元素的基於堆的優先佇列,插入元素操作只需不超過 lgn使用有序或無序陣列的優先佇列的實現總是需要線性時間來完成其中一種操作,但基於堆的實現則能夠保證在對數時間內完成它們。這種差別使得我們能夠解決以前無法解決的問題。+1
lgn+1
lgn+
1 次比較,刪除最大元素的操作需要不超過 2lg
n2lgn
2lgn
次比較
由 n 個給定的元素構造乙個堆,我們當然可以在與 nlg
nnlgn
nlgn
成正比的時間內完成這項任務,只需從左至右遍歷陣列,用 swim() 保證掃瞄指標左側的所有元素已經是一棵堆有序的完全二叉樹即可,就像連續向優先佇列中插入元素一樣。
乙個更聰明更高效的辦法是從右至左用 sink() 函式構造子堆。陣列的每個位置都已經是乙個子堆的根結點了,sink() 操作對這些子堆也適用。如果乙個結點的兩個子結點都已經是堆了,那麼在該結點上呼叫 sink() 可以將它們變成乙個堆。
用下沉操作由 n 個元素構造堆只需少於 2n 從比較以及少於 n 次交換堆排序的主要工作都是在第二階段完成的,我們將堆中的最大元素刪除,然後放入堆縮小後陣列中空出的位置。
將 n 個元素排序,堆排序只需少於 2nl**:gn+2
n2nlgn+2n
2nlgn+
2n次比較(以及一般次數的交換)
見上方 sort()
堆排序在排序複雜性的研究中有著重要地位,因為它是我們所知的唯一能夠同時最優地利用空間和時間的方法。但現代系統的許多應用很少使用它,因為它幾乎無法利用快取。陣列元素很少和相鄰的其他元素進行比較,因此快取未命中的次數要遠遠高於大多數比較都在相鄰元素間進行的演算法,如快速排序、歸併排序,甚至是希爾排序。另一方面,用堆實現的優先佇列在現代應用程式中越來越重要,因為它能在插入操作和刪除最大元素操作混合的動態場景中保證對數級別的執行時間。
優先佇列和堆排序
1.從佇列到優先佇列 無論棧還是佇列,它們最重要最本質的操作都是入集合,出集合。佇列是在隊尾入隊 插入 在隊頭出隊 刪除 也即是說先出隊的是先入隊的,那麼存在這麼一種情況,如果排隊的人有著一種優先順序 與排隊順序無關的優先順序 那麼我們就需要一種新的資料結構 優先佇列。它需要插入操作和刪除優先順序最...
優先佇列 堆排序
一種支援刪除最大元素和插入元素兩種操作的資料結構叫做優先佇列。實現棧or佇列與實現優先佇列的最大不同在於效能的要求。對於棧和佇列,我們實現能在常數時間完成所有操作 而優先佇列,插入元素和刪除最大元素這兩個操作在最壞情況下需要線性時間完成 優先佇列的各種實現在在最壞情況下執行時間的增長數量級 資料結構...
堆排序 優先佇列
1.堆排序 a.堆的定義 n個元素序列當且僅當滿足以下關係時,稱之為堆。ki k2i且ki k2i 1 小根堆 ki k2i且ki k2i 1 大根堆 以下針對最大堆 b.維護堆的性質 max heapify通過讓a i 的值在最大堆中 逐級下降 a i 的值小於其左右孩子的值時 從而使得以i為根結...