堆通常是一个可以被看做一棵树的数组对象。
- 堆中某个节点的值总是不大于或不小于其父节点的值
- 堆总是一棵完全二叉树
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
(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 协议》,转载必须注明作者和本文链接