排序算法(七)-堆排序

来源:互联网 发布:提取网页数据到excel 编辑:程序博客网 时间:2024/06/08 13:20

1. 原理

1.1 什么是堆

要理解堆排序,首先要先理解什么是堆。堆是一颗顺序存储的完全二叉树,堆又分为最大堆和最小堆。

  • 完全二叉树:设二叉树的深度为h,除h层外,其他各层的节点数都达到最大个数,第h层的所有节点都连续集中在最左边。
  • 最小堆:每个节点的值都不大于其子节点的值的完全二叉树
  • 最小堆:每个节点的值都不小于其子节点的值的完全二叉树

根据上面的描述我们可以用一个数学描述来定义最大最小堆:

对于数组[D(0), D(1), …., D(n)]当且仅当满足下列关系时称之为堆

  • 最小堆:D(i) <= D(2 * i + 1)并且D(i) <= D(2 * i + 2)
  • 最大堆:D(i) >= D(2 * i + 1)并且D(i) >= D(2 * i + 2)
  • 其中整数i = 0, 1, 2, …, n/2

举个栗子:[3, 4, 7, 12, 15, 18]就是一个典型的最小堆, i <= 2。

  • i取0时:3 > 4, 3 > 4
  • i取1时:4 > 12, 4 > 15
  • i取2时:7 > 18
1.2 堆排序

理解了堆的概念之后,堆排序就是利用最大堆和最小堆的特性进行排序

  1. 将数组初始化成最大堆
  2. 交换最大堆的第一个和最后一个数字,输出最后一个数字(最大值)
  3. 将破坏后的最大堆重新调整为最大堆
  4. 接着重复2~3步直到交换堆的第一和第二个节点,此时结束排序

2. 实现

此算法的关键在于如何构建最大堆。根据上诉最大最小堆的定义,可以写出针对某一parent的调整算法。

public void adjustMaxHeap(int[] data, int parent, int length) {    int temp = data[parent];    int child = 2 * parent + 1;    while (child < length) {        //如有有右子节点,并且右子节点大于左子节点,则选取右子节点和parent比较        if (child + 1 < length && data[child] < data[child + 1]) {            child++;        }        //如果父节点的值大于子节点的值,则跳出循环        if (temp >= data[child]) {            break;        }        //把孩子节点的值赋给父节点        data[parent] = data[child];        //选取子节点的左子节点,继续向下调整        parent = child;        child = 2 * child + 1;    }    data[parent] = temp;}

此函数为调整某个parent的值,初始化的时候需要从n/2开始一直循环调整到0;

for (int i = data.length / 2; i >= 0; i--) {    adjustMaxHeap(data, i);}

完整对堆排序算法如下:

@Overridepublic int[] sort(int[] data) {    if (data == null || data.length <= 1) {        return data;    }    for (int i = data.length / 2; i >= 0; i--) {        adjustMaxHeap(data, i, data.length);    }    for (int i = data.length - 1; i > 0; i--) {        //最后一个数字和第一个数字交换        swap(data, 0, i);        //由于parent=0的节点发生了变化,因此需要重新调整堆        adjustMaxHeap(data, 0, i);    }    return data;}

完整实现可查看:

GitHub/HeapSort

3. 复杂度

堆排序是一种不稳定的排序算法。平均,最坏和最好的时间复杂度都是O(nlogn)。

原创粉丝点击