文章目录
一、用数组存储层序遍历的堆
1.按层序遍历
2.算左子节点、算右子节点、算父节点的下标
(1)伪代码结果
设i
为本结点,j
为所求结点,采用[1,n]
-
LEFT(i) = 2 ∗ i \small{\text{LEFT(i)}}=2*i LEFT(i)=2∗i
-
RIGHT(i) = 2 ∗ i + 1 \small{\text{RIGHT(i)}}=2*i+1 RIGHT(i)=2∗i+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=2∗i+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)+1,则j=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)/2⌋−1
二、堆
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;
}