堆的一些知识点回顾
堆是一个完全二叉树
完全二叉树即是:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
堆满足两个性质: 堆的每一个父节点数值都大于(或小于)其子节点,堆的每个左子树和右子树也是一个堆。
堆分为最小堆和最大堆。最大堆就是每个父节点的数值要大于孩子节点,最小堆就是每个父节点的数值要小于孩子节点。排序要求从小到大的话,我们需要建立最大堆,反之建立最小堆。
堆的存储一般用数组来实现。假如父节点的数组下标为i的话,那么其左右节点的下标分别为:(2i+1)和 (2i+2)。如果孩子节点的下标为j的话,那么其父节点的下标为(j-1)/2。
完全二叉树中,假如有n个元素,那么在堆中最后一个父节点位置为(n/2-1)。
算法思想
建立堆(最大堆,or最小堆)
调整堆
堆排序:交换堆顶元素和堆的最后一个元素
如何创建堆:
由于堆是使用数组保存的,所有我的理解是,建堆的过程就是堆化数组,即使给定的数组具有堆的性质。堆化的过程其实就是调整堆的过程,我们把调整堆的过程定义成一个单独的函数
void AdjustHeap(int heap[], int root_index, int heap_length),
第一个参数:代表堆的数组,
第二个参数:要调整的堆的根节点在数组中的下标,注意是下标。
第三个参数是堆的长度,也就是待建堆的数组长度,或者说 待调的数组元素个数。
调整堆的过程如下:
1.根据根节点下标,确定左右子节点的下标。
2.找出根节点和左右子节点中最小值的下标,注意该过程要判断边界,即左右子节点的下标是否超出堆的长度。
3.如果最小值的下标等于根节点的下标,函数返回。
4.否则,交换把根节点与最小值交换,由于交换可能破坏了最小值所在子树的对性质,所以递归调用AdjustHeap函数。
堆化的时候,从哪个节点开始调整堆呢?
这个也是建堆的关键,我们可以这样想,子节点已经是堆了,无需在调整了,所以我们没必要从最后一个节点开始调整,一般是从(heap_length/2)开始调节,一直到下标1为止(堆的下标一般从1开始,0位置不用),从(heap_length/2)开始调节的原因是完全二叉树的性质决定的。这样遍历一遍,对每个节点调用堆调整函数就完成建堆了。
所以从(heap_length/2)处,开始自底向上遍历,遍历一次,调整一次。
堆排序:
取出堆的第一个元素,调整堆,循环下去,知道取完为止。
实现的过程就是
1.先把堆顶元素和堆的最后一个元素互换,
2.然后堆长度减1,再调整堆,注意此时只用从上面0这个位置,调整就行,且只调一次。
原因:
1.为什么从头部调:此时下面的都是大顶堆,只有头部不满足要求,所以从头调,即使调了之后使得下面的顺序不满足了,也因为一次调堆的函数,包含了向下的递归调堆。
2.为什么只调一次:上面建堆时是自下往上调,而这个是自顶往下,首先顶部的上面没有元素了,其次一次调堆函数的调用包含了自顶往下的过程
这样就使堆排序的空间复杂度为O(1)了,注意的是大顶堆排完序之后数组中元素的顺序是从小到大,小顶堆是从大到小。
代码实现:
1.建堆:
void make_heap(int *a, int len)
{
for(int i = (len-1)/2; i >= 0; --i) //遍历每个 非叶子节点
adjust_heap(a, i, len);//不用考虑那么多, 用面向对象的思乡去考虑,
} //这个函数的作用就是用来使 当前节点的子树 符合 堆的规律
2… 要使某节点的当前节点的字数符合 堆规律,需要以下操作:
void adjust_heap(int* a, int node, int size)
{
int left = 2*node + 1;
int right = 2*node + 2;
int max = node;
if( left < size && a[left] > a[max])
max = left;
if( right < size && a[right] > a[max])
max = right;
if(max != node)
{
swap( a[max], a[node]); //交换节点
adjust_heap(a, max, size); 很关于这里递归的解释请看下面
}
}
————————————————
版权声明:本文为CSDN博主「pursue_my_life」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
排序:
for(int i = len - 1; i >= 0; i--)
{
swap(a[0], a[i]); // 将当前最大的放置到数组末尾
adjust_heap(a, 0 , i); // 将未完成排序的部分继续进行堆排序
}
完整代码:
#include <iostream>
using namespace std;
void adjust_heap(int* a, int node, int size)
{
int left = 2*node + 1;
int right = 2*node + 2;
int max = node;
if( left < size && a[left] > a[max])
max = left;
if( right < size && a[right] > a[max])
max = right;
if(max != node)
{
swap( a[max], a[node]);
adjust_heap(a, max, size);
}
}
void heap_sort(int* a, int len)//传入的len是长度,元素个数
{
for(int i = len/2 -1; i >= 0; --i)
adjust_heap(a, i, len);
//调堆,传入的i是开始调的下标
//第一个for代表一共调这么多次
for(int i = len - 1; i >= 0; i--)
{
//这个for代表取len次,每次只调一次堆
swap(a[0], a[i]); // 将当前最大的放置到数组末尾
adjust_heap(a, 0 , i); // 将未完成排序的部分继续进行堆排序
}
}
int main()
{
int a[10] = {3, 2, 7, 4, 2, -999, -21, 99, 0, 9 };
int len= sizeof(a) / sizeof(int);
for(int i = 0; i < len; ++i)
cout << a[i] << ' ';
cout << endl;
heap_sort(a, len);
for(int i = 0; i < len; ++i)
cout << a[i] << ' ';
cout << endl;
return 0;
}