天天看点

基本数据结构——堆堆(Heap)维护堆的性质建堆堆排序优先队列

堆(二叉堆)作为一种比较重要的数据结构,完全二叉树的线性存储。其典型的应用就是堆排序和优先队列。

堆(Heap)

堆(二叉堆)是一个数组,也可以被看做一个近似的完全二叉树。将二叉树从顶层向底层,从左向右,从1开始编号,直到二叉树节点个数n。得到完全二叉树的层序遍历序列,即是二叉堆。

二叉堆中编号是从1到n的,所以节点 i 的父节点编号为 ⌊ i/2⌋ ⌊   i / 2 ⌋ (i/2向下取整,在高级语言中,整型运算i/2即可做到),左孩子为2i,右孩子为2i+1。

即有:

Parent(i)
return i/
           
Left(i)
return *i
           
Right(i)
return *i+
           

在实际应用中,我们主要考虑最大堆(也称大顶堆)和最小堆(小顶堆)。其中最大堆(Max Heap)的根节点关键字(key)为所有节点关键字中最大值,且要求所有节点关键字的值大于等于其左孩子和右孩子的关键字。最小堆同理。以下主要以最大堆为例。

定义堆中节点的高度为根节点到所有叶子节点的最长简单路径上边的数目,即对应完全二叉树的高度。可证明节点数为n的堆高度为 ⌊log2n⌋ ⌊ log 2 ⁡ n ⌋ 。即堆高度为 Θ(log2n) Θ ( log 2 ⁡ n ) 。

维护堆的性质

Max_Heapify是用来维护最大堆性质的过程。

过程描述:输入数组A及下标i。假定此时节点 i 的左子树和右子树都是最大堆。但是节点 i 关键字可能小于其孩子。而Max_Heapify则可以通过让A[i]的值逐级下降,从而使根节点为 i 的子树重新遵循最大堆。

因为堆高度为 Θ(log2n) Θ ( log 2 ⁡ n ) ,所以Max_Heapify过程时间复杂度为 O(log2n) O ( l o g 2 n ) 。

伪代码描述如下:

Max_Heapify(A,i)
           
l = Left(i)
r = Right(i)
largest = i
if l<=A.Heap_Size and A[l]>A[i]     //A.Heap_Size表示存放在数组A中的有效堆元素
    largest = l
if r<=A.Heap_Size and A[r]>A[i]
    largest = r
if largest != i
    exchange A[i] with A[largest]
    Max_Heapify(A,largest)
           

C++实现:

这里在实现中为了方便和清晰舍弃了下标为0的存储位置,从下标1开始存放元素,初始化时数组大小为n+1,使其下标等于节点标号。
void Max_Heapify(int A[],int i,int n)   //n即为A.Heap_Size
{
    int largest=i;
    if(*i<=n && A[*i]>A[largest])
    {
        largest = *i;
    }
    if(*i+<=n && A[*i+]>A[largest])
    {
        largest = *i+;
    }
    if(largest != i)
    {
        swap(A[i],A[largest]);
        Max_Heapify(A,largest,n);   
    }
}
           

建堆

建堆指输入一个数组,将其转化为最大堆。利用上面的Max_Heapify过程完成。

思路:从n/2递减到1依次调用Max_Heapify即可实现。

伪代码:

Build_Max_Heap(A)
A.Heap_Size = A.length
for i=A.length/ downto 
    Max_Heapify(A,i)
           

C++实现:

void Build_Max_Heap(int A[],int n)
{
    for(int i=n/;i>=;i--)
    {
        Max_Heapify(A,i,n);
    }
}
           

堆排序

堆排序是一个经典的排序算法,借助最大堆(最小堆)完成。

过程描述:输入数组A,n=A.length,将数组中元素按升序排列。

思路:堆中的最大元素总是在根节点A[1],将其与堆中最后一个元素(A[n])交换,并从堆中去掉最后一个节点(通过A.Heap_Size自减1实现)。为了维护最大堆的性质,需要在交换后调用Max_Heapify(A,1),从而使A[1…n-1]构造成一个新的最大堆。重复这一过程直到堆大小下降为2。

步骤:

1. 将数组A建堆

2. 交换A[1]和堆中最后一个元素,堆大小减1

3. 维护根节点堆的性质,将最大元素置为根节点

4. 重复2,3直到排序完成

伪代码:

Heap_Sort(A)
Build_Max_Heap(A)
for i=A.length downto 
    exchange A[] with A[i]
    A.Heap_Size --
    Max_Heapify(A,)
           

C++实现:

void Heap_Sort(int A[],int n)
{
    Build_Max_Heap(A,n);
    for(int i=n;i>=;i--)
    {
        swap(A[],A[i]);            //A[i]为堆中最后一个元素
        Max_Heapify(A,,i-);      //i-1为堆中元素个数
    } 
}
void swap(int &x,int &y)
{
    int temp = x;
    x = y;
    y = temp;
}
           

堆排序完整Test代码:

//堆排序
#include<iostream>
#include<stdlib.h>
using namespace std;

void Max_Heapify(int A[],int i,int n);
void Build_Max_Heap(int A[],int n);
void Heap_Sort(int A[],int n); 
void swap(int &x,int &y);
void Disp(int A[],int n);

int main(void)
{
    int n;
    cin >> n;
    int A[n+];
    for(int i=;i<=n;i++)
    {
        cin >> A[i]; 
    }
    Disp(A,n);
    Heap_Sort(A,n);
    Disp(A,n);
    system("pause");
    return ;
}

void swap(int &x,int &y)
{
    int temp = x;
    x = y;
    y = temp;
}

void Max_Heapify(int A[],int i,int n)
{
    int largest=i;
    if(*i<=n && A[*i]>A[largest])
    {
        largest = *i;
    }
    if(*i+<=n && A[*i+]>A[largest])
    {
        largest = *i+;
    }
    if(largest != i)
    {
        swap(A[i],A[largest]);
        Max_Heapify(A,largest,n);   
    }
}

void Build_Max_Heap(int A[],int n)
{
    for(int i=n/;i>=;i--)
    {
        Max_Heapify(A,i,n);
    }
}

void Heap_Sort(int A[],int n)
{
    Build_Max_Heap(A,n);
    for(int i=n;i>=;i--)
    {
        swap(A[],A[i]);
        Max_Heapify(A,,i-);
    } 
}

void Disp(int A[],int n)
{
    for(int i=;i<n;i++)
    {
        cout << A[i] << ",";
    }
    cout << A[n] <<  endl;
}
           

优先队列

坑已填,见优先队列 & 堆。——2018.3.24

参考资料:算法导论

继续阅读