堆(heap)
堆通常是一個可以被看做一棵樹的陣列物件。
- 堆中某個節點的值總是不大於或不小於其父節點的值
- 堆總是一棵完全二叉樹
將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。
快速排序
快速排序演算法透過多次比較和交換來實現排序,其排序流程如下:
(1)首先設定一個分界值,透過該分界值將陣列分成左右兩部分。
(2)將大於或等於分界值的資料集中到陣列右邊,小於分界值的資料集中到陣列的左邊。此時,左邊部分中各元素都小於或等於分界值,而右邊部分中各元素都大於或等於分界值。
(3)然後,左邊和右邊的資料可以獨立排序。對於左側的陣列資料,又可以取一個分界值,將該部分資料分成左右兩部分,同樣在左邊放置較小值,右邊放置較大值。右側的陣列資料也可以做類似處理。
(4)重複上述過程,可以看出,這是一個遞迴定義。透過遞迴將左側部分排好序後,再遞迴排好右側部分的順序。當左、右兩個部分各資料排序完成後,整個陣列的排序也就完成了。
func Sort(data Interface)
排序思想:
- 當元素數目小於等於12時進行插入排序
- 當元素數目大於12同時閾值為0時進行堆排序
- 當元素數目大於12同時閾值不為0時進行快速排序,返回初次排序的分界值後重新進行元素數目判斷排序
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less 比較索引為i的元素a[i]與索引為j的元素a[j],若.a[i]<a[j]返回true,否則返回false.
Less(i, j int) bool
// Swap 用索引i和j交換元素.
Swap(i, j int)
}
- 呼叫sort.Sort()進行排序
/**
Sort對資料進行排序
它對data.Len進行一次呼叫以確定n,對data.Less和data.Swap進行O(n*log(n))呼叫。 不能保證排序是穩定的。
*/
func Sort(data Interface) {
n := data.Len()
quickSort(data, 0, n, maxDepth(n))
}
- 開始排序
func quickSort(data Interface, a, b, maxDepth int) {
for b-a > 12 {
//閾值為0時進行堆排序
if maxDepth == 0 {
heapSort(data, a, b)
return
}
maxDepth--
mlo, mhi := doPivot(data, a, b)
// 避免對較大的子問題進行遞迴可確保堆疊深度最多為lg(b-a)。
if mlo-a < b-mhi {
quickSort(data, a, mlo, maxDepth)
a = mhi // i.e., quickSort(data, mhi, b)
} else {
quickSort(data, mhi, b, maxDepth)
b = mlo // i.e., quickSort(data, a, mlo)
}
}
if b-a > 1 {
/*
用6做間隔點,它能使用這種簡化方式編寫是因為b-a<=12
如果元素大於6,則先將6之後的元素a[j]與索引值j-6相對應的元素a[j-6]進行簡單比較,當a[j-6]>a[j]時進行置換,即data.Less(i, i-6)為true
然後進行插入排序
*/
for i := a + 6; i < b; i++ {
if data.Less(i, i-6) {
data.Swap(i, i-6)
}
}
//插入排序
insertionSort(data, a, b)
}
}
- 當元素數目小於等於12時進行插入排序
// 插入排序
func insertionSort(data Interface, a, b int) {
//從小到大排序, 從第一個元素開始,與它前面的元素相比較,如果比前面小則置換, 即data.Less(j,j-1)為true
for i := a + 1; i < b; i++ {
for j := i; j > a && data.Less(j, j-1); j-- {
data.Swap(j, j-1)
}
}
}
- 當元素數目大於12同時maxDepth(閾值)為0時進行堆排序
//siftDown在data [lo,hi]上實現了heap屬性。
//first是堆根所在的陣列的偏移量。
func siftDown(data Interface, lo, hi, first int) {
root := lo
// 開始下沉
for {
// 定位左孩子節點位置
child := 2*root + 1
if child >= hi {
break
}
// 如果右孩子節點比左孩子大,則定位到右孩子
if child+1 < hi && data.Less(first+child, first+child+1) {
child++
}
// 如果孩子節點小於或等於父節點,則下沉結束
if !data.Less(first+root, first+child) {
return
}
// 父節點進行下沉
data.Swap(first+root, first+child)
root = child
}
}
//堆排序
func heapSort(data Interface, a, b int) {
first := a
lo := 0
hi := b - a
// 構建最大堆.
for i := (hi - 1) / 2; i >= 0; i-- {
siftDown(data, i, hi, first)
}
// 進行堆排序.
for i := hi - 1; i >= 0; i-- {
// 把堆頂元素與最後一個元素交換
data.Swap(first, first+i)
// 把打亂的堆進行調整恢復堆的特性
siftDown(data, lo, i, first)
}
}
- 當元素數目大於12時進行快速排序
// meanOfThree將三個值data [m0],data [m1],data [m2]的中值移到data [m1]中。
func medianOfThree(data Interface, m1, m0, m2 int) {
// 排序3個元素
/*
先將data[m0]與data[m1]比較排序,然後data[m1]與data[m2]比較排序,最後再進行一次data[m0]與data[m1]比較排序得到data[m0] <= data[m1] <= data[m2]
*/
if data.Less(m1, m0) {
data.Swap(m1, m0)
}
// 此時data[m0] <= data[m1]
if data.Less(m2, m1) {
data.Swap(m2, m1)
// 此時data[m0] <= data[m2] && data[m1] < data[m2]
if data.Less(m1, m0) {
data.Swap(m1, m0)
}
}
// now data[m0] <= data[m1] <= data[m2]
}
//快速排序
func doPivot(data Interface, lo, hi int) (midlo, midhi int) {
m := int(uint(lo+hi) >> 1) // 這樣寫可以避免整數溢位。
if hi-lo > 40 {
// Tukey's ``Ninther,'' median of three medians of three.
s := (hi - lo) / 8
medianOfThree(data, lo, lo+s, lo+2*s)
medianOfThree(data, m, m-s, m+s)
medianOfThree(data, hi-1, hi-1-s, hi-1-2*s)
}
// 使data[m0] <= data[lo] <= data[hi-1]
medianOfThree(data, lo, m, hi-1)
// Invariants are:
// data[lo] = pivot (set up by ChoosePivot)
// data[lo < i < a] < pivot
// data[a <= i < b] <= pivot
// data[b <= i < c] unexamined
// data[c <= i < hi-1] > pivot
// data[hi-1] >= pivot
pivot := lo
a, c := lo+1, hi-1
// 從前往後找出比data[pivot]大的第一個數,獲得此時的下標a,a>=c時結束
for ; a < c && data.Less(a, pivot); a++ {
}
b := a
for {
// 從前往後找出比data[pivot]大的第一個數,獲得此時的下標b,b>=c時結束
for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot
}
// 從後往前找出比data[pivot]小的第一個數,獲得此時的下標c,b>=c時結束
for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot
}
// 當b>=c時結束迴圈
if b >= c {
break
}
// data[b] > pivot; data[c-1] <= pivot
data.Swap(b, c-1)
b++
c--
}
// 如果hi-c <3,則存在重複項(按中位數9的屬性)。
// 讓我們更加保守一些,將border設定為5。
protect := hi-c < 5
if !protect && hi-c < (hi-lo)/4 {
// Lets test some points for equality to pivot
dups := 0
if !data.Less(pivot, hi-1) { // data[hi-1] = pivot
data.Swap(c, hi-1)
c++
dups++
}
if !data.Less(b-1, pivot) { // data[b-1] = pivot
b--
dups++
}
// m-lo = (hi-lo)/2 > 6
// b-lo > (hi-lo)*3/4-1 > 8
// ==> m < b ==> data[m] <= pivot
if !data.Less(m, pivot) { // data[m] = pivot
data.Swap(m, b-1)
b--
dups++
}
// if at least 2 points are equal to pivot, assume skewed distribution
protect = dups > 1
}
if protect {
/*
防止大量重複
新增不變式:
data[a <= i <b]未檢查
data [b <= i <c] = pivot
*/
for {
//從後往前找出比data[pivot]小的第一個數,獲得此時的下標b,a>=b時結束 data[b-1]<data[pivot]
for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot
}
//從前往後找出比data[pivot]大的第一個數,獲得此時的下標b,a>=b時結束 data[a]>data[pivot]
for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot
}
if a >= b {
break
}
// data[a] == pivot; data[b-1] < pivot
data.Swap(a, b-1)
a++
b--
}
}
// Swap pivot into middle
data.Swap(pivot, b-1)
return b - 1, c
}
- 計算閾值
/**
maxDepth返回一個閾值,在該閾值處,快速排序應切換為堆排序。返回2*ceil(lg(n+1)).
*/
func maxDepth(n int) int {
var depth int
//透過右移後賦值計算n的二進位制位數,即ceil(lg(n+1))
for i := n; i > 0; i >>= 1 {
depth++
}
return depth * 2
}
問題
對快速排序部分protect := hi-c < 5接下來的處理過程不是很理解,請大佬多多指教。
參考文獻
本作品採用《CC 協議》,轉載必須註明作者和本文連結