十大基础算法通俗讲解(2):堆排序

堆排序即利用 堆 进行排序。堆是什么呢?翻开数据结构的书,会有这样的定义:堆是被完全填充的二叉树。树又是什么?计算机科学中,它是一种数据结构。既然叫树,那自然是跟现实世界的树很类似,否则为嘛不叫阿猫阿狗。(既然是通俗讲解,尽量做到事无巨细,不愿听我扯淡完全可以跳过这些) 小孩的视角更接近这个世界的本真。如果幼儿园老师让小朋友画一棵树,大约会是这个样子:

十大基础算法通俗讲解(2):堆排序_第1张图片
tree.png

(幸好我的绘画水平还停留在幼儿园阶段,简直毫无瑕疵,这波伪装我给满分).
这就是树抽象出来的样子,有树干,树干上有些树枝,树枝上又分出树枝...在基界(计算机理工男的世界)。大家习惯于倒着画这样的树:


十大基础算法通俗讲解(2):堆排序_第2张图片
tree2.png

有些人会说老湿 "你倒着画我也就忍了,但是每个枝上都分出两个枝这就不能忍了,不过强迫症看着是TM真爽呢。" 我:“对啊,爽不就中了,爽了还不收钱,这事哪找去?”
于是接上面的话题,我们引出“二叉树”的概念。---就是每个节点上顶多有两个分枝(支)的树。树的概念有了,既然是数据结构,没数据玩蛇?灵魂画师再次登场:

十大基础算法通俗讲解(2):堆排序_第3张图片
tree5.png

树的事就讲完了,被完全填充的二叉树又如何解释呢。还是看上图吧,除了最后一层的8,每个节点都是两个分支,是不是有种被满满填充的满足感。(额...基界呆久了) 其实这样的方式可不是为了提供满足感,而是在处理这些数据时,非常的便利。假使用代码去抽象树:

struct Node                    //抽象节点,这里是二叉树,所以每个节点可以有两个分支节点,data用来保存本节点的数据
{
    int    _data;
    struct Node * _left;
    struct Node * _right;
};
struct Tree                    //root即是根节点,像是树干,有了树干就可以方便的找到其他的节点
{
    struct Node * _root;
};

有点麻烦,让我们再回头去想下。“完全二叉树”,重点在“完全”。图中我从左至右,从上至下的标记了每个节点。并且,特意从0开始。0.5秒之后你是不是已经意识到了什么。对!没错。就是数组。我们是不是可以把这样一个完全二叉树(堆)用数组去保存。但是这样保存似乎会丢掉了树的结构。在树结构,我们可以清晰的知道每个节点的左节点和右。如果在数组中一次排开呢?0 | 1 2 | 3 4 5 6 | 7 8 9 10 ......(竖线是我特意加的,可以方便的跟树结构每一层对应上。) 第一个元素 0 的左右节点分别是 1、2 ;2 的 两个节点分别是 5 、6 ;4 的左右节点分别是 9 、10;用括号将它们分组 (0 :1 | 2 ) (2 :5 | 6)(4 :9 | 10)。左节点的序号是当前节点序号的2倍加1,右节点的序号是左节点载加一。有了这样的事实,我们便可以放心大胆的用数组去保存一个“堆”了。
有了一”堆“数字,就能排序了?对,接近了。正式开始排序之前,再聊一个概念。最大堆和最小堆。最大堆的意思就是 :每个节点的值都大于它的两个子节点的值。这样话就意味着最大值一定在顶端(因为每个节点的值都要大于它的子节点,于是作为最大值只能在这个没有父节点的位置,否则它将大于它的父节点,从而它的父节点将不符合必须大于子节点的这一规则)。
在最大堆概念的指引下,我们有了初级思路。假设我们有了将一个堆变成最大堆的函数 maxHeapify。那排序的就变得轻而易举了。于是,我们敲下这样的代码:

void swap(int * heap,int i1,int i2)
{
    int temp = 0;
    temp = heap[i1];
    heap[i1] = heap[i2];
    heap[i2] = temp;
}
void heapSort(int *heap,int length)
{
    for(int i = length; i > 0 ;i--)
    {
        maxHeapify(heap, i);
        swap(heap, 0, i-1);
    }
}

代码思路非常清晰易懂,就是将堆编程最大堆,然后将最大元素放到最后。然后排除最后一个元素的小了一点的堆继续做同样的操作,直到堆的大小变成1。接下来的问题就是如何写这个 maxHeapify 函数了。基界有一句至理名言,没有啥是不能通过增加一层抽象解决不了的。我觉得和这条接近的应该是这么一句“没有啥问题是穷举不能解决的,如果能够穷举。” 让我们把“每一个节点的值必须分别大于它的两个子节点。”这句话在脑子里过三遍。再结合“穷举”两个字。立马可以得到如下的思路:我们将每个节点都和它的父节点比较一次,如果大于父节点,则调换。于是代码如下:

void maxHeapify  (int * heap,int length)
{
    for(int i = length-1;i>0;i--)
    {
        if(heap[i] > heap[PARENT(i)])
            swap(heap, i, PARENT(i));
    }
}

这样代码就全部写完了。

你可能感兴趣的:(十大基础算法通俗讲解(2):堆排序)