排序算法之堆排序

堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序。

大根堆:每个结点的值都大于其左孩子和右孩子结点的值。所以对任一棵子树:根节点的值都是最大的。

小根堆:每个结点的值都小于其左孩子和右孩子结点的值。所以对任一棵子树:根节点的值都是最小的。

堆可以采用数组存储,结点的标号从 $0$ 开始,则对任一个结点 $i$,其左孩子的结点标号为 $2i+1$,右孩子的结点标号为 $2i+2$。

下面以大根堆为例,介绍堆排序的基本步骤。

    1)首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端。

    2)将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为 $n-1$。

    3)将剩余的 $n-1$ 个数再构造成大根堆,再将顶端数与 $n-1$ 位置的数交换,如此反复执行,便能得到有序数组(一开始就是数组存储的)。

可见这个算法最重要的操作就是:将树调整为大顶堆。

对某个子树进行堆化(Heapify)的前提是:除自身外,该子树的任一棵子树都满足大顶堆结构。

因为 BuildHeap 的过程是自底向上,所以将一棵乱序的树调整为大顶堆的过程可以调用 Heapify,换句话说:将一棵树调整

为大顶堆之前,其任一棵子树就都已经被调整为大顶堆了。

/*
 * n: 树的结点总数
 * i: 对以 i 为根节点的子树做堆化操作
 * 之所以可以使用递归,必须有一个前提条件: 树 i 的任一棵子树都是大顶堆。
   这样至上而下的调整时只需考虑下面的堆结构是否被破坏,而不用担心上面的结构是否被破坏。
 */
void Heapify(int tree[], int n, int i)
{
    if (i >= n) return;

    int c1 = (2 * i) + 1;
    int c2 = (2 * i) + 2;
    int max = i;

    if (c1 < n && tree[c1] > tree[max]) {
        max = c1;
    }
    if (c2 < n && tree[c2] > tree[max]) {
        max = c2;
    }
    // 孩子结点的值比父节点的大,需要交换
    if(max != i) {
        swap(tree[max], tree[i]);
        // 被交换后的子节点,其堆结构可能被破坏,继续向下调整
        Heapify(tree, n, max);
    }
}

void BuildHeap(int tree[], int n)
{
    int last_node = n - 1;
    int parent = (last_node - 1) / 2;

    // 因为是从下到上调整的,所以满足 Heapify 的那个前提
    for(int i = parent; i >= 0; --i) {
        Heapify(tree, n, i);
    }
}

void HeapSort(int tree[], int n)
{
    // 先调整成一个堆
    BuildHeap(tree, n);

    for(int i = n - 1; i >= 0; --i) {
        swap(tree[0], tree[i]);
        Heapify(tree, n - 1, 0);
    }
}

 

posted @ 2020-09-20 07:45  _yanghh  阅读(427)  评论(0编辑  收藏  举报