文章目录
一.前言
堆排序是利用堆这种数据结构所设计的一种排序算法。但是堆排序所涉及到的堆的知识并不多,堆排序可以说就是一种利用堆的概念来排序的选择排序。
堆要符合下面的这两个特点:
1.是一棵完全二叉树;
2.所有父节点的值都要大于(小于)子节点的值。
完全二叉树:
上图中左边的就是一棵完全二叉树,而右边的就不是。
完全二叉树:在满二叉树中,从最后一个结点开始,连续去掉任意几个结点,便是完全二叉树。
特点二:
左边的一棵完全二叉树:父节点的值都小于子节点的值。
右边的一棵完全二叉树:父节点的值都大于子节点的值。
只需要满足这两个概念就是堆。我们也只需要利用好这两个知识点,也就可以实现堆排序 。
小根堆:
父节点的值都小于子节点的值。小根堆用于降序排序。
大根堆:
父节点的值都大于子节点的值。大根堆用于升序排序。
为何小根堆用于降序排序,而大根堆用于升序排序,后面会解释。
二.为何要利用堆的这两个特点来排序
我们将数据存在数组中,那为什么要用到完全二叉树这种结构?假设有一个数组和一棵完全二叉树,我们对二叉树从左到右,从上到下编号,如下:
我们发现它们两者之间可以相互表示,既然如此我们就可以将要排序的数组元素,用一棵完全二叉树表示。
同时堆排序还利用了完全二叉树中父节点与孩子节点的内在关系来排序。
对于堆的第二个特点,如果所有父节点的值都大于(小于)子结点的值,并且数据不是逆序排序的,那么它表示成数组的形式就是一个有序的数据。
小根堆为什么是降序:
我们将堆的第一个根结点与最后一个叶子结点交换,对应到数组中就是数组最后一个位置,越小的越靠后。
大根堆为什么是升序:
我们将堆的第一个根结点与最后一个叶子结点交换,对应到数组中也是数组最后一个位置,越大的越靠后。
三.如何将一个完全二叉树调整成堆
看一个简单的完全二叉树,如下:
如果我们要将这棵完全二叉树调整成为大根堆,那么根结点的值是要大于子结点的值,此时并不满足大根堆的要求。那么要从子结点中选一个最大的来作根结点,如下:
此时就满足大根堆的要求。
这里可能有人会疑惑,万一我们将下半部分的子树调整好后,上半部分又不满足了呢?也就是 会不会出现叶子结点比 其父亲的父亲结点还大的结点? 的确我们这样进行操作,可能会出现这种情况。但是我们在将一个数组数据构造成为堆的时候就已经避免了这种情况,下面会做回答。
四.要实现堆排序需要解决什么问题
在讲述该知识之前,还得知道下面的概念:
有如下一个完全二叉树,我们为其从左至右,从上至下编号:
用 i 来代表各结点的编号,各结点中存在这样的关系:
1.父节点:parent=(i-1)/2;
1.左孩子结点:lc=2i+1;
1.右孩子结点:rc=2i+2;
要实现堆排序需要解决两个问题:
1.如何由一个无序序列建成一个堆?
2.如何在输出堆顶元素后,调整剩余元素为一个新的堆?
4.1如何由一个无序序列建成一个堆?
很显然
· 单结点的二叉树是堆;
· 在完全二叉树中所有以叶子结点为根的子树是堆。
假设有如下的一组数组序列及其对应的完全二叉树,现在需要将其建成一个大根堆:
现在已知完全二叉树的所有以叶子结点本身就是堆。对于本完全二叉树我们只需要进行调整以0、1、2号为根结点的各子树。但是要保证整个完全二叉树满足堆的要求,那么我们要先保证其各子树都满足堆的要求。所以,对于堆的创建,我们要从下到上,从右至左即先调整2号结点,再调整1号结点,最后才调整0号结点。
先调整以2号结点为根结点的子树,如下: