算法初探——堆排序

堆可以说是一种数据结构,但似乎又不完全是,它本质上是一棵完全二叉树

堆需要满足以下两个·条件:
①父节点的键值总是大于或等于(小于等于)任何一个子节点的值。
②每个结点的左右子树依旧是一个二叉堆。

当父节点的键值总是大于等于子节点的是最大堆;当父节点的键值总是小于等于任何一个子结点的是最小堆。

这里写图片描述

堆的存储结构

这里写图片描述

这样的储存结构有极为重要的特点:

① 结点 i 的父节点为 ( i - 1)/2;
② 结点 i 的左右孩子分别为 2*i + 1 , 2*i+2 .

就是由于数组存储完全二叉树,其父节点和子节点之间下标有特定的运算关系,这才在逻辑上保证了数的逻辑结构。

要用堆来完成排序我们第一步就是要完成堆的建立,最后再完成排序。

第一步我们先完成堆的结构调整函数,先看一看下面这个结构
这里写图片描述

此时我们需要完成以下步骤:

① 找到 i 结点的左右子结点中,若该子节点大于父节点,则调整完成,若该子结点小于父节点,则进入步骤二;
②将父节点取出暂时保存,然后用较小子结点替换父节点,再对该子节点的子节点重复上述步骤(注意都是与一开始取出的父节点12比较大小),直至到达边界n处;
③将一开始取出的父节点放到最后被移动的子节点处。

实现也十分的简单

void MinHeapFixdown(vector<int> & t, int i,int n)
{

    int j, temp;

    temp = t[i];
    j = 2 * i + 1;
    while (j < n-1)
    {
        if (j + 1 < n && t[j + 1] < t[j]) //在左右孩子中找最小的  
            j++;

        if (t[j] >= temp)
            break;

        t[i] = t[j];     //把较小的子结点往上移动,替换它的父结点  
        i = j;
        j = 2 * i + 1;
    }
    t[i] = temp;
}

这里写图片描述

完成后就 似乎 可以得到一个最小堆,那么问题出在哪了?这个函数可以说是一个只管儿子,然后通过儿子管孙子的,它最大的问题在于无法直接管理孙子,因此仅靠他是无法将一个完全乱序的数组变成一个“吊炸天”的堆的。

那么进入第二步
从最后父节点开始,倒序生成堆。
由于我们已经写好的函数只能管到它的儿子,换句话说它要求父节点的左右子树都是堆。
因此 我们只要从最后一个父节点开始,从后向前依次调用上述调整函数。

void MakeMinHeap(vector<int> & t)
{
    int n = t.size();
    for (int i = (n-1)/2; i >= 0; i--)
        MinHeapFixdown(t,i,n);
}

好的
我们已经得到一个堆了
这里写图片描述

得到的序列是 3 4 16 12 5,并非我们需要的有序序列,最后的处理便是得到有序序列。

具体的做法是
①交换i 和 n ,然后 n –;
②对 i n 调用结构调整函数;
③重复上述步骤,直到 n = i;

这里比较好理解的就是 根节点一定是最小的,每次都把根结点取出来放到数组后面,再对前半部分调用结构调整,最后得到的便是一个 反序 的排序。

void MinheapsortTodescendarray(vector<int> & t)
{
    int n = t.size();
    for (int i = n - 1; i >= 1; i--)
    {
        Swap(t[i], t[0]);
        MinHeapFixdown(t,0,i);
    }
}

这样就得到一个反序的堆排序了,为了少做一次翻转,就反着来选择最大堆最小堆就好。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
void Show(int x);
void MakeMinHeap(vector<int> & t);
void MinHeapFixdown(vector<int> & t, int i, int n);
void MinheapsortTodescendarray(vector<int> & t);
void Swap(int & x, int &y);
int main()
{
    vector<int> temp = { 12,15,18,3,9,21 };
    for_each(temp.begin(), temp.end(), Show);
    cout << endl;
    MakeMinHeap(temp);
    MinheapsortTodescendarray(temp);
    for_each(temp.rbegin(), temp.rend(), Show);
    cin.get();
    return 0;
}
void MinHeapFixdown(vector<int> & t, int i,int n)
{

    int j, temp;

    temp = t[i];
    j = 2 * i + 1;
    while (j < n-1)
    {
        if (j + 1 < n && t[j + 1] < t[j]) //在左右孩子中找最小的  
            j++;

        if (t[j] >= temp)
            break;

        t[i] = t[j];     //把较小的子结点往上移动,替换它的父结点  
        i = j;
        j = 2 * i + 1;
    }
    t[i] = temp;
}
void MakeMinHeap(vector<int> & t)
{
    int n = t.size();
    for (int i = (n-1)/2; i >= 0; i--)
        MinHeapFixdown(t,i,n);
}

void MinheapsortTodescendarray(vector<int> & t)
{
    int n = t.size();
    for (int i = n - 1; i >= 1; i--)
    {
        Swap(t[i], t[0]);
        MinHeapFixdown(t,0,i);
    }
}

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

void Show(int x)
{
    cout << x << " ";
}

至此 , 冒泡排序 、选择排序 、插入排序 、希尔排序 、堆排序 、 二叉排序树 、归并排序 、快速排序 都简单的介绍了一下, 最近实习生面试中,光快速排序都被要求写了三次,这些都了然于胸还是挺重要的 O(∩_∩)O哈!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值