菜鸟写的堆和堆排序,都是根据个人理解,欢迎指出不当的地方。。。。。。大神就飘过吧。。。。。。
从poj上刷堆排序和优先队列,只知道堆排序貌似和优先队列有关系,不知道堆排序(似乎排序一般用快排)在其他能干什么用,也就是不知道堆的题型是什么,不管怎样,先把它搞懂再说吧,自己能接触的也就是堆排序了,先把堆排序写出来。
堆:
说到堆,应该先说二叉树,堆是建立在二叉树树的基础上的,是一个近似完全二叉树的数据结构。如图:(最大堆)
图 1
图 2
堆分最大堆(大根堆)和最小堆(小根堆),最大堆是任何一个非叶子节点都比其孩子大,最小堆就是任何一个非叶子节点都比他的孩子小。图1和图2对应着看,不难发现,一个节点的左孩子(如果它有的话)下标是这个节点的下标的2倍,右孩子(如果它有的话)是它的2倍再+1,而它的父节点(如果它有的话)就是它的下标/2,记住这个对应关系。
堆操作:
1 插入:
插入一个元素就是把插入的元素放到最后,然后再把目前的数组调整成一个堆,相当如重新建立堆,调整方法在下面。
2 删除:
删除就是把某一个元素删除,然后把最后一个元素放到删除的元素的位置,然后重新调整数组使之成为堆,方法在下面。
堆排序:
堆排序和其他排序效果是一样的,时间复杂度为O(nlogn),空间复杂度为O(n+k),n是输入长度,k是一个常数,这个常数很小,几乎可以忽略。堆排序的大致想法是:用第一个元素和最后一个元素交换,然后堆的元素个数–,然后把剩下的元素再次调整成一个堆,不断重复这个过程,直到元素个数为0,输出原来的数组,就是堆排序的结果。
堆排序之前首先要建立一个堆:(这里以对一串int数排序为例子,建立最大堆)
1 从控制台读入一串数,存入数组,数组下标从1开始
2 从len(数的个数)/ 2开始(因为这个下标正好是非叶子节点的最大的一个下标),判断它的左右孩子是否比它大,如果不比它大,判断下一个节点,如果比它大,它和比最大的那个孩子交换,然后判断交换后的它的孩子那棵子树是否构成最大堆,用先前的方法判断,知道不再交换,再判断下一个节点。一直判断到1,也就是根节点。
例如,用16,4,10,14,7,9,3,2,8,1这一串数作为例子,建立最大堆,然后从小到大排序:
初始化(输入完后)是:
图 3
调整:从数的个数/2的下标开始,本图从下标为5的地方开始,因为7比它的左孩子大,所以,这里满足最大堆的性质,然后判断下标为4的子树,发现也符合最大堆性质,那么,继续判断,下一个该判断的就是下标为3的节点,发现依然符合,那就判断下标为2的节点,现在发现不符合了,根据上面的介绍,4应该和14交换,变成下面这个图:
图 4
这样交换完之后,下表为4的节点又不满足最大堆的性质了,根据规则,4应该和8交换,交换完成后如图:
图 5
这样,都满足最大堆性质了,那么,调整完成,继续我们的检查,下标为2的子树检查完了,那么就该检查下标为1的子树了,发现符合最大堆,建堆完成。随着调整,相应的数组也会变化。
排序:
排序很简单,就是让第一个元素和最后一个元素交换,然后调整整个数组再次成为一个最大堆,当然,每交换一次,数组长度就要-1,因为第一个元素,永远都是最大的。然后再交换,然后再调整,直到数组长度减为0,数组中存储的就是从小到大的排序后的序列。
图不给了,直接上代码:
#include <stdio.h>
#include <string.h>
int s[1000010];
void adjustheap(int len,int i)
{
int lt = i << 1;//设置当前节点的左孩子
int rt = i << 1 | 1;//设置当前节点的右孩子
int mx = i;//当前节点,当前节点的左右孩子中最大值的下标,默认为当前节点的下标
while(lt <= len || rt <= len)//当左孩子或右孩子在数组长度范围内
{
if(lt <= len && s[mx] < s[lt])
mx = lt;
if(rt <= len && s[mx] < s[rt])
mx = rt;//这两个if是在当前节点和当前节点的左右孩子中找最大值,记录下标
if(mx != i)//最大值默认为当前节点,在上面两个if判断后mx变化了,说明当前节点的值小于它的某一个孩子的值,应该调整
{
int tmp = s[mx];
s[mx] = s[i];
s[i] = tmp;//当前节点的值和当前节点孩子中的最大值交换
lt = mx << 1;
rt = mx << 1 | 1;
i = mx;//交换完成,判断以交换的那个孩子为根的子树是否为最大堆,所以,左右子树的下标都要改成交换后的最大值的左右孩子的下标,默认的最大值也要改变
}
else
break;//如果未发生交换,说明不用调整,就跳出
}
}
void build(int len)
{
for(int i = len >> 1;i > 0;i--)//从下表最大且不为叶子节点的数的下标开始判断,调整,直到判断到根节点
adjustheap(len,i);//判断和调整
}
void heapsort(int len)
{
while(len)
{
int tmp = s[len];
s[len] = s[1];
s[1] = tmp;//第一个值和最后一个值交换
len--;//长度-1
adjustheap(len,1);//第一个节点(根节点)值改变,调整当前堆
}
}
int main()
{
int n;
scanf("%d",&n);//设定输入多少个数
for(int i = 1;i <= n;i++)
scanf("%d",&s[i]);//输入n个数,下表从1开始接收
build(n);//建立堆,参数为输入数的个数
heapsort(n);//堆排序
for(int i = 1;i <= n;i++)
{
printf("%d",s[i]);
if(i <= n - 1)
printf(" ");
}
printf("\n");
return 0;
}
这就是堆排序了,关键是理解如何调整。
C++ STL中有函数可以直接调用:
#include <stdio.h>
#include <algorithm>
using namespace std;
int s[1000010];
int main()
{
int n;
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
scanf("%d",&s[i]);
}
make_heap(s,s + n);//没有参数,默认为最大堆
sort_heap(s,s + n);
for(int i = 0;i < n;i++)
{
printf("%d",s[i]);
if(i < n - 1)
printf(" ");
}
printf("\n");
return 0;
}