《算法导论》读书笔记-第六章-堆排序(HEAPSORT)

文章目录

    • 序言
      • 什么是堆
      • 最大堆和最小堆特性及二叉树的常用性质
    • 维护堆的性质
    • 建堆
    • 堆排序算法
    • 问题总结


序言

在算法中,排序算法是尤为重要,在多种排序算法中,从时间效率上来看的话,效率比较高的排序算法主要是:归并排序堆排序快速排序。前面两个的时间复杂度是Θ(nlgn),后面一个的期望时间复杂度为Θ(nlgn)。本文主要围绕堆排序展开讨论。


什么是堆

堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。且我们也是通常对堆进行操作的时候当作完全二叉树来进行操作。
《算法导论》读书笔记-第六章-堆排序(HEAPSORT)_第1张图片
图(a)表示二叉堆,图(b)表示二叉堆的数组对象。

最大堆和最小堆特性及二叉树的常用性质

对于最大堆和最小堆以及他们的特性,书上给出的定义是这样的:
最大堆的特性:除了根节点以外的每个节点i,有A[PARENT(i)] >= A[i];
最小堆的特性:除了根节点以外的每个节点i,有A[PARENT(i)] <=A[i]。
所以在最大堆中,最大元素该子树的根上;在最小堆中,最小元素在该子树的根上。
因为我们通常对堆处理当成二叉树来处理,所以我们需要知道二叉diu的基本性质:

  • 含有n个元素的二叉树的高度为lgn
  • 一个位置为i的结点,他的父结点为i / 2
  • 当用数组表示存储了n个元素的堆时,最后一个非叶子节点是:n/2;叶子节点的下标是:n/2+1,n/2+2,……,n
    本文讲述堆排序主要是围绕最大堆来展开讨论的。

维护堆的性质

所谓维护堆的性质,就是要维护当前节点的数值都要大于等于他的左子节点和右子节点所存的数值。书上引入了MAX_HEAPIFY的算法,通过让A[i]的值在最大堆中进行逐级下降,从而使下标为i的为根节点的子数保持最大堆的性质。

下图表示维护堆的性质的过程:

《算法导论》读书笔记-第六章-堆排序(HEAPSORT)_第2张图片

由书上的伪代码,本人将其转变为C语言的形式:

void max_heapify(int* array, int i, int length)
{
	//array表示传递给子函数的数组,i表示其下标
	int l, r, largest;
	int temp;
	l = LEFT(i);
	r = RIGHT(i);

	if (l <= length && array[l] > array[i])
		largest = l;
	else largest = i;
	if (r <= length && array[r] > array[largest])
		largest = r;
	if (largest != i)       //实现交换A[i]和A[largest]
	{
		temp = array[largest];
		array[largest] = array[i];
		array[i] = temp;
		max_heapify(array, largest,length);
	}
}

由代码可以看出,这个算法用到了递归,因为我们需要对以该节点为根节点的最大堆进行MAX_HEAPIFY操作。

建堆

有了对堆性质的维护,下面我们需要建最大堆。我们需要自下往上的利用MAX_HEAPIFY的过程将一个大小为n = A.length的数组转化为最大堆。将数组视为一颗完全二叉树,从其最后一个非叶子节点(n/2)开始调整。调整过程如下图所示:
《算法导论》读书笔记-第六章-堆排序(HEAPSORT)_第3张图片

由书上的伪代码,转变为C语言的形式:

void build_max_heap(int* array, int length)
{
	for (int i = length / 2; i > 0; i--) //从非叶子节点开始
	{
		max_heapify(array, i, length);
	}
}

堆排序算法

前面提到的max_heapify和build_max_heap都是为了为堆排序算法打基础,下面将讲述堆排序算法的过程:先调用创建堆函数将输入数组A[1…n]造成一个最大堆,使得最大的值存放在数组第一个位置A[1],然后用数组最后一个位置元素与第一个位置进行交换,并将堆的大小减1,并调用最大堆调整函数从第一个位置调整最大堆
《算法导论》读书笔记-第六章-堆排序(HEAPSORT)_第4张图片
《算法导论》读书笔记-第六章-堆排序(HEAPSORT)_第5张图片
根据书上的伪代码转为C语言代码:


void heap_sort(int* array, int length)
{
	int i = length,temp;
	build_max_heap(array, length);   //建造最大堆
	while (i>1)
	{
		temp = array[length];        
		array[length] = array[1];
		array[1] = temp;			//将该节点和根节点交换
		i--;						//长度减一
		max_heapify(array, 1, i);
	}
}

问题总结

在这一章的学习中,遇到几个不能理解的地方:

  • 空间原址性,如何看排序算法是不是原址的(书上说的概念没看懂,希望得到通俗易懂的答案)
  • 《算法导论》读书笔记-第六章-堆排序(HEAPSORT)_第6张图片

以上截图所针对的问题有两个
(1)为什么在MAX_HEAPIFY过程中的最坏情况为:子问题是A[i]的左孩子是满二叉树且恰比右孩子多一层。
(2)为什么运行时间的递归方程是:T(n) = T(2n / 3) + Θ(1)

针对这一章的内容就总结就在这里,最后的问题没有解决,未完待续~


针对上面提的问题,作出回答:

  • 空间原址性通俗的说:算法在处理的时候,只需要常数个存储空间,一般我们称这样的空间复杂度为 O(1)。堆排序是在原数组上直接操作,只需要一些循环变量,所以可以说空间复杂度是 O(1)。
  • 根据资深大佬来说,如果说时间复杂度的表达式如果不随着输入规模的改变而改变的话,就不存在最坏情况这一说。

你可能感兴趣的:(算法导论)