ACM基础(四):排序之堆排序(用数组存储层序遍历的堆)


一、用数组存储层序遍历的堆

1.按层序遍历

在这里插入图片描述
在这里插入图片描述

2.算左子节点、算右子节点、算父节点的下标

(1)伪代码结果

在这里插入图片描述
i为本结点,j为所求结点,采用[1,n]

  • LEFT(i) = 2 ∗ i \small{\text{LEFT(i)}}=2*i LEFT(i)=2i

  • RIGHT(i) = 2 ∗ i + 1 \small{\text{RIGHT(i)}}=2*i+1 RIGHT(i)=2i+1

  • PARENT(i) = ⌊ i / 2 ⌋ \small{\text{PARENT(i)}}=\lfloor i/2 \rfloor PARENT(i)=i/2

(2)C++

i为本结点,j为所求结点,采用[0,n-1]

  • LEFT(i): j + 1 = 2 ( i + 1 ) , 则 j = 2 ∗ i + 1 j+1=2(i+1),则j=2*i+1 j+1=2(i+1)j=2i+1

  • RIGHT(i): j + 1 = 2 ( i + 1 ) + 1 , 则 j = 2 ( i + 1 ) j+1=2(i+1)+1,则j=2(i+1) j+1=2(i+1)+1j=2(i+1)

  • PARENT(i): j + 1 = ⌊ ( i + 1 ) / 2 ⌋ j+1=\lfloor (i+1)/2 \rfloor j+1=(i+1)/2,则 j = ⌊ ( i + 1 ) / 2 ⌋ − 1 j=\lfloor{(i+1)/2}\rfloor-1 j=(i+1)/21

二、堆

1.大顶堆和小顶堆

  • 大顶堆:父结点比左右子节点的值都大
  • 小顶堆:父结点比左右子节点的值都小

2.堆的高度和层

  • 高度: l o g 2 n log_2{n} log2n
  • 层=高度+1

三、大顶堆排序算法

  • MAX-HEAPIFY(调整本结点以下的顶堆为大顶堆)
  • BUILD-MAX-HEAP(无序数组→大顶堆)

1.MAX-HEAPIFY

// 伪代码
// 数组A,要调整的本结点下标i(调整本结点以下的顶堆为大顶堆)
MAX-HEAPIFY(A, i)
	// 获得左孩子下标
	l ← LEFT(i)
	// 获得右孩子下标
	r ← RIGHT(i)

	/* 比较本节点、左子节点、右子节点的值,让largest为最大下标 */
	
	// 检验l是否没有越界,并且当左孩子的值比本结点的更大时
	if l ≤ heap-size[A] and A[l] > A[i]
		// 则最大值的下标largest为左孩子的下标l
		then largest ← l
	// 否则,当本结点的值比左孩子的更大时,则最大值的下标largest为本结点的下标i
	else largest ← i
	
	// 检验r是否没有越界,并且当右孩子的值比最大值结点的更大时
	if r ≤ heap-size[A] and A[r] > A[largest]
		// 则最大值的下标largest为右孩子的下标r
		then largest ← r
	
	// 当最大值的下标不是本结点i时(需要调整因交换而破坏的下一层大顶堆)时
	if largest ≠ i
		// 则交换本结点和最大值结点的值
		then exchange A[i] ⇿ A[largest]
			 // 对最大值结点的大顶堆进行调整
			 MAX-HEAPIFY(A, largest)

在这里插入图片描述

2.BUILD-MAX-HEAP

感觉像是冒泡排序一样,大的元素逐渐浮上堆顶,小的元素沉积下去。

注意:完事后数组不是降序的,左右子节点的大小还没有排

// 伪代码
// 无序数组A(我们想要将无序数组→大顶堆)
BUILD-MAX-HEAP(A)
	// 堆的大小是数组A的长度
	heap-size[A] ← length[A]
	// 从一半的下标开始(最后一个叶子节点的父结点下标,直接用PARENT(n))
	// 下标递减往上遍历各个结点。
	for i ← ⌊ length[A] / 2 ⌋ downto 1
		// 对每个结点进行MAX-HEAPIFY(调整本结点以下的顶堆为大顶堆)
		do MAX-HEAPIFY(A, i)

在这里插入图片描述
PS:循环不变式做验证的过程
在这里插入图片描述

3.堆排序算法

// 伪代码
// 无序数组A
HEAPSORT(A)
	// 将无序数组→大顶堆
	BUILD-MAX-HEAP(A)
	// i表示每次循环中堆的最后一个叶子结点
	// 直到只剩下一个根节点(因为不用MAX-HEAPIFY了)
	for i ← length[A] downto 2
		// 交换树根A[1]和最后的叶子结点A[i]
		do	exchange A[1] ⇿ A[i]
			// 剪掉这个叶子结点(堆中目前的最大值),即堆的大小减1
			heap-size[A] ← heap-size[A] - 1
			// 对树根进行MAX-HEAPIFY,因为交换了树根A[1]和最后的叶子结点A[i]
			MAX-HEAPIFY(A, 1)

数组:增序(因为是倒着减去堆中的最大值)

PS:A[i]是最后的叶子结点,而不是最小值的结点。

在这里插入图片描述

4.时间复杂度

BUILD-MAX-HEAP n l g n nlgn nlgn

堆排序算法 n l g n nlgn nlgn

共:还是 n l g n nlgn nlgn

四、C++实现

#include <iostream>

using namespace std;

// 初始化于BUILD_MAX_HEAP(),用于MAX_HEAPIFY()中
int heap_size;

int LEFT(int i)
{
    // 伪代码是2 * i
    return 2 * i + 1;
}

int RIGHT(int i)
{
    // 伪代码是2 * i + 1
    return 2 * (i + 1);
}

int PARENT(int i)
{
    // 伪代码是PARENT(i)=⌊i/2⌋
    return (int)((i + 1) / 2) - 1;
    // 或者 return floor((i + 1) / 2) - 1; 需要 #include <math.h> // for floor()
}

// 调整本结点以下的顶堆为大顶堆
void MAX_HEAPIFY(int A[], int i)
{
    // 获得左孩子下标
    int l = LEFT(i);
    // 获得右孩子下标
    int r = RIGHT(i);

	/* 比较本节点、左子节点、右子节点的值,让largest为最大下标 */
	
    // 本结点、左孩子、右孩子中值最大的结点下标。先假设本结点最大
    int largest = i;
    // 检验l是否没有越界,并且当左孩子的值比本结点的更大时
    if (l < heap_size && A[l] > A[i])
    {
	    // 则最大值的下标largest为左孩子的下标l
        largest = l;
    }
    
    // 检验r是否没有越界,并且当右孩子的值比最大值结点的更大时
    if (r < heap_size && A[r] > A[largest])
    {
    	// 则最大值的下标largest为右孩子的下标r
        largest = r;
    }

	// 当最大值的下标不是本结点i时(需要调整因交换而破坏的下一层大顶堆)时
    if (largest != i)
    {
        // 则交换本结点A[i]和最大值结点的值A[largest]
        swap(A[i], A[largest]);
		// 对最大值结点的大顶堆进行调整
        MAX_HEAPIFY(A, largest);
    }
}

// 无序数组→大顶堆和初始化heap_size
void BUILD_MAX_HEAP(int A[], int length)
{
    // 初始化全局变量heap_size,为数组A的元素个数
    heap_size = length;
    // 从一半的下标开始(最后一个叶子节点的父结点下标,直接用PARENT(heap_size -1))
	// 下标递减往上遍历各个结点。
    for (int i = PARENT(heap_size - 1); i >= 0; i--)
    {
    	// 对每个结点进行MAX-HEAPIFY(调整本结点以下的顶堆为大顶堆)
        MAX_HEAPIFY(A, i);
    }
}

// 要调用的堆排序函数
void HEAPSORT(int A[], int length)
{
    // 无序数组→大顶堆和初始化heap_size
    BUILD_MAX_HEAP(A, length);
	// i表示每次循环中堆的最后一个叶子结点
	// 直到只剩下一个根节点(下标0)(因为不用MAX-HEAPIFY了)
    for (int i = heap_size - 1; i >= 1; i--)
    {
        // 交换树根A[0]和最后的叶子结点A[i]
        swap(A[0], A[i]);
        // 剪掉这个叶子结点(堆中目前的最大值),即堆的大小减1
        heap_size--;
        // 对树根进行MAX-HEAPIFY,因为交换了树根A[0]和最后的叶子结点A[i]
        MAX_HEAPIFY(A, 0);
    }
}

int main()
{
    int A[] = {16, 14, 10, 8, 7, 9, 3, 2, 4, 1};
    int length = sizeof(A) / sizeof(A[0]);
    HEAPSORT(A, length);
    for (int i = 0; i < length; i++)
    {
        cout << A[i] << " ";
    }
    cout << endl;
    return 0;
}

五、扩展:优先级队列

手写优先级队列

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值