部分摘自【图解排序算法(三)之堆排序 - dreamcatcher-cx - 博客园】
目录
堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
首先简单了解下堆结构:
堆
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:
根结点arr[i]、根结点的左子树arr[2 * i]、右子树arr[2 * i + 1]
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
接下来,我们来看看堆排序的基本思想及基本步骤:
堆排序思路
将要排序的arr建堆,然后不断输出并删除堆顶节点,提取堆顶元素后删除堆顶,将堆的最后一个元素移到堆顶,然后对根结点往下调整,所以又保持了一个arr[len-1]的堆。以此重复。
建堆步骤
1、构造初始堆,将给定无序序列构造成一个大顶堆,假设给定无序序列结构如下。
2、此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
3、找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
此时,我们就将一个无需序列构造成了一个大顶堆。
详细
操作
- 建堆:建堆就是一个反复筛选的过程,n个节点的完全二叉树,最后一个节点是第[n/2]个节点的孩子,对第[n/2]个节点为根子树筛选(对于大根堆:若根节点的关键字小于小于左右的子女中关键字的最大者,则交换),使该子树成为堆。对以向前([n/2]-1 至 1)的为根节点的子树重复上述过程进行筛选,过程中节点的交换可能破坏下一级的堆,于是就向下进行调整。所以遍历的顺序:内层往下,外层往上。
- 堆中插入节点:将插入的新节点放在堆末端,然后对此结点往上调整。向上调整是只对新插入的节点及其直系上级或上上级...节点进行调整,而不涉及其他旁系的上[上]级节点。
- 堆中删除节点:删除堆顶时,将堆的最后一个元素与堆顶交换,然后对根结点往下调整
- 堆排序:将要排序的arr[len]建堆,然后不断输出并删除(即上面第3点)堆顶节点,删除堆顶时,将堆的最后一个元素与堆顶交换,然后对根结点往下调整,所以又保持了一个arr[len-1]的堆。
建堆(以大顶堆为例):
向下调整的时间复杂度为O(h),因为大部分节点的高度都较小,所以可以证明建堆为O(n)
public void buildMaxHeap(int arr[], int len) {
for(int i = len / 2; i > 0; i--) //最后一个节点是第len/2个节点的孩子
adjustDown(arr, i, len)
}
public void adjustDown(int arr[], int i, int len) {
arr[0] = arr[i]; //暂存
for(int j = i * 2; j <= len; j++) { //i为根,j为i的各子代
if(j < len && arr[j] < arr[j + 1]) //比较左右孩子的大小
j++;
if(arr[0] > arr[j]) break; //退出循环
else {
arr[i] = arr[j]; //将j上移到双亲节点上
i = j; //重新选跟,以这个子代(j)为下次循环的跟
}
}
arr[j] = a[0];
}
大根堆中插入节点时的向上调整:
public void adjustUp(int arr[], int i) { //i为向上调整的结点
arr[0] = arr[i];
int j = i / 2; //j为双亲,i为向上的调整的起点节点
while(j > 0 && arr[j] < arr[0]) {
arr[i] = arr[j];
i = j;
j = i / 2;
}
arr[i] = arr[0];
}
堆排序:
堆排序,空间复杂度为O(1),时间复杂度最好/最坏/平均为O(nlogn),且不稳定
public void heapSort(int arr[], int len) {
buildMaxHeap(arr, len);
for(int i = len; i > 1; i--) {
print(arr[1]);
swap(arr[i], arr[1]); // 交换之后只对n-1个节点进行排序:adjustDown(arr, 1, i - 1);
adjustDown(arr, 1, i - 1);
}
print(arr[1]);
}