堆排序

堆排序是非常快的一种排序方法。其时间复杂度为O(nlogn),和快速排序不相上下。


什么是堆

我们在介绍堆排序之前,当然需要知道什么是堆。堆是一种数据结构,相当于二叉树一样。但是它是使用数组存放的。存放的方法是:从左到右,从上到下地将节点依此放入数组中:

 

 像图中的二叉树,首先从顶端开始将34放入数组中。然后来到第二层,将28,16放入数组中。然后来到第三层,将15,14,9,1放入数组中...以此类推。如果你是将数组变为二叉树的话就是一个相反的过程:先将34置为树根,然后从左到右,从上到下的依次添加节点。注意这里我说的是变成二叉树而不是变成堆,因为很有可能这个数组对应的二叉树不满足堆的性质。
 
堆的性质:
堆不仅仅是二叉树,它还有自己的性质:
  • 任何一个节点的数值一定比两个子节点都大

有这个性质的堆称为最大堆,如果反过来:

  • 任何一个节点的数值一定比其子节点都小

那么就是最小堆。

最大堆最后排序出来的元素是升序的。最小堆对应降序排列。

堆还有一个隐含的性质:

  • 每一个堆都是完全二叉树

这个性质很容易被发现:由于我们从数组生成二叉树时总是自左向右,自上而下的,所以最后的结果一定是最下层的最右边空缺,所以一定会生成完全二叉树。

 堆的拓展性质:

接下来通过性质我们还可以推导出一些十分有用的拓展性质,我们需要使用这些性质来进行堆排序:

  1. 对于有n个元素的堆来说,其二叉树高度为floor(logn)。(其中floor为向下取整)

    这个性质的证明还是比较简单的。可以看出层数h和当前层的节点数n的关系是n=2^(h-1)。这样使用求和公式,最后考虑最下一层不全部排满的情况就可以推导出这个公式了。

  2. 对于一个有n个元素的堆来说,第floor(n/2)+1个元素为第一个叶子节点,floor(n/2)+2为第二个叶子结点...依此类推。
  3. 对于第n个父节点来说,其左节点是第2*n个节点,右节点是第2*n+1个节点

维护堆的性质 

对于一个数据结构,我们需要做到在插入,删除元素的时候不改变数据结构的性质。这里我们以最大堆为例来说明如何维护堆的性质:

最大堆的性质是:

  任何一个父节点都比两个子节点的数值要大。

我们需要在更改堆的时候让堆不违反这个性质。我们这里使用一个函数来维护堆的性质。这个函数将在每次打乱堆的时候使堆自动变成符合性质的结构:

 1 LEFT(n)
 2     return n*2
 3 
 4 RIGHT(n)
 5     return n*2+1
 6 
 7 MAX_HEAP(heap,i)
 8     left=LEFT(i)
 9     right=RIGHT(i)
10     if(left<=length && heap[left]>heap[I])
11         max=left
12     Else 
13         max=I
14     If(right<=length && heap[right]>heap[max])
15         max =right
16     if(max!=i)
17         Exchange heap[I] and heap[max]
18         MAX_HEAP(heap,max)   

首先根据拓展性质3,LEFT()和RIGHT()函数用于获得节点n的左右节点的下标。

接下来MAX_HEAP用于维护堆的性质: 

  第8,9行获得节点i的左右子节点。

  第10,14行比较这个节点和其子节点的大小,并且将三者中最大的节点下标记录在max中。

  第16行判断最大的节点是不是原本的父节点i。如果是的话就停止维护堆的性质。否则再次递归调用MAX_HEAP()函数向下维护堆的性质。

对于递归,我们知道总是有一个基础步骤和一个递归步骤:

基础步骤:对于节点i,找出i,i的子节点中最大的节点下标,放入max中。

递归步骤:如果max!=i,则交换heap[max]和heap[i]的值,并且调用MAX_HEAP(heap,max)

这就是MAX_HEAP函数的递归描述。

具体的C++代码如下:

堆的类结构:

template <typename T>
class Heap{
public:
    Heap():heap(nullptr),len(0){}
    Heap(T* arr,int l);
    vector<T> GetSortedVector();
    void OutPut();
    ~Heap();
private:
    T GetLeft(int i);
    T GetRight(int i);
    void Max_Heap(int i);
    void Max_Heap_for_Sort(T* arr,int i,int length);
    void CreateHeap();
    T* heap;
    int len;
};
类结构

维护堆性质的代码:

template <typename T>
T Heap<T>::GetLeft(int i){
    return heap[i<<1];
}

template <typename T>
T Heap<T>::GetRight(int i){
    if((i<<1)+1 > len)
        return INT_MIN;
    return heap[(i<<1)+1];
}

template <typename T>
void Heap<T>::Max_Heap(int i){
    int max;
    T l=GetLeft(i),r=GetRight(i);
    if((i<<1)<=len && l>heap[i])
        max=i<<1;
    else
        max=i;
    if((i<<1)+1<=len && r>heap[max])
        max=(i<<1)+1;
    if(max!=i){
        int temp=heap[max];
        heap[max]=heap[i];
        heap[i]=temp;
        Max_Heap(max);
    }
}
维护性质 

 构建堆 

由于是排序算法,所以首先一定会给出一些数,这些数常常放在数组中,像是这样: 

我们就将这个数组当作一个二叉树,那么根据上面说的规则,采用自左向右,自上而下的方式构建二叉树: 

现在这个还只是二叉树。要将这棵树变成堆,需要满足堆的性质。这样的话维护堆性质的函数就派上用场了。我们自底向上的对,每个父节点使用MAX_HEAP函数来将二叉树变成堆(也就是说最下一层不使用):

CEATE_HEAP(heap)
    For i=floor(length/2) downto 1
        MAX_HEAP(heap,i)

其中第二行的for循环用于从下到上的使用MAX_HEAP函数。这里floor(lengtth/2)的条件是根据拓展性质3得出的。

对应的C++代码如下: 

template <typename T>
void Heap<T>::CreateHeap(){
    for(int i=floor(len/2);i>=1;i--)
        Max_Heap(i);
}
构建堆

 对于上面的树,变成堆堆过程如下: 

其中蓝色是for循环里MAX_HEAP()执行到的节点。橙色的是MAX_HEAP()递归递归到的节点。

可能会有些人说,为什么要自底向上创建,我自顶向下创建不行吗?下面我们来看看自顶向下的创建过程(只需要将for循环里改成i=1 to floor(length/2)即可)

 

可以看出最后的结果并不正确。


 

堆排序 

终于到了我们最后这一章的核心,堆排序了。其实前面说的都是如何创建堆这个数据结构。接下来通过这个数据结构来进行排序。

堆排序的思想如下:

根据最大堆的性质(你要是使用的最小堆就看最小堆的性质),父节点总是比子节点大。这样的话堆的根节点一定是整个堆里面最大的节点。我们可以把这个节点和最右下角的节点互换:

  

为什么要和最右下角的节点互换呢?不要忘记这颗树是通过数组存储的哦。我们把最大元素和最右下角的元素互换,其实就是将最大元素和数组最后一个元素互换哦: 

这个时候右下角的节点就已经不属于堆了。

但是这样做的话又破坏了堆的性质(这里7小于8了),所以我们需要再次维护堆的性质,对根节点调用MAX_HEAP即可:

 

由于原本最大的节点已经放到数组的最后一位了,现在堆里面的节点全部都小于数组最右边的节点。通过维护堆的性质再将堆中最大节点放到根节点处。这个时候根节点是整个排序数组中第二大节点。然后将根节点放到倒数第二个节点处就完成了一次排序。

然后重复地将根节点和右下方的节点互换,再对根节点使用MAX_HEAP。直到堆里面没有节点了,排序就完成了。

伪代码如下:

HEAPSORT(heap)
    For i=length downto 2
        Exchange heap[i] and heap[1]
        Length--
        MAX_HEAP(heap,1)

C++代码如下:

template <typename T>
void Heap<T>::Max_Heap_for_Sort(T* arr,int i,int length){
    int max,r,l;
    l=i<<1;
    r=(i<<1)+1;
    if(l<=length && arr[l]>arr[i])
        max=l;
    else
        max=i;
    if(r<=length && arr[r]>arr[max])
        max=r;
    if(max!=i){
        int temp=arr[i];
        arr[i]=arr[max];
        arr[max]=temp;
        //length--;
        Max_Heap_for_Sort(arr, max, length);
    }
    for(int i=1;i<=len;i++)
        cout<<arr[i]<<",";
    cout<<endl;
}
堆排序

最后数组就被排序成功了。

下面是C++的全部实现代码:

#ifndef Heap_hpp
#define Heap_hpp

#include <iostream>
#include <cmath>
#include <vector>
using namespace std;

template <typename T>
class Heap{
public:
    Heap():heap(nullptr),len(0){}
    Heap(T* arr,int l);
    vector<T> GetSortedVector();
    void OutPut();
    ~Heap();
private:
    T GetLeft(int i);
    T GetRight(int i);
    T GetParent(int i);
    void Max_Heap(int i);
    void Max_Heap_for_Sort(T* arr,int i,int length);
    void CreateHeap();
    T* heap;
    int len;
};

template <typename T>
Heap<T>::Heap(T* arr,int l):len(l){
    heap=new T[l+1];
    for(int i=0;i<l;i++)
        heap[i+1]=arr[i];
    CreateHeap();
}

template <typename T>
T Heap<T>::GetLeft(int i){
    return heap[i<<1];
}

template <typename T>
T Heap<T>::GetRight(int i){
    if((i<<1)+1 > len)
        return INT_MIN;
    return heap[(i<<1)+1];
}

template <typename T>
T Heap<T>::GetParent(int i){
    return heap[i>>1];
}

template <typename T>
void Heap<T>::Max_Heap(int i){
    int max;
    T l=GetLeft(i),r=GetRight(i);
    if((i<<1)<=len && l>heap[i])
        max=i<<1;
    else
        max=i;
    if((i<<1)+1<=len && r>heap[max])
        max=(i<<1)+1;
    if(max!=i){
        int temp=heap[max];
        heap[max]=heap[i];
        heap[i]=temp;
        Max_Heap(max);
    }
}


template <typename T>
void Heap<T>::Max_Heap_for_Sort(T* arr,int i,int length){
    int max,r,l;
    l=i<<1;
    r=(i<<1)+1;
    if(l<=length && arr[l]>arr[i])
        max=l;
    else
        max=i;
    if(r<=length && arr[r]>arr[max])
        max=r;
    if(max!=i){
        int temp=arr[i];
        arr[i]=arr[max];
        arr[max]=temp;
        //length--;
        Max_Heap_for_Sort(arr, max, length);
    }
    for(int i=1;i<=len;i++)
        cout<<arr[i]<<",";
    cout<<endl;
}

template <typename T>
vector<T> Heap<T>::GetSortedVector(){
    vector<T> v;
    T temp[len+1];
    int length=len;
    for(int i=0;i<=len;i++)
        temp[i]=heap[i];
    for(int i=len;i>=2;i--){
        v.push_back(temp[1]);
        temp[1]=temp[i];
        Max_Heap_for_Sort(temp, 1, --length);
    }
    return v;
}

template <typename T>
void Heap<T>::OutPut(){
    for(int i=1;i<=len;i++)
        cout<<heap[i]<<",";
    cout<<endl;
}

template <typename T>
Heap<T>::~Heap(){
    if(heap!=nullptr)
        delete[] heap;
}
#endif
全代码
原文地址:https://www.cnblogs.com/learn-program/p/9613042.html