heap sort

堆排序

堆排序(Heap Sort)是一种基于完全二叉树结构的排序算法。它的主要思想是将待排序的序列构建成一个大顶堆(升序排序时使用)或小顶堆(降序排序时使用),然后依次将堆顶元素与最后一个元素交换,并重新调整堆,直到所有元素都排好序为止。

具体的堆排序算法可以分为以下几个步骤:

  1. 构建堆:将待排序的序列看作完全二叉树,并从最后一个非叶子节点开始,依次向前构建堆。构建堆的过程是将当前节点与其左右子节点进行比较,将最大(或最小)的元素交换到当前位置,并递归调整被交换元素所在的子树,使其满足堆的性质。
  2. 排序:构建堆后,堆顶元素即为序列中最大(或最小)的元素。将堆顶元素与序列的最后一个元素交换,然后将序列长度减1,再重新调整堆,使剩余的元素仍满足堆的性质。重复这个步骤,直到所有元素都排好序为止。

堆排序的时间复杂度为O(nlogn),其中n为待排序序列的长度。由于堆排序是一种原地排序算法,不需要额外的辅助空间,因此空间复杂度为O(1)。

完全二叉树的性质

完全二叉树是一种特殊的二叉树,具有以下性质:

  1. 定义:对于一棵二叉树,如果除了最后一层外,其他层的节点数都达到最大值,并且最后一层的节点依次从左到右排列,那么这棵二叉树就是完全二叉树。

  2. 节点编号:将完全二叉树的节点按照从上到下、从左到右的顺序进行编号,其中根节点编号为1,对于任意一个节点i,其左子节点的编号为2i,右子节点的编号为2i+1。

  3. 节点数量:设完全二叉树的高度为h,则节点总数为2^h - 1,其中^表示乘方运算。

  4. 特点:完全二叉树相比于一般的二叉树有以下特点:

    • 在同样的节点数量下,完全二叉树的高度较小。
    • 完全二叉树的前n/2个节点(其中n为节点总数)都是非叶子节点,而后面的节点都是叶子节点。
    • 如果将完全二叉树的节点按照从上到下、从左到右的顺序进行编号,那么对于任意一个节点i,如果i > 1,则其父节点的编号为i/2。

实现

  • heap_sort.hpp
#include 
#include 

template<typename T>
void PrintHeap(T* data, int len)
{
    std::cout << "++++++++++++++++\n";
    int level = std::ceil(std::log2(len + 1));
    // 最后一行的元素的个数
    int last_row_num = std::pow(2, level);
    int print_num = 0;// 
    for (int level_i = 0; level_i < level; ++level_i)
    {
        // 该层的最大节点数
        int node_num = std::pow(2, level_i);

        // 空白数
        int blan_num = last_row_num - node_num; 
        
        // 节点之间的空白
        int between_blank_num = blan_num / node_num;
        between_blank_num = between_blank_num == 0 ? 1 : between_blank_num;
        // 左边其实位置处的空白数
        int left_blank_num = (blan_num - (node_num - 1) * between_blank_num) / 2;

        //left blank
        for (int i = 0; i < left_blank_num; ++i)
        {
            std::cout << " ";
        }
        // 打印节点数和空白
        for (int i = 0; i < node_num && print_num < len; ++i)
        {
            std::cout << data[print_num];
            print_num += 1;
            for (int j = 0; j < between_blank_num; ++j)
                std::cout << " ";
        }
        for (int i = level - level_i; i >= 0; --i)
        {
            std::cout << std::endl;
        }

    }
    std::cout << "++++++++++++++++\n";
}


template<typename T>
void PrintArray(T* data, int len)
{
    if (!data) return;
    for (int i = 0; i < len - 1; ++i)
    {
        std::cout << data[i] << ",";
    }
    std::cout << data[len - 1] << std::endl;
}


template<typename T>
void Swap(T& a, T& b)
{
    T temp = a;
    a = b;
    b = temp;
}


// 在一棵完全二叉树中,如果index从0开始,那么最后一个非叶子节点的序号是heap_size/2 - 1.
// left_child的index是dad*2 + 1,right_child的序号是dad*2 + 2.
template<typename T>
void MaxHeapify(T* data, int root, int heap_size)
{
    if (!data) return;

    int largest = root;
    int left = (root << 1) + 1;
    int right = left + 1;
    // 在一个非叶子节点中找到最大的元素
    if (left < heap_size && data[left] > data[largest])
        largest = left;
    if (right < heap_size && data[right] > data[largest])
        largest = right;
    if (largest != root)//根节点不是最大的,则把最大的点和根节点交换,
    {
        Swap(data[largest], data[root]);
        // 交换以后root为最大点,但是刚才被交换的largest位置不一定满足大顶堆,所以需要在largest处最大堆化。
        MaxHeapify(data, largest, heap_size);
    }

}

template<typename T>
void HeapSort(T* data, int heap_size)
{
    if (!data) return;
    //构建大顶堆
    for (int i = heap_size / 2 - 1; i >= 0; --i)
    {
        //PrintHeap(data, heap_size);
        MaxHeapify(data, i, heap_size);
    }
    
    for (int i = heap_size - 1; i >= 0; --i)
    {
        //大顶堆的第一个元素是最大的,把最大的元素交换到最后,然后重新"最大堆化"
        Swap(data[0], data[i]);
        MaxHeapify(data, 0, i);
    }

}
  • main.cpp
#include
#include "heap_sort.hpp"


void TestHeapSort()
{
   int a[] = {1,2,3,1,0,2,3,1,0,0,0,1,32,3,444,56,12,45,0,1,1,1,12,3,3,3,4,5,44,0};
   int len = sizeof(a)  / sizeof(a[0]);
   PrintArray(a, len);
   PrintHeap(a, len);
   HeapSort(a, len);
   PrintHeap(a, len);
   PrintArray(a, len);


}


int main()
{
   TestHeapSort();
   return 0;
}

heap sort_第1张图片

你可能感兴趣的:(数据结构与算法,算法,数据结构,排序算法)