堆排序

1,055 阅读2分钟

关于完全二叉树最后一个非叶子节点的索引求解思路为:
如果父节点的索引为x,则其子节点为2x + 1 和 2x + 2,
假设某数组有n个元素,则最后一个元素的索引为n-1,
设父节点索引为m,分2种情况讨论:

  • 最后一个节点位于左子树,则n-1=2*m+1, 则m=(n-2)/2
  • 最后一个节点位于右子树,则n-1=2*m+2,则m=(n-3)/2,最终得到的都是m=n/2-1

思路直接参考源码注释

/*
 * 1、先将当前的数组建成大顶堆
 * 2、依次交换index 0 的元素和当前的最大索引的元素,比如第一次就是index 0 和 最后一个交换
 * 因为第一个已经是最大的了,所以交换之后,最后一个即为最大元素,交换之后,不一定满足大顶堆
 * 的特性,进入第三步
 * 3、重新针对index 0 再调整堆 为大顶堆
 * 4、再交换index 0 和 当前的最大索引交换,直至结束,就完成了数组的排序
 */

#include <iostream>
using namespace std;

void swap(int* arr, int a, int b)
{
    int tmp = arr[a];
    arr[a] = arr[b];
    arr[b] = tmp;
}

void adjust(int* arr, int i, int max_index)
{
    // 针对每一个非叶子节点,先找出左右子节点中较大的一个值
    // 然后拿较大的值与当前的节点对比,如果当前节点小,则将
    // 当前节点与较大的值交换(因为要得到一个大顶堆),交换
    // 之后可能会导致后面的子树不平衡了,继续往下处理,即i = larger_index这一步
    while(i * 2 + 1 <= max_index) {
        int j = i * 2 + 1;
        int larger_index = j;
        if (j + 1 <= max_index && arr[j + 1] > arr[j]) {
            larger_index = j + 1;
        }
        if (arr[larger_index] > arr[i]) {
            swap(arr, i, larger_index);
        } else {
            break;
        }
        i = larger_index;
    }
}

void heap_sort(int* arr, int num)
{
    // 这一步是将当前的数组建成大顶堆,具体就是从最后一个非叶子节点开始往上回溯
    // 最后一个非叶子节点为元素个数num/2 - 1
    for (int i = num / 2 - 1; i >= 0; i--) {
        adjust(arr, i, num - 1);
    }

    num--;
    while(num>0) {
        // 每次都将index 0的元素与当前的最大index值交换,这样当前的最大值就下沉到最后
        swap(arr, 0, num);
        // 交换之后,大顶堆的特性可能被破坏,重新调整回来
        adjust(arr, 0, --num);
    }
}

int main()
{
    int arr[] = {2, 4, 1, 99, 8, 10};
    heap_sort(arr, sizeof(arr) / sizeof(int));
    for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
        cout << arr[i] << "\t";
    }
    cout << endl;
    return 0;
}