算法导论学习笔记03——堆与堆排序

目录

1 堆

1.1 堆的定义

1.2 大根堆、最大优先队列

2 堆排序

3  C代码与测试

3.1 堆排序C代码

3.2  测试代码


        首先介绍什么是堆,以及大(小)根堆和最大(小)优先队列;然后介绍堆排序算法;最后给出堆排序的C代码和测试脚本。

1 堆

1.1 堆的定义

        (二叉堆)是一个数组,它可以被看成一个近似完全的二叉树,树上的每一个节点对应数组中的一个元素。除了底层外,该树是完全充满的,而且是从左向右填充。如图1(a)所示的二叉树可以使用图1(b)的二叉堆存储。

图1

         当二叉堆中第一个元素的下标为0时,那么对于任意一个节点i

            左孩子:2i+1

            右孩子:2i+2

            父亲:(i-1)/2

        对于一个d叉堆,其孩子节点的范围是[id + 1, (i+1)d],父亲为(i-1)/d。(注:这里的堆中的第一个元素下标为0,而书中第一个元素下标为1,因而给出的孩子和父亲的结论不同)。

1.2 大根堆、最大优先队列

        在堆结构中,如果除根节点外所有节点都满足 value(i)\leqslant value(PARENT(i)),称这个堆是一个大根堆。图1中展示的就是一个大根堆。从大根堆的定义中可以看到,任何一个子树中,子树的根节点是这个子树的最大节点。最大优先队列是大根堆的一个应用,集合S是满足大根堆特性的一个最大优先队列,其具有以下4中操作:

  1. INSERT(S, i): 把节点i插入到集合S中

  2. MAXIMUN(S): 返回S中具有最大值的元素

  3. EXTPACT-MAX(S): 去掉S中具有最大值的元素

  4. INCREASE-KEY(S, i, k): 将节点i的值增加到k

        上述4种操作中,第2条MAXIMUN(S)只需要返回A[0]的值即可,这个操作不会改变最大优先队列原有的结构,时间复杂度O(1)。而1、3、4都会影响到最大优先队列的结构,可以在O(lgn)的时间复杂度下完成操作。

        INCREASE-KEY(S, i, k): 将一个元素的值增加到k,最大优先队列在设计时只允许一个关键字增加而不允许其减小。首先将这个元素的值替换,然后循环比较节点i和i的父节点,如果i值大,则与父节点交换,否则结束循环。将图1所示最大优先队列中节点9的值增加到15,其维护过程如图2:

图2

        在上述过程中,插入替换节点耗时O(1),调整位置时,每次比较上升一层,最坏的情况比较lg(n)次,所以时间复杂度为O(lg(n))。伪代码如下:

INCREASE-KEY(S, i, key)
    if key < S[i]
        err "new key is smaller than current key"
    S[i] = key
    while i > 0 and S[PARENT(i)] S[i]
        exchange S[i] with S[PARENT(i)]
        i = PARENT[i]

        INSERT(S, i): 把元素i插入到集合S中。插入一个元素可以分成2步:首先在队列的最后加入一个最小的值-\infty,因为-\infty一定小于它的父节点,所以不会影响到最大优先队列的性质。然后在调用INCREASE-KEY函数,将-\infty增加到i.key。增加一个节点时间复杂度为O(1),INCREASE-KEY的时间复杂度为O(lg(n)),所以整体的时间复杂度为O(lg(n))

        EXTPACT-MAX(S): 去掉S中具有最大值的元素。最大优先队列只能从根节点删除(弹出)一个节点。当根节点被删除时,其大根堆性质遭到了破坏·,需要调用方法MAX-HEAPIFY(A, i)来维护堆的性质。MAX-HEAPIFY在调用时,我们假设i节点的左孩子和右孩子都是大根堆,但节点i可能小于它的某个孩子,从而破坏大根堆的性质。在调用MAX-HEAPIF(A, i)时,比较i和它的左右孩子,如果i小于它的孩子,则max(i.right_child, i.left_child)与i交换。交换可能造成左/右子树大根堆性质遭到破坏,递归调用MAX-HEAPIFY,直到i大于它的左右节点时停止。MAX-HEAPIFY(A, i)伪代码如下:

MAX-HEAPIFY(A, i)
    l = LEFT(i)
    r = RIGHT(i)
    if l <= heap-size and A[l] > A[i]
        largest = l
    else largest = i
    if r <= heap-size and A[r] > A[largest]
        largest = r
    if largest != i
        exchange A[i] with A[largest]
        MAX-HEAPIFY(A, largest)

        MAX-HEAPIFY每次递归时,较上一次下降一层,因此最多调用lg(n)次,时间复杂度O(lg(n))

        EXTPACT-MAX(S)分3步:① 返回A[0]节点的值; ② 将A[0]赋值为A[heap-size]; ③ 调用MAX-HEAPIFY(A, 0)对大根堆的性质进行维护。①②两步的时间都是O(1),所以EXTPACT-MAX(S)的时间复杂度与MAX-HEAPIFY相同为O(lg(n))

2 堆排序

        堆排序满足空间原址性和稳定性。空间原址性:任何时候都只需要常数个额外空间存放临时数据。稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,经过排序,这些记录的相对次序保持不变。

        对于一个大根堆,每调用一次EXTPACT-MAX,得到当前堆中的最大数字,堆的长度从堆末尾减1。如果将EXTPACT-MAX得到的数填充到堆末尾减少的位置,那么经过n-1次操作后,便实现了将大根堆从小到大的原址排序。EXTPACT-MAX的时间复杂度时O(lg(n)),需要进行n - 1次操作,所以总时间复杂度O(nlg(n))

        那么堆排序需要解决的问题就只剩下如何将输入的数构成一个二叉堆——建堆。下面提供2中建堆的方法。

        第一种:从输入数组的第二个元素A[1]开始,直到最后一个元素,循环调用INSERT(S, i),把每一个元素插入到堆中。INSERT的时间复杂度为O(lg(n)),有n-1个元素需要插入,时间复杂度非紧确上界为O(nlg(n))

        第二种:自底向上的使用过程MAX-HEAPIFY把一个大小为size的数组转换为大根堆。当用数组表示存储n个元素的堆时,叶子结点的下标分别为n/2,n/2+1,...n。因此从n/2 - 1到0对每个结点调用MAX-HEAPIFY即可将输入数组转换为大根堆。MAX-HEAPIFY时间复杂度为O(lg(n)),需要调用n/2次,所以粗略的估算其非紧确上界为O(nlg(n))(实际上时间复杂度为O(n),但粗略估算值O(nlg(n))并不会影响堆排序的整体时间复杂度计算)。伪代码如下:

    HEAPSORT(A)
      BUILD-MAX-HEAP(A)
      for i = A.heap-size - 1 to 1
          exchange A[0] with A[i]
	  A.heap-size = A.heap-size - 1
	  MAX-HEAPIFY(A, 0)

 

3  C代码与测试

3.1 堆排序C代码

/*
    MAX-HEAPIFY(A, i)
      l = LEFT(i)
      r = RIGHT(i)
      if l < A.heap-size and A[i] < A[l]
          largest = l
      else largest = i
      if r < A.heap-size and A[largest] < A[r]
          largest = r
      if larget != i
          exchange A[i] with A[largest]
	  MAX-HEAPIFY(A, largest)
 */
#define LEFT(i) (2 * (i) + 1)
#define RIGHT(i) (2 * (i) + 2)
#define exchange(p1, p2) \
		do { \
			int tmp; \
			tmp = *p1; \
			*p1 = *p2; \
			*p2 = tmp; \
		} while(0)

int max_heapify(int A[], int i, int size)
{
	int l, r, largest;

	l = LEFT(i);
	r = RIGHT(i);
	if (l < size && A[i] < A[l]) {
		largest = l;
	} else {
		largest = i;
	}
	if (r < size && A[largest] < A[r]) {
		largest = r;
	}
	if (largest != i) {
		exchange(&A[i], &A[largest]);
		max_heapify(A, largest, size);
	}
	return 0;
}
/*
    BUILD-MAX-HEAP(A)
      for i = (A.heap-size / 2 - 1) downto 0
          MAX-HEAPIFY(A, i)
 */
int build_max_heap(int A[], int size)
{
	int i;
	for (i = size / 2 - 1; i >= 0; i--) {
		max_heapify(A, i, size);
	}
	return 0;
}
/*
    HEAPSORT(A)
      BUILD-MAX-HEAP(A)
      for i = A.heap-size - 1 to 1
          exchange A[0] with A[i]
	  A.heap-size = A.heap-size - 1
	  MAX-HEAPIFY(A, 0)
 */
int heap_sort(int A[], int size)
{
	int i;

	build_max_heap(A, size);
	for (i = size - 1; i >= 0; i--) {
		exchange(&A[i], &A[0]);
		size --;
		max_heapify(A, 0, size);
	}
	return 0;
}

int sort(int n, int A[])
{
	heap_sort(A, n);
	return 0;
}

3.2  测试代码

        程序输入参数为待排序数组,输出为排序后结果,main函数如下;

#include <stdio.h>
#include <stdlib.h>

extern int sort(int num, int arr[]);

int get_number(int num, int **arr, char *argv[])
{
	int i;
	
	*arr = (int *)malloc(sizeof(int) * num);
	if (NULL == *arr) {
		printf("Error: malloc failed\n");
		return -1;
	}
	for (i = 0; i < num; i ++) {
		(*arr)[i] = atoi(argv[i + 1]);
	}
	return 0;
}

int print_number(int num, int arr[])
{
	int i;
	for (i = 0; i < num; i++) {
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

int main(int argc, char *argv[])
{
	int num, *arr;

	num = argc - 1;
	if (get_number(num, &arr, argv)) {
		return -1;
	}
	if (sort(num, arr)) {
		return -1;
	}
	print_number(num, arr);
	free(arr);
	return 0;
}

        使用python脚本对程序进行测试,测试脚本如下:

import os
import random

def do_test(func_str, arg, res):
    cmd = func_str + " " + arg
    ret = os.popen(cmd).read().strip()
    if res != ret:
        print "Error case\n" + "case: " + arg
        print "res:  " + ret
        return -1
    return 0

MAX = 2147483647
MIN = -2147483648
# test 10000 times
TIMES = 10000
# we will input 'number_len', which is between 1 and MAX_NUMBER, numbers into the sort program
MAX_NUMBER = 10000

if __name__ == '__main__':
    func = "./sort"
    for i in range(TIMES):
        number_len = random.randint(1, MAX_NUMBER)
	# get input number array
        number = [random.randint(MIN, MAX) for i in range(number_len)]
	# get sorted input number array
        number_s = sorted(number)
	# exchange number to string, as input srting
        arg = ' '.join(str(n) for n in number)
	# exchange sorted number to string, as result
        res = ' '.join(str(n) for n in number_s)
	# do test
        ret = do_test(func, arg, res)
        if ret == -1:
            break
    if ret != -1:
        print "test success"

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值