算法导论笔记(三)

第六章 堆排序

堆的定义:

堆就是使用二叉树的结构来维护的一位数组。

堆

如图,给定一个数组A,可以用一个近似完全二叉树表示。树的根节点是A[1],这样给定一个结点的下标i,很容易计算得到它的父节点、左孩子、右孩子的下标:

PARENT(i)
    return i/2

LEFT(i)
    return 2*i

RIGHT(i)
    return 2*i+1

在大多数计算机上,通常将i的值左移一位表示2i,右移一位表示i/2。在堆排序中,这三个函数通常是以“宏”或者“内联函数”的方式实现。下面我们用宏实现这三个函数:

#define PARENT(i) (i)>>1
#define LEAF(i) (i)<<1
#define RIGHT(i) ((i)<<1)+1

二叉堆可以分为两种形式:最大堆和最小堆。

最大堆需要满足A[PARENT(i)]\geq A[i],也就是某个节点必须小于等于它的父节点。

最小堆则相反,满足A[PARENT(i)]\leq A[i],某个节点必须大于等于它的父节点

 

维护堆的性质

MAX-HEAPIFY是用于维护最大堆性质的重要过程。它的输入为一个数组A和一个下标i。在调用MAX-HEAPIFY时,我们假定根节点为LEFT(i)和RIGHT(i)的二叉树都是最大堆,但这时A[i]有可能小于其孩子,这样就违背了最大堆的性质。MAX-HEAPIFY通过让A[i]的值在最大堆中 “逐级下降”,从而保证最大堆的性质。实现过程如下:

void MAX_HEAPIFY(int *A, int len, int i) //len表示数组的长度
{
	int r = RIGHT(i);
	int l = LEAF(i);
	int largest = i;
	if(l <= len && A[l-1] > A[largest-1])
		largest = l;
	if(r <= len && A[r-1] > A[largest-1])
		largest = r;

	if(i != largest)
	{
		int temp = A[i-1];
		A[i-1] = A[largest-1];
		A[largest-1] = temp;
		MAX_HEAPIFY(A, len, largest);
	}
}

调用MAX_HEAPIFY(A, 10, 2)的算法过程如下:

保持最大堆性质

 

建堆

我们可以用自低向上的方法利用过程MAX-HEAPIFY把一个大小为n的数组A[1..n]转换成最大堆。由于子数组A[n/2+1..n]中的元素都是树的叶子结点。每个叶子结点都可以看成只包含一个元素的堆。过程BUILD-MAX-HEAP对树中的其他节点都调用一次MAX-HEAPIFY,就可以保证整棵树都是最大堆。实现过程如下:

void BUILD_MAX_HEAP(int *A, int len) //len表示数组A的长度
{
	for(int i = len/2; i > 0; i--)
	{
		MAX_HEAPIFY(A, len, i);
	}
}

建堆

 

堆排序

初始时候,利用BUILD-MAX-HEAP将输入数组A[1..n]建成最大堆。因为A[1]肯定是最大值,所以将A[1]与A[n]互换;再调用MAX-HEAPIFY将A[1..n-1]建成最大堆,将A[1]与A[n-1]互换,;重复此过程,直到堆的大小从n-1降到2。算法实现:

void HEAPSORT(int *A, int len)
{
	for (int i = 1; i < len; ++i)
	{
		int temp = A[0];
		A[0] = A[len - i];
		A[len - i] = temp;
		MAX_HEAPIFY(A, len - i, 1);
	}

}

图解:

堆排序

堆排序的所有实现代码

#include <iostream>

#define PARENT(i) (i)>>1
#define LEAF(i) (i)<<1
#define RIGHT(i) ((i)<<1)+1


void MAX_HEAPIFY(int *A, int len, int i)
{
	int r = RIGHT(i);
	int l = LEAF(i);
	int largest = i;
	if(l <= len && A[l-1] > A[largest-1])
		largest = l;
	if(r <= len && A[r-1] > A[largest-1])
		largest = r;

	if(i != largest)
	{
		int temp = A[i-1];
		A[i-1] = A[largest-1];
		A[largest-1] = temp;
		MAX_HEAPIFY(A, len, largest);
	}
}

void BUILD_MAX_HEAP(int *A, int len)
{
	for(int i = len/2; i > 0; i--)
	{
		MAX_HEAPIFY(A, len, i);
	}
}

void HEAPSORT(int *A, int len)
{
	for (int i = 1; i < len; ++i)
	{
		int temp = A[0];
		A[0] = A[len - i];
		A[len - i] = temp;
		MAX_HEAPIFY(A, len - i, 1);
	}

}

int main()
{
	int A[10] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
	BUILD_MAX_HEAP(A, 10);
	HEAPSORT(A,10);
	for(int i = 0; i < 10; ++i)
		std::cout<<A[i]<<" ";

	return 0;
}



优先队列

优先队列(priority queue)是一种用来维护由一组元素构成的集合S的数据结构,其中每个元素都有一个相关的值,称为关键字(key)。一个最大优先队列支持一下操作:

    INSERT(S,x):把一个元素插入集合S中。

    MAXIMUM(S):返回S中具有最大键字的元素。

    EXTRACT-MAX(S):去掉并返回S中的具有最大键字的元素。

    INCREASE-KEY(S, x, k):将元素x的关键字值增加到k,这里假设k的值不小于x的原关键字值。

相应的,最小优先队列也支持上述操作,只是返回的不是最大键字的元素,而是最小键字的元素。显然,优先队列可以用堆来实现,下面我们来讨论用堆如何实现最大优先队列的操作。

    过程HEAP-MAXIMUM可以在\Theta (1)时间内实现MAXMUM操作。伪代码:

HEAP-MAXIMUM(A)
    return A[1]

    过程HEAP-EXTRACT-MAX实现EXTRACT-MAX操作,其实就是堆排序HEAPSORT过程中的for循环中步骤——把第一个元素与最后的元素交换,然后在重新调用MAX-HEAPIFY使得保持最大堆的性质,时间复杂度为\Theta (lg n)。伪代码:

HEAP-EXTRACT-MAX(A)
    if A.heap-size<1
        error "heap underflow"
max=A[1]
A[1]=A[A.heap-size]
A.heap-size=A.heap-size-1
MAX-HEAPIFY(A,1)
return max

    过程HEAP-INCREASE-KEY能够实现INCREASE-KEY操作。我们将A[i]的值更新时,可能违反了最大堆的性质。因此,我们需要将A[i]不断的与父节点比较,直到其小于父节点为止。时间复杂度为\Theta (lg n),伪代码:

HEAP-INCREASE-KEY(A,i,key)
    if key < A[i]
        error "new key is smaller than current key"
    A[i] = key
    while i>1 and A[PARENT(i)]<A[i]
        exchange A[i] with A[PARENT(i)]
        i=PARENT(i)

实现过程:

    MAX-HEAP-INSERT能够实现INSERT操作。它的输入是要在最大堆中插入一个新的元素,算法的实现过程是:首先通过增加一个关键字-\infty来扩展最大堆,之后调用上面的HEAP-INCREASE-KEY为新节点设置对应的关键字,同时保持最大堆的性质。时间复杂度为\Theta (lg n)。伪代码:

MAX-HEAP-INSERT(A,key)
    A.heap-size = A.heap-size+1
    A[A.heap-size]=-∞
    HEAP-INCREASE-KEY(A,A.heap-size,key)

总之,在包好n个元素的堆中,所有优先队列的操作都可以在\Theta (lg n)时间内完成。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值