堆排序

1. 完全二叉树:叶节点只能出现在最后层或次下层,并且最下面一层的节点都集中在该层的最左边的二叉树。
                                                                    
2. 二叉堆:堆是一颗二叉树,并且满足下面的条件:
  (1)树的每一层都是满,除了最后一层的最右边元素
  (2)任意一个父节点都大于或等于(小于或等于)两个子节点。(大于或等于的为最大堆,小于或等于的为最小堆)
  (3)每个结点的左子树或右子树都是二叉堆(最大堆或最小堆)
                                      
                                   最小堆                                                     堆的形状
3. 堆的存储:
使用数组的方式存储(从下标0开始),每个父节点的左右子节点为2*i+1 2*i+2,子节点的父节点为(i-1)/2
                     
 
4. 堆排序的特点:
   堆排序是一种不稳定的排序(如堆顶出去后,最后一层的元素跑到堆顶,即改变了其他元素的相对位置)。时间复杂度是O(nlogn),排序速度略低于快排。但是在百万级的数据排序中,快排和归并是递归的容易造成栈溢出,所以这时候用堆排序就很有优势了。

5. C++实现最小堆:

/*最小堆的插入节点*/
void MinHeapInsertNum(std::vector<int> & a, int num)
{
/* 把插入的元素放在堆的最后面,通过自底向上的不断上潜,来插入新元素 */
/* 由于堆的高度为log(n),所以插入效率是O(log(n)) */
    a.push_back(num);          //尾结点插入元素
    int tmp = a[a.size()-1];   //tmp为要插入的元素的值
    int i = a.size()-1;        //i是子节点
    int j = (i-1)/2;           //j是父节点
    while(j>=0 && i!=0)        //i必须不等于0 因为 j=(int)0-0.5=0,会无限循环
    {
        if(tmp >= a[j])        //如果是最大堆的插入,则改为: if(tmp <= a[j])
            break;
        else
        {
            a[i] = a[j];       //父节点值比子节点大,所以父节点的值要下堆到子节点的位置。
            i = j;             //子节点上移
            j = (i-1)/2;       //父节点上移
        }
    }
    a[i] = tmp;                //父节点下堆完成后,把新元素插入在符合要求的位置(某个父节点下的子节点位置)
}

/*最小堆的删除根节点*/
int MinHeapDeleteNum(std::vector<int> & a)
{
/* 将根节点和尾结点交换位置后,删除尾节点,之后根节点自顶向下不断下潜,来完成堆*/
/* 根节点的每次下潜要与左右子节点比较,即总共2*log(n)的比较次数,所有删除效率为O(log(n))*/
    //头结点与尾结点交换位置
    std::swap(a[0],a[a.size()-1]);
    //将最后一个元素释放,即释放了头结点
    a.pop_back();
    //现在的头结点从上向下沉,即数据下沉堆化
    int tmp = a[0];
    int i = 0;         //i为父节点
    int j = i*2+1;     //j为子节点
    while(j<a.size())
    {
        //找出当前父节点的左右子节点中,最小的那个
        if( (j+1) < (a.size()-1) && a[j+1]<a[j] )
            ++j;
        if(tmp<=a[j])        //找到了头结点的正确位置
            break;
        else
        {
            a[i] = a[j];       //子节点的值比父节点小,所以子节点要上堆到当前父节点的位置
            i = j;             //父节点下移
            j = i*2+1;        //子节点下移
        }
    }
    a[i] = tmp;               //子节点上堆完成后,将原始堆的尾结点放在适当的位置
    return HeadNum;
}

/*最小堆的堆化*/
void MakeMinHeap(std::vector<int> & a)
{
/* 从原始树的次下层开始,每一个元素都自顶向下地下潜(若该元素比子节点大,则该元素下潜)*/
/* 在最坏的情况下,总的比较次数是2(n-log(n+1)),所有堆化效率是O(n) */
    //所有子堆进行自顶往下的堆化
    for(int k=a.size()/2-1; k>=0; k--)
    {
        int tmp = a[k];
        int i = k;         //i为父节点
        int j = i*2+1;     //j为子节点
        while(j<a.size())
        {
            //找出子节点中最小的那个
            if( (j+1) < (a.size()-1) && a[j+1]<a[j] )
                ++j;
            if(tmp<=a[j])
                break;
            else
            {
                a[i] = a[j];      //子节点的值比父节点小,所以子节点要上堆到当前父节点的位置
                i = j;            //父节点下移
                j = i*2+1;        //子节点下移
            }
        }
        a[i] = tmp;
    }
}
6. 堆排序:
将普通的二叉树构建成一颗最小堆,依次删除最小堆的根节点,得到的出队顺序就是堆排序的结果。实现代码如下:
/*最小堆出队顺序为从小到大*/
std::vector<int> MinHeapSort(std::vector<int> & a)
{
    /* ① 构造堆   
       ② 删除堆的根节点,即对剩下的堆删除n-1次根节点,得到的输出序列就是堆排序结果 */
    /* 构造堆的效率为O(n),删除堆根节点的总效率为O(nlog(n)),所以总效率为O(nlog(n))*/
    std::vector<int> res;
    while(!a.empty())
    {
        res.push_back(MinHeapDeleteNum(a));
    }
    return res;
}
原文地址:https://www.cnblogs.com/ladawn/p/8297274.html