C++ 大根堆排序学习笔记

 更新时间:2023年10月29日 15:49:07   作者:Totn  
这篇文章主要为大家介绍了C++ 大根堆排序的学习教程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

什么是大根堆

大根堆是完全二叉树,其中每个节点都比其子节点大,而根节点是最大的节点,所以称为“大根” 堆;

大根堆排序则是基于大根堆实现的排序算法,基本思想是将待排序的序列组成一个大根堆,然后依次取出顶元素(即最大元素),并将剩余元素构成新的大根堆,重复以上过程直到最后一个元素,就得到了完全排好序的序列。大根堆排序是一种原地排序算法,所以其空间复杂度为O(1),只需要常数级别的空间;而时间复杂度则为O(nlogn)

排序步骤

  • 1 构建大根堆

我们从序列的最后一个非叶子节点开始,依次将其与其子节点进行比较,如果有子节点比它大,则将它与最大的子节点交换位置,然后再以交换后的子节点作为根节点,继续向下比较和交换,直到该节点成为叶子节点或者它的子节点都比它小为止。这样就可以保证每个节点都比其子节点大,从而构建成一个大根堆。

  • 2 取出堆顶元素

由于大根堆的根节点是最大的元素,因此我们可以直接取出堆顶元素,并将其放到序列的末尾。

  • 3 重新构建大根堆

取出堆顶元素后,我们需要重新构建剩余元素的大根堆。具体操作是将序列的第一个元素作为根节点,依次将其与其子节点进行比较,如果有子节点比它大,则将它与最大的子节点交换位置,然后再以交换后的子节点作为根节点,继续向下比较和交换,直到该节点成为叶子节点或者它的子节点都比它小为止。这样就可以保证剩余元素构成一个新的大根堆。

  • 4 重复执行步骤2和步骤3
    重复执行步骤2和步骤3,直到所有元素都被取出,序列就由大到小排好了。

C++代码实现

#include <iostream>
#include <vector>
// 打印数组
void printArr(std::vector<int>& arr, int len = 0);
// 大根堆排序主方法
void heapify(std::vector<int>&, int, int);
// 大根堆排序
void heapSort(std::vector<int>&);
// 大堆排序
void heapify(std::vector<int>& arr, int len, int index) {
    int largest = index;
    std::cout << "设置当前节点arr[" << index << "]=" << arr[index] << "为根节点" << std::endl;
    int left = 2 * index + 1;
    std::cout << "左节点索引: " << left << std::endl;
    int right = 2 * index + 2;
    std::cout << "右节点索引: " << right << std::endl;
    if (left < len) {
        std::cout << "比较左节点arr[" << left << "]=" << arr[left] << "与根点节大小" << std::endl;
    } else {
        std::cout << "左节点超出范围,即不存在" << std::endl;
    }
    if (left < len && arr[left] > arr[largest])
    {
        largest = left;
    }
   if (right < len) {
        std::cout << "比较右节点arr[" << right << "]=" << arr[right] << "与根点节大小" << std::endl;
    } else {
        std::cout << "右节点超出范围,即不存在" << std::endl;
    }
    if (right < len && arr[right] > arr[largest])
    {
        largest = right;
    }
    if (largest != index) {
        std::cout << "根节点小于子节点,交换根(arr[" << index << "]=" << arr[index] << ")与子(arr[" << largest << "]=" << arr[largest] << ")节点" << std::endl;
        std::swap(arr[index], arr[largest]);
        printArr(arr, len);
        heapify(arr, len, largest);
    }
}
void heapSort(std::vector<int>& arr) {
    int len = arr.size();
    // 构建大根堆, 从最后一个非叶子节点向下调整
    for (int i = len / 2 - 1; i >= 0; i--)
    {
        heapify(arr, len, i);
    }
    std::cout << "大根堆: ";
    printArr(arr);
    // 依次将堆顶元素与堆的最后一个元素交换,并调整堆
    for (int i = len - 1; i > 0; i--)
    {
        std::cout << "将最大值(arr[0]=" << arr[0] << ")与最后元素(arr[" << i << "]=" << arr[i] << ")交换" << std::endl;
        std::swap(arr[0], arr[i]);
        std::cout << "重新设置大根堆: ";
        printArr(arr, i);
        heapify(arr, i, 0);
    }
}
void printArr(std::vector<int>& arr, int len) {
    std::cout << "{ ";
    if (len <= 0) {
        for (auto &&n : arr)
        {
            std::cout << n << " ";
        }
    } else {
        for (int i = 0; i < len; i++)
        {
            std::cout << arr[i] << " ";
        }
    }
    std::cout << "}" << std::endl;
}
int main() {
    std::vector<int> arr = {1, 39, 2, 66, 23, 5, 6, 9, 4, 8};
    std::cout << "原数组: ";
    printArr(arr);
    heapSort(arr);
    std::cout << "排序后: ";
    printArr(arr);
}

排序过程

编译以上代码后执行,得到以下结果

原数组: { 1 39 2 66 23 5 6 9 4 8 }
设置当前节点arr[4]=23为根节点
左节点索引: 9
右节点索引: 10
比较左节点arr[9]=8与根点节大小
右节点超出范围,即不存在
设置当前节点arr[3]=66为根节点
左节点索引: 7
右节点索引: 8
比较左节点arr[7]=9与根点节大小
比较右节点arr[8]=4与根点节大小
设置当前节点arr[2]=2为根节点
左节点索引: 5
右节点索引: 6
比较左节点arr[5]=5与根点节大小
比较右节点arr[6]=6与根点节大小
根节点小于子节点,交换根(arr[2]=2)与子(arr[6]=6)节点
{ 1 39 6 66 23 5 2 9 4 8 }
设置当前节点arr[6]=2为根节点
左节点索引: 13
右节点索引: 14
左节点超出范围,即不存在
右节点超出范围,即不存在
设置当前节点arr[1]=39为根节点
左节点索引: 3
右节点索引: 4
比较左节点arr[3]=66与根点节大小
比较右节点arr[4]=23与根点节大小
根节点小于子节点,交换根(arr[1]=39)与子(arr[3]=66)节点
{ 1 66 6 39 23 5 2 9 4 8 }
设置当前节点arr[3]=39为根节点
左节点索引: 7
右节点索引: 8
比较左节点arr[7]=9与根点节大小
比较右节点arr[8]=4与根点节大小
设置当前节点arr[0]=1为根节点
左节点索引: 1
右节点索引: 2
比较左节点arr[1]=66与根点节大小
比较右节点arr[2]=6与根点节大小
根节点小于子节点,交换根(arr[0]=1)与子(arr[1]=66)节点
{ 66 1 6 39 23 5 2 9 4 8 }
设置当前节点arr[1]=1为根节点
左节点索引: 3
右节点索引: 4
比较左节点arr[3]=39与根点节大小
比较右节点arr[4]=23与根点节大小
根节点小于子节点,交换根(arr[1]=1)与子(arr[3]=39)节点
{ 66 39 6 1 23 5 2 9 4 8 }
设置当前节点arr[3]=1为根节点
左节点索引: 7
右节点索引: 8
比较左节点arr[7]=9与根点节大小
比较右节点arr[8]=4与根点节大小
根节点小于子节点,交换根(arr[3]=1)与子(arr[7]=9)节点
{ 66 39 6 9 23 5 2 1 4 8 }
设置当前节点arr[7]=1为根节点
左节点索引: 15
右节点索引: 16
左节点超出范围,即不存在
右节点超出范围,即不存在
大根堆: { 66 39 6 9 23 5 2 1 4 8 }
将最大值(arr[0]=66)与最后元素(arr[9]=8)交换
重新设置大根堆: { 8 39 6 9 23 5 2 1 4 }
设置当前节点arr[0]=8为根节点
左节点索引: 1
右节点索引: 2
比较左节点arr[1]=39与根点节大小
比较右节点arr[2]=6与根点节大小
根节点小于子节点,交换根(arr[0]=8)与子(arr[1]=39)节点
{ 39 8 6 9 23 5 2 1 4 }
设置当前节点arr[1]=8为根节点
左节点索引: 3
右节点索引: 4
比较左节点arr[3]=9与根点节大小
比较右节点arr[4]=23与根点节大小
根节点小于子节点,交换根(arr[1]=8)与子(arr[4]=23)节点
{ 39 23 6 9 8 5 2 1 4 }
设置当前节点arr[4]=8为根节点
左节点索引: 9
右节点索引: 10
左节点超出范围,即不存在
右节点超出范围,即不存在
将最大值(arr[0]=39)与最后元素(arr[8]=4)交换
重新设置大根堆: { 4 23 6 9 8 5 2 1 }
设置当前节点arr[0]=4为根节点
左节点索引: 1
右节点索引: 2
比较左节点arr[1]=23与根点节大小
比较右节点arr[2]=6与根点节大小
根节点小于子节点,交换根(arr[0]=4)与子(arr[1]=23)节点
{ 23 4 6 9 8 5 2 1 }
设置当前节点arr[1]=4为根节点
左节点索引: 3
右节点索引: 4
比较左节点arr[3]=9与根点节大小
比较右节点arr[4]=8与根点节大小
根节点小于子节点,交换根(arr[1]=4)与子(arr[3]=9)节点
{ 23 9 6 4 8 5 2 1 }
设置当前节点arr[3]=4为根节点
左节点索引: 7
右节点索引: 8
比较左节点arr[7]=1与根点节大小
右节点超出范围,即不存在
将最大值(arr[0]=23)与最后元素(arr[7]=1)交换
重新设置大根堆: { 1 9 6 4 8 5 2 }
设置当前节点arr[0]=1为根节点
左节点索引: 1
右节点索引: 2
比较左节点arr[1]=9与根点节大小
比较右节点arr[2]=6与根点节大小
根节点小于子节点,交换根(arr[0]=1)与子(arr[1]=9)节点
{ 9 1 6 4 8 5 2 }
设置当前节点arr[1]=1为根节点
左节点索引: 3
右节点索引: 4
比较左节点arr[3]=4与根点节大小
比较右节点arr[4]=8与根点节大小
根节点小于子节点,交换根(arr[1]=1)与子(arr[4]=8)节点
{ 9 8 6 4 1 5 2 }
设置当前节点arr[4]=1为根节点
左节点索引: 9
右节点索引: 10
左节点超出范围,即不存在
右节点超出范围,即不存在
将最大值(arr[0]=9)与最后元素(arr[6]=2)交换
重新设置大根堆: { 2 8 6 4 1 5 }
设置当前节点arr[0]=2为根节点
左节点索引: 1
右节点索引: 2
比较左节点arr[1]=8与根点节大小
比较右节点arr[2]=6与根点节大小
根节点小于子节点,交换根(arr[0]=2)与子(arr[1]=8)节点
{ 8 2 6 4 1 5 }
设置当前节点arr[1]=2为根节点
左节点索引: 3
右节点索引: 4
比较左节点arr[3]=4与根点节大小
比较右节点arr[4]=1与根点节大小
根节点小于子节点,交换根(arr[1]=2)与子(arr[3]=4)节点
{ 8 4 6 2 1 5 }
设置当前节点arr[3]=2为根节点
左节点索引: 7
右节点索引: 8
左节点超出范围,即不存在
右节点超出范围,即不存在
将最大值(arr[0]=8)与最后元素(arr[5]=5)交换
重新设置大根堆: { 5 4 6 2 1 }
设置当前节点arr[0]=5为根节点
左节点索引: 1
右节点索引: 2
比较左节点arr[1]=4与根点节大小
比较右节点arr[2]=6与根点节大小
根节点小于子节点,交换根(arr[0]=5)与子(arr[2]=6)节点
{ 6 4 5 2 1 }
设置当前节点arr[2]=5为根节点
左节点索引: 5
右节点索引: 6
左节点超出范围,即不存在
右节点超出范围,即不存在
将最大值(arr[0]=6)与最后元素(arr[4]=1)交换
重新设置大根堆: { 1 4 5 2 }
设置当前节点arr[0]=1为根节点
左节点索引: 1
右节点索引: 2
比较左节点arr[1]=4与根点节大小
比较右节点arr[2]=5与根点节大小
根节点小于子节点,交换根(arr[0]=1)与子(arr[2]=5)节点
{ 5 4 1 2 }
设置当前节点arr[2]=1为根节点
左节点索引: 5
右节点索引: 6
左节点超出范围,即不存在
右节点超出范围,即不存在
将最大值(arr[0]=5)与最后元素(arr[3]=2)交换
重新设置大根堆: { 2 4 1 }
设置当前节点arr[0]=2为根节点
左节点索引: 1
右节点索引: 2
比较左节点arr[1]=4与根点节大小
比较右节点arr[2]=1与根点节大小
根节点小于子节点,交换根(arr[0]=2)与子(arr[1]=4)节点
{ 4 2 1 }
设置当前节点arr[1]=2为根节点
左节点索引: 3
右节点索引: 4
左节点超出范围,即不存在
右节点超出范围,即不存在
将最大值(arr[0]=4)与最后元素(arr[2]=1)交换
重新设置大根堆: { 1 2 }
设置当前节点arr[0]=1为根节点
左节点索引: 1
右节点索引: 2
比较左节点arr[1]=2与根点节大小
右节点超出范围,即不存在
根节点小于子节点,交换根(arr[0]=1)与子(arr[1]=2)节点
{ 2 1 }
设置当前节点arr[1]=1为根节点
左节点索引: 3
右节点索引: 4
左节点超出范围,即不存在
右节点超出范围,即不存在
将最大值(arr[0]=2)与最后元素(arr[1]=1)交换
重新设置大根堆: { 1 }
设置当前节点arr[0]=1为根节点
左节点索引: 1
右节点索引: 2
左节点超出范围,即不存在
右节点超出范围,即不存在
排序后: { 1 2 4 5 6 8 9 23 39 66 }

以上就是C++ 大根堆排序学习笔记的详细内容,更多关于C++ 大根堆排序的资料请关注脚本之家其它相关文章!

相关文章

  • C++编写生成不重复的随机数代码

    C++编写生成不重复的随机数代码

    本文给大家汇总介绍了3种c++实现生成不重复的随机数的函数,十分的简单实用,有需要的小伙伴可以参考下。
    2015-05-05
  • C++实现字符串切割的两种方法

    C++实现字符串切割的两种方法

    这篇文章主要介绍了C++实现字符串切割的两种方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • 深入解析C++ Data Member内存布局

    深入解析C++ Data Member内存布局

    本篇文章是对C++中的Data Member内存布局进行了详细的分析介绍,需要的朋友参考下
    2013-07-07
  • C语言实现随机生成6位数密码

    C语言实现随机生成6位数密码

    这篇文章主要为大家详细介绍了如何使用C语言实现一个简单而实用的随机密码生成器,该生成器将生成包含字母、数字和特殊字符的随机密码,有需要的小伙伴可以参考下
    2023-11-11
  • C++中生成随机数的方法总结

    C++中生成随机数的方法总结

    这篇文章主要介绍了C++中生成随机数的方法总结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-05-05
  • c语言_构建一个静态二叉树实现方法

    c语言_构建一个静态二叉树实现方法

    下面小编就为大家带来一篇c语言_构建一个静态二叉树实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • C++常见异常处理原理及代码示例解析

    C++常见异常处理原理及代码示例解析

    这篇文章主要介绍了C++常见异常处理原理及代码示例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • C语言与C++动态通讯录超详细实现流程

    C语言与C++动态通讯录超详细实现流程

    这篇文章主要为大家介绍了C语言与C++动态实现通讯录,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-05-05
  • C++多线程中的锁和条件变量使用教程

    C++多线程中的锁和条件变量使用教程

    这篇文章主要介绍了C++多线程中的锁和条件变量使用,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-11-11
  • 一文带你搞懂C语言动态内存管理

    一文带你搞懂C语言动态内存管理

    动态内存是指在堆上分配的内存,而静态内存是指在栈上分配的内存。本文将通过几个示例带大家深入了解一下C语言的动态内存管理,需要的可以参考一下
    2022-11-11

最新评论