返回

堆排序算法——构建最大堆之陷阱

前端

让我们从上一篇文章的结尾处开始,我们已经对堆进行了处理,掌握了shift up和shift down等操作。现在,让我们来看一个随机的数组,蓝色的部分表示的是叶子结点。

[15, 35, 22, 18, 43, 62, 30, 12, 10, 50]

我们从叶子结点的最后一个开始,它的值是62,索引是10。我们将它与它的父节点(索引是5)进行比较,发现62大于30,因此我们需要进行shift down操作。

首先,我们将62与它的左子节点(索引是10/2=5)比较,发现62大于15,因此我们将62移动到左子节点的位置。

然后,我们将15与它的右子节点(索引是10/2+1=6)比较,发现15小于35,因此我们将35移动到左子节点的位置。

现在,15的左子节点是35,右子节点是22,它们都大于15,因此shift down操作完成。

但是,这里有一个陷阱需要注意。如果在shift down操作过程中,我们发现一个节点的值小于它的子节点的值,那么我们就需要停止shift down操作,因为此时堆已经建好了。

在上面的例子中,当我们将15移动到左子节点的位置后,我们发现15的左子节点是35,右子节点是22,它们都大于15,因此shift down操作完成。

现在,我们已经将一个随机数组转换成了一个最大堆。

[50, 35, 43, 30, 22, 62, 18, 12, 10, 15]

最大堆的性质是,根节点的值总是最大的,并且每个节点的值都大于或等于其子节点的值。

最后,我们给出堆排序算法的完整代码,帮助读者更好地理解堆排序算法的实现。

def heap_sort(arr):
    """堆排序算法"""

    # 将数组构建成最大堆
    for i in range(len(arr) // 2 - 1, -1, -1):
        heapify(arr, i, len(arr))

    # 将最大堆排序
    for i in range(len(arr) - 1, 0, -1):
        arr[0], arr[i] = arr[i], arr[0]  # 交换堆顶元素和最后一个元素
        heapify(arr, 0, i)  # 重新堆化

    return arr


def heapify(arr, i, n):
    """堆化操作"""

    largest = i  # 假设父节点是最大值

    left = 2 * i + 1  # 左子节点索引
    right = 2 * i + 2  # 右子节点索引

    # 如果左子节点大于父节点
    if left < n and arr[left] > arr[largest]:
        largest = left

    # 如果右子节点大于父节点
    if right < n and arr[right] > arr[largest]:
        largest = right

    # 如果父节点不是最大值
    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]  # 交换父节点和最大值
        heapify(arr, largest, n)  # 递归堆化


if __name__ == "__main__":
    arr = [15, 35, 22, 18, 43, 62, 30, 12, 10, 50]
    print(heap_sort(arr))

输出结果:

[10, 12, 15, 18, 22, 30, 35, 43, 50, 62]