这两天突然想到了堆排序,然后就写了一下,并花点时间整理记录。
堆说白了就是一个二叉树,不过不是普通的二叉树,堆是一个完全二叉树。二叉树有普通的二叉树,完全二叉树和满二叉树。
完全二叉树:除最后一层外,所有的层数即是满节点。层间节点和层(除最后一层)之间存在:, 是层数。
满二叉树:除叶子节点外,所有的节点都是满子节点,即2个子节点。国内也有说是必须节点数达成当前层数最大节点数的,即是,这里以国外教材为准(反正我们教的就是这样)。
继续来说完全二叉树,可知节点间具有以下关系:对于节点pos,左子节点为:2*pos+1;右子节点为:2*pos+2。
堆通常分为最大堆和最小堆,最大堆任意父节点都比他的子节点要大,相对的最小堆即是,任意的父节点都比他的子节点要小。对堆得操作有:建堆,元素插入,元素删除。
建堆:给定一个无序的数组,将数组以堆的形式从新组织。
元素插入/删除:因为元素的插入/删除会影响堆的结构,所以需要进行堆的维护。
下面以建立最大堆来进行分析。给定数组,建立完全二叉树
{2, 5, 3, 9, 0, 4, 6, 1, 7, 8}
显然这不是一个最大堆,因为每个父节点都需要比子节点大。像0,5,2,3(这里只节点存储的数值,下同)这些,都需要下沉。建堆的过程,其实就是小节点下沉的过程,下沉当然从非叶子节点开始(叶子节点没法沉了)。
节点下沉:因为是最大堆,所以需要节点和左右子节点的最大值比较(如果是最小堆则是和最小值比较)。如果父节点比子节点最大值大,则符合要求,不需要下沉。否则需要和最大值交换位置。从0开始,和8交换位置;9不需要动;以5的下沉为例:
这里跳过了3(也就是2号节点)的下沉,实际是写代码的时候,是从第一个非叶子节点(倒数)往前依次下沉的过程。这样所有的非叶子节点都下沉完之后,我们的最大堆也就建成了。
#include <iostream>
#include <vector>
using namespace std;
void buildHeap(int *, int); //建堆
void downadj(int *, int, int); //节点下沉
int heapFirst(int *, int); //删除堆顶节点,并返回
void printArray(int *array, int len)
{
for(int i=0;i<len;i++)
cout <<array[i] <<" ";
cout <<endl;
}
int main()
{
int array[10] = {2, 5, 3, 9, 0, 4, 6, 1, 7, 8};
int len = 10;
cout <<"original: ";
printArray(array, len);
// // downadj(array, 4, len);
// // printArray(array, len);
// downadj(array, 3, len);
buildHeap(array, len);
cout <<"\nbuild heap: ";
printArray(array, len);
return 0;
}
void buildHeap(int *array, int len)
{
int pos = len - 1;
int par = (pos - 1)/2;
for(int i=par;i>=0;i--){
cout <<"I am in" <<" pos is:" <<i <<endl;
downadj(array, i, len);
}
}
void downadj(int *array, int pos, int len)
{
int tem = array[pos];
int childleft = 2*pos + 1;
int childright = 2*pos + 2;
while(childleft < len){
if(childright < len && tem < array[childright] && array[childleft] < array[childright]){
array[pos] = array[childright];
pos = childright;
} else if(tem < array[childleft]){
array[pos] = array[childleft];
pos = childleft;
} else {
break;
}
childleft = 2*pos + 1;
childright = 2*pos + 2;
}
array[pos] = tem;
}
结果如下:
建成最大堆离我们的堆排序也就是差不多是完成了,既然最大堆的根节点是最大元素,那每次取最大堆的堆顶,然后删除根节点,进行最大堆的维护。那依次取出的不就是从大到小排序的元素吗?删除根节点,堆元素减1,所以节点数做响应的减少,最简单的方法就是直接将最后一个节点(叶节点)和根节点进行交换,然后做根节点(由最后的叶节点交换而来)的下沉。
懒得分析了,有时间再补充吧。看添加代码:
// 在上面代码基础上添加
int main()
{
int array[10] = {2, 5, 3, 9, 0, 4, 6, 1, 7, 8};
int len = 10;
cout <<"original: ";
printArray(array, len);
// // downadj(array, 4, len);
// // printArray(array, len);
// downadj(array, 3, len);
buildHeap(array, len);
cout <<"\nbuild heap: ";
printArray(array, len);
// get the maxMember one by one
cout <<"\n\nheap sort print: ";
int tem;
while(len){
tem = heapFirst(array, len);
cout <<tem <<" ";
len--;
}
cout <<endl;
return 0;
}
int heapFirst(int *array, int len)
{
if(len == 0) return -1;
if(len == 1) return array[0];
int res = array[0]; // return the heap header
array[0] = array[len-1]; // swap the first with the last
array[len-1] = res;
len--;
downadj(array, 0, len); // down the
return res;
}
结果如下:
总结:所以堆排序就是几个操作,建堆,堆顶数据的弹出(堆删除),堆的维护。这几个操作会写了,堆也就会了。然后建堆过程简单的总结就是一个非叶子节点的下沉过程,堆删除就是堆顶和末元素交换,然后堆顶下沉的过程。