《算法导论》学习笔记(1)——堆与堆排序

         堆排序( Heapsort )是指利用“堆”这种数据结构所设计的一种排序算法

         堆是一种数据结构,是一个数组。它可以被看成一个近似的完全二叉树,树上的每一个结点对应数组中的一个元素。除了最底层外,即叶子结点,该树是完全充满的,而且是从左到右填充。

         最大堆的每个结点都要满足堆的性质,此外还有其他的约束:堆中的最大元素存放在根结点中,并且在任一子树中,该子树所包含的所有结点的值都不大于该子树根结点的值。

         堆排序就是利用最大堆的性质来排序。大致步骤如下:

         ①建最大堆

         ②将根结点(最大元素)与最后一个结点的元素互换,并将剩下的结点维护最大堆的性质。

         ③重复步骤②,直到将每个元素排完序。

下面就讲解几个重要过程:

         维护最大堆的性质:MaxHeapify

         传入参数:待排序数组、需要维护的下标、当前堆的大小。返回值:空

         每次在传入参数的下标、该下标的左、右孩子结点三者中选出最大的元素。如果最大元素是该下标,则满足最大堆性质,结束。否则,需要将父亲与最大的孩子交换位置,使得当前下标及其左右孩子满足最大堆的性质。之后递归调用,使得整个子树都满足最大堆的性质。

         特别需要注意,数组的大小和堆的大小是不一样的概念。堆的大小表示有多少个堆元素存储在数组中。在进行堆排序的过程中,每次需要把根结点元素与最后一个元素交换,这样堆的大小每次就会减一。而数组大小是不会变的。也就是:

0 ≤ 堆的大小 ≤ 数组的大小

                                                              

         建最大堆:BuildMaxHeap

         传入参数:待排序数组、当前堆的大小。返回值:空

         只需保证所有的非叶子结点满足最大堆的性质即可。非叶子结点的下标为从0到堆大小 / 2(向下取整)。

                 

         堆排序:HeapSort

         传入参数:待排序数组、当前堆的大小。返回值:空

         首先建堆,然后每次去除根结点元素(最大元素),然后维护最大堆的性质。如此循环,直到所有元素排序完毕。


         实现代码如下:

#include <iostream>
using namespace std;

typedef int ElemType;

int getLeftChild( int i ) //返回左孩子下标。注:数组从位置0开始
{
	return 2 * i + 1;
}

int getRightChild( int i ) //返回右孩子下标。注:数组从位置0开始
{
	return 2 * i + 2;
}

void swap( ElemType *a, ElemType *b ) //交换两个元素
{
	ElemType temp = *a;
	*a = *b;
	*b = temp;
}

/* 维护最大堆的性质。
 * 参数:待排序数组、需要维护的下标、当前堆的大小
 * 返回值:空
 */
void MaxHeapify( ElemType *A, int i, int num )
{
	int left = getLeftChild( i );
	int right = getRightChild( i );
	int largest; // 存放父结点、左右子节点三者的最大值的下标位置
	if( left <= num && A[left] > A[i] )
		largest = left;
	else
		largest = i;
	if( right <= num && A[right] > A[largest] )
		largest = right;
	if( largest != i )
	{
		swap( &A[i], &A[largest] );
		MaxHeapify( A, largest, num );
	}
}

/* 建最大堆
 * 传入参数:待排序数组、当前堆的大小
 * 返回值:空
 */
void buildMaxHeap( ElemType *A, int num )
{
	for( int i=num/2; i>=0; i-- )
		MaxHeapify( A, i, num );
}

/* 堆排序
 * 待排序数组、当前堆的大小
 * 返回值:空
 */
void heapSort( ElemType *A, int num )
{
	buildMaxHeap( A, num-1 );
	int HeapSize = num - 1; //当前堆的大小
	for( int i=num-1; i>0; i-- )
	{
		swap( &A[0], &A[i] );
		HeapSize--;
		MaxHeapify( A, 0, HeapSize );
	}
}

int main()
{
	ElemType A[10] = {1,3,6,2,4,8,0,9,7,5};
	heapSort( A, 10 );
	for( int j=0; j<10; j++ )
		cout << A[j] << " ";
	cout << endl;
	return 0;
}

时空复杂度:

    由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)。

    由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

    堆排序是就地排序,辅助空间为O(1), 它是不稳定的排序方法。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值