堆和堆排序

堆其实就是一个完全二叉树。
对中每一个节点的值都必须大于等于(小于)其子树中所有节点的值。
完全二叉树比较适合用数组来存储。

堆化可以从上向下,也可以从下向上

向堆中插入一个元素

将插入的数据放入数组的最后,并将新插入的数据从下向上开始交换,直到满足堆特性即可。(从下往上堆化)
代码实现(大顶堆)

func (s *Stack) Insert(target int) bool {
    if s.count >= s.max {
        return false
    }
    s.count++
    s.value[s.count] = target
    for i := s.count; i/2 > 0; {
        if s.value[i] > s.value[i/2] {
            s.Change(i, i/2)
            i = i / 2
        } else {
            break
        }
    }
    return true
}
删除堆顶元素

删除堆中的元素,若该元素有子节点,那么可以将其与最后的元素进行交换,并将交换后的元素“沉”下去,删除最后的元素(从上往下堆化)
代码实现(大顶堆)

func (s *Stack) RemoveTop() bool {
    if s.count <= 0 {
        return false
    }
    s.Change(1, s.count)
    s.count--
    i := 1
    var maxPos int
    for i * 2 <= s.count {
        maxPos = 2 * i
        if 2 * i + 1 <= s.count && s.value[2 * i + 1] > s.value[2 * i] {
            maxPos = 2 * i + 1
        }
        if s.value[i] < s.value[maxPos] {
            s.Change(i, maxPos)
        } else {
            break
        }
        i = maxPos
    }
    return true
}
如何基于堆实现排序

第一步:建堆
第一种方法:将原始数据重新插入另一个数组中逐步建堆(从下往上堆化)
第二种方法:下标从 2n​ 开始到 1 的数据进行堆化。(从上向下堆化)
时间复杂度为O(n)
第二步:排序
建堆结束之后,数组中的数据已经是按照大顶堆的特性来组织的,不断删除堆顶元素即可排好序。

堆排序整体的时间复杂度是 O(nlogn)
堆排序不是稳定的排序算法,因为在排序的过程,存在将堆的最后一个节点跟堆顶节点互换的操作,所以就有可能改变值相同数据的原始相对顺序。
节点的下标最好是从1开始,但是也可以从0开始,只不过父节点以及子节点的下标计算不同。

为什么快速排序要比堆排序性能好?

堆排序数据访问的方式没有快速排序友好:对排序对数据的访问都是2i,2i + 1来访问的,这对于CPU缓存是不友好的
对于同样的数据,在排序过程中,堆排序算法的数据交换次数要多于快速排序:比如说对于一个有序的数据,堆化可能会导致数据变得无序,而快速排序则是在降低数据集合的逆序度。

你可能感兴趣的:(堆和堆排序)