堆
堆,是一种类似于二叉树的结构,或者说就是二叉树的一种。堆的每一个结点都有一个数值,并且有这样的性质:根节点比要比两个孩子节点都要大(至少也是相等),这样的堆叫做大顶堆,同样也有小顶堆的概念。
排序
首先声明,我们不需要创造一个二叉树来存储函数传递过来的数组信息,因为数组本身就可以构成一个二叉树,而且是完全二叉树。之前的关于数组存储二叉树的文章
然后,我们需要整理这个数组使其成为一个大顶堆。
(这里是以大顶堆为例,小顶堆同理)
如何用数组实现大顶堆
遍历二叉树的每一个结点,对于非叶子节点,将左孩子、右孩子(如果有的话)和根节点拿出来,选择最大的一个作为根节点。当遍历结束后,整个树也就是一个大顶堆了。
我们还知道,给出的数组下标就是层次遍历二叉树的结果,所以我们不需要什么先序中序的。
另外,也是最重要的一部分,那就是我们整理的过程一定是从下到上的,因为一旦有大的数据在很下面,开始就整理根节点是不能实现大顶堆的。
for i<-A.length/2 downto 1
对每一个结点进行交换操作。
那和排序用有什么关系呢?至少我们知道二叉搜索树还是左孩子小于根节点小于右孩子的,直接遍历就行,那么如何来用堆排序呢?
我们的大顶堆的根节点已经有了,而且是降序的第一位,所以我们只需要将这个结点拿出来,然后对剩下的树整理、取数……直到树为空。
有一个细节上的就是我看到的官方教程都是i从A.length到2,建立堆之后将A[1]和A[i]进行交换,然后重新构造最小堆,但是本人的想法是将最大的直接放在那,然后从第二个开始接着做成树,继续处理(貌似都差不多其实)
代码:
void exchange(int A[],int i, int length)
{
if(2*i+2 == length)
{
if(A[2*i+1]>A[i])
{
int temp=A[i];
A[i]=A[2*i+1];
A[2*i+1]=temp;
exchange(A,2*i+1,length);
}
}
else if(2*i+2 < length)
{
if(A[i]<A[2*i+2]||A[i]<A[2*i+1])
{
int temp_place=i;
int temp;
if(A[2*i+2]>A[2*i+1])
{
temp_place=2*i+2;
temp=A[i];
A[i]=A[2*i+2];
A[2*i+2]=temp;
}
else
{
temp_place=2*i+1;
temp=A[i];
A[i]=A[2*i+1];
A[2*i+1]=temp;
}
if(temp_place!=i)
exchange(A,temp_place,length);
}
}
}
void build(int A[],int length)//构建大顶堆
{
for(int i=length/2-1; i>=0; i--)
{
exchange(A,i,length);
}
}
void Heap(int a[],int length)
{
build(a,length);
for(int i=length-1; i>=0; i--)
{
int temp=a[0];
a[0]=a[i];
a[i]=temp;
length--;
exchange(a,0,length);
}
}
exchange函数中的参数是防止在寻找过程中发生了下标越界。
在这个函数中最懵的可能就是那个对交换后的位置继续调用交换函数了,示例:
在交换了4和9之后,很明显47不符合大顶堆的定义,所以我们还是需要交换的,这时应该交换的就是4(根节点)被交换到的位置。
在每一次取元素之后,只有堆顶是经过交换的,所以我们只需要处理一下顶端即可,不需要对整体进行构建。
堆排序的本质还是构建二叉树来处理,也属于基于比较、交换的方式排序。
首先,我们要知道exchange函数就是在把一棵二叉树从指定结点一直走到一个叶子,所以时间复杂度为O(logn),其中build函数调用了n/2次,接下来的循环又是n-1次,所以整体的时间复杂度还是O(logn),也就是比较类排序的极限了。
堆排序中第一步整理堆的代价是O(n)