C++ 大根堆排序学习笔记
作者:Totn
什么是大根堆
大根堆是完全二叉树,其中每个节点都比其子节点大,而根节点是最大的节点,所以称为“大根” 堆;
大根堆排序则是基于大根堆实现的排序算法,基本思想是将待排序的序列组成一个大根堆,然后依次取出顶元素(即最大元素),并将剩余元素构成新的大根堆,重复以上过程直到最后一个元素,就得到了完全排好序的序列。大根堆排序是一种原地排序算法,所以其空间复杂度为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++ 大根堆排序的资料请关注脚本之家其它相关文章!