Skip to content

Latest commit

 

History

History
120 lines (77 loc) · 4.42 KB

算法-堆排序.md

File metadata and controls

120 lines (77 loc) · 4.42 KB

堆排序

简介

(二叉)堆是一个数组,它可以被看成是一个近似的完全二叉树。

完全二叉树:一棵二叉树中,只有最下面两层结点的度可以小于 2,并且最下一层的叶结点集中在靠左的若干位置上。这样的二叉树称为完全二叉树

结构图示

结构图

最大堆,最小堆

二叉堆可以分为最大堆和最小堆:

  • 最大堆:除了根以外的所有节点 i 都要满足 A[PARENT(i)]>=A[i],也就是说某个节点的值至多与其父节点一样大,堆中最大元素存放在根节点中
  • 最小堆:除了根以外的所有节点 i 都要满足 A[PARENT(i)]<=A[i],最小堆中最小的元素存放在根节点中

实现

维护堆的性质

MAX-HEAPIFY 通过让 A[i] 的值在最大堆中下沉,从而使得以下标 i 为根节点的子树重新遵循最大堆的性质。

该操作是按照堆的结构逐层下沉,最复杂的情况是从根节点逐层下沉到叶节点,所以该操作的时间复杂度主要取决于堆的高度,复杂度为 O(lgn)

maxheapify

建堆

我们可以用自底向上的方法利用过程 MAX-HEAPIFY 把数组转换为最大堆。

build-max-heap

当 i>A.length/2 时,都是叶节点,叶节点只有一个节点没有建最大堆的必要

如果 i 从 1 遍历至 A.length/2 ,那么有可能导致创建的堆不满足最大堆的性质,使得根节点不是数组中最大的值。

例如考虑数组:[24,15,7,5,43,87,34],如果从根节点开始遍历,那么数组最大值 87 无法通过建堆操作移动至根节点,如下所示:

示例1

堆排序算法

初始的时候,堆排序算法利用建堆操作将数组建成最大堆,因为数组中的最大元素总在根节点中,通过把它与 A[n] 进行交换,我们可以让该元素放到正确的位置。

每次循环,通过交换最大值的方式,可以将数组最大值,第二个最大值...第 n 最大值依次放到正确的位置。

堆排序算法的核心就是交换最大值,所以该算法每次循环都能确定好一个第 n 个最大值的位置。

heapsort

复杂度

O(nlgn)

因为每次调用建堆 BUILD-MAX-HEAP 操作的时间复杂度是 O(n),维护堆性质的操作 MAX-HEAPIFY 的时间复杂度为 O(lgn),所以总的复杂度为 O(nlgn)

是否稳定

不稳定

堆排序算法无法判断数组是否已经排序好,所以当数组已经排序好时,堆排序还是会继续执行,例如数组[1,1,1,1],堆排序无法知晓数组是否需要排序,所以依然会交换相等的元素,从这个角度看堆排序是不稳定排序。

其次,堆排序交换元素不是基于相邻元素交换的算法,所以对于相等元素之间的相对位置无法做到控制。例如,反过来考虑冒泡排序,针对于数组 [5,2,5],当数组交换 5,2 后变成 【2,5,5】,这时通过比较 5,5 即可确定不需要交换元素。 堆排序不是基于相邻元素比较的算法,所以当数组中存在 5,5 相等元素时,可能第一个 5 在堆的左分支中,第二个 5 在堆的右分支中,两个相等元素交换相对位置是有可能的。

go 实现

// 堆排序-算法导论版本
type HeapSortAlgorithms struct {
	Value []int
}

func (s *HeapSortAlgorithms) Sort() ([]int, error) {
	s.buildMaxHeap()
	for i := len(s.Value) - 1; i > 0; i-- {
		s.Value[i], s.Value[0] = s.Value[0], s.Value[i]
		s.maxHeapify(0, i)
	}
	return s.Value, nil
}

func (s *HeapSortAlgorithms) maxHeapify(t, len int) {
	if t >= len {
		return
	}
	largest, left, right := t, Left(t), Right(t)

	if left < len && s.Value[left] > s.Value[largest] {
		largest = left
	}

	if right < len && s.Value[right] > s.Value[largest] {
		largest = right
	}

	if largest != t {
		s.Value[t], s.Value[largest] = s.Value[largest], s.Value[t]
		s.maxHeapify(largest, len)
	}
}

func (s *HeapSortAlgorithms) buildMaxHeap() {
	for i := len(s.Value)/2 - 1; i >= 0; i-- {
		s.maxHeapify(i, len(s.Value))
	}
}

func Left(t int) int {
	return 2*t + 1
}

func Right(t int) int {
	return 2*t + 2
}