C++堆排序实现

55 阅读4分钟

堆排的前提是基于完全二叉树。其核心优势在于时间复杂度稳定为 O (n log n) ,且是原地排序(仅需常数级额外空间),广泛应用于大规模数据排序场景。

  • 大根堆:每个父节点的值 ≥ 其左右子节点的值(堆顶为最大值)。
  • 小根堆:每个父节点的值 ≤ 其左右子节点的值(堆顶为最小值)。

堆排序通常采用大根堆实现(升序排序),步骤如下:

  1. 构建大根堆:将无序数组转换为大根堆(初始堆)。
  2. 交换堆顶与堆尾:将堆顶(最大值)与当前堆的最后一个元素交换,此时最大值已放置在数组末尾(有序区)。
  3. 调整堆:将剩余元素(无序区)重新调整为大根堆,重复步骤 2 和 3,直到整个数组有序。

假设一个数组arr从零下标开始,最后一个非叶子节点的下标是(n / 2) - 1, 其中n是数组的长度。

319AD4E0961B4F4064A9B31CCD049FD9.png

我们就可以从最后一个非叶子节点从下往上构建堆,因为以叶子节点为堆顶看作一棵树已经是一个符合要求的堆了(不管是大根堆还是小根堆)。我们先以大根堆为例子构建堆,当我们已经构建好一个堆后,arr[0]一定是最大的,我们只需要将arr[0]与arr[n -1]交换位置,再维护arr[0]到arr[n - 2]这段数组成新的堆,然后再交换arr[0]和arr[n - 2],这样不断下去就可以将数组进行升序排序。

代码实现: 通过模板可以使排序对不同类型的数据进行排序

#include <vector>
#include <functional>
using namespace std;

/**
 * 堆调整函数(下滤操作)
 * 将指定节点向下调整,使其满足堆的性质
 * @tparam T 数据类型
 * @tparam Comp 比较器类型
 * @param arr 待调整的数组
 * @param start 需要调整的节点下标
 * @param len 当前堆的末尾下标
 * @param comp 比较器函数对象,定义元素的比较规则
 */
template<typename T, typename Comp>
void Heap_Adjust(vector<T>& arr, int start, int len, Comp comp)
{
    T temp = arr[start];  // 保存当前节点的值
    // 从start节点的左孩子开始,沿较大的孩子节点向下筛选
    for(int i = 2 * start + 1; i <= len; i = 2 * i + 1)
    {
        // 如果存在右孩子且右孩子比左孩子更符合堆性质(更大或更小取决于comp)
        if(i < len && comp(arr[i], arr[i + 1]))
        {
            i++;  // 选择右孩子
        }
        // 如果当前节点值已经比孩子节点更符合堆性质,则不需要调整
        if(!comp(temp, arr[i]))
        {
            break;
        }
        // 将较大的孩子节点值上移到父节点位置
        arr[start] = arr[i];
        start = i;  // 继续向下调整
    }
    // 将最初保存的值放到最终位置
    arr[start] = temp;
}

/**
 * 堆排序主函数
 * 使用堆排序算法对数组进行排序
 * @tparam T 数据类型
 * @tparam Comp 比较器类型,默认为std::less<T>(升序)
 * @param arr 待排序的数组
 * @param comp 比较器函数对象,定义排序顺序
 */
template<typename T, typename Comp = less<T>>
void Heap_Sort(vector<T>& arr, Comp comp = Comp())
{
    int n = (int)arr.size();
    if(n <= 1) return;  // 数组为空或只有一个元素时直接返回
    
    // 构建初始堆:从最后一个非叶子节点开始,向前逐个调整
    // 最后一个非叶子节点的下标为 n/2 - 1
    for(int i = n / 2 - 1; i >= 0; --i)
    {
        Heap_Adjust(arr, i, n - 1, comp);
    }
    
    // 排序过程:每次将堆顶元素(最大或最小)与堆尾元素交换,然后调整堆
    for(int j = n - 1; j > 0; --j)
    {
        // 交换堆顶和堆尾元素
        T t = arr[0];
        arr[0] = arr[j];
        arr[j] = t;
        // 调整剩余元素,使其满足堆性质
        Heap_Adjust(arr, 0, j - 1, comp);
    }
}

int main()
{
    // 测试整数升序排序(默认使用std::less,即升序)
    vector<int> v1 = {1, 3, 2, 6, 5, 4, 9, 8, 7, 0};
    Heap_Sort(v1);
    cout << "升序排序(整数): ";
    for(size_t i = 0; i < v1.size(); ++i)
    {
        cout << v1[i] << (i + 1 == v1.size() ? '\n' : ' ');
    }

    // 测试整数降序排序(使用std::greater)
    vector<int> v2 = {1, 3, 2, 6, 5, 4, 9, 8, 7, 0};
    Heap_Sort(v2, greater<int>());
    cout << "降序排序(整数): ";
    for(size_t i = 0; i < v2.size(); ++i)
    {
        cout << v2[i] << (i + 1 == v2.size() ? '\n' : ' ');
    }

    // 测试浮点数排序
    vector<double> v3 = {3.14, 2.71, 1.41, 1.73, 0.57};
    Heap_Sort(v3);
    cout << "升序排序(浮点数): ";
    for(size_t i = 0; i < v3.size(); ++i)
    {
        cout << v3[i] << (i + 1 == v3.size() ? '\n' : ' ');
    }

    // 测试字符串排序(按字典序)
    vector<string> v4 = {"banana", "apple", "cherry", "date"};
    Heap_Sort(v4);
    cout << "升序排序(字符串): ";
    for(size_t i = 0; i < v4.size(); ++i)
    {
        cout << v4[i] << (i + 1 == v4.size() ? '\n' : ' ');
    }

    return 0;
}