堆排序
堆排序(Heap Sort)是一种基于完全二叉树结构的排序算法。它的主要思想是将待排序的序列构建成一个大顶堆(升序排序时使用)或小顶堆(降序排序时使用),然后依次将堆顶元素与最后一个元素交换,并重新调整堆,直到所有元素都排好序为止。
具体的堆排序算法可以分为以下几个步骤:
- 构建堆:将待排序的序列看作完全二叉树,并从最后一个非叶子节点开始,依次向前构建堆。构建堆的过程是将当前节点与其左右子节点进行比较,将最大(或最小)的元素交换到当前位置,并递归调整被交换元素所在的子树,使其满足堆的性质。
- 排序:构建堆后,堆顶元素即为序列中最大(或最小)的元素。将堆顶元素与序列的最后一个元素交换,然后将序列长度减1,再重新调整堆,使剩余的元素仍满足堆的性质。重复这个步骤,直到所有元素都排好序为止。
堆排序的时间复杂度为O(nlogn),其中n为待排序序列的长度。由于堆排序是一种原地排序算法,不需要额外的辅助空间,因此空间复杂度为O(1)。
完全二叉树的性质
完全二叉树是一种特殊的二叉树,具有以下性质:
-
定义:对于一棵二叉树,如果除了最后一层外,其他层的节点数都达到最大值,并且最后一层的节点依次从左到右排列,那么这棵二叉树就是完全二叉树。
-
节点编号:将完全二叉树的节点按照从上到下、从左到右的顺序进行编号,其中根节点编号为1,对于任意一个节点i,其左子节点的编号为2i,右子节点的编号为2i+1。
-
节点数量:设完全二叉树的高度为h,则节点总数为2^h - 1,其中^表示乘方运算。
-
特点:完全二叉树相比于一般的二叉树有以下特点:
- 在同样的节点数量下,完全二叉树的高度较小。
- 完全二叉树的前n/2个节点(其中n为节点总数)都是非叶子节点,而后面的节点都是叶子节点。
- 如果将完全二叉树的节点按照从上到下、从左到右的顺序进行编号,那么对于任意一个节点i,如果i > 1,则其父节点的编号为i/2。
实现
- heap_sort.hpp
#include <iostream>
#include <cmath>
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<iostream>
#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;
}