数据结构——堆排序
排序方法很多,各有优势,我们常用的排序方法是属于内部排序的,对辅助空间的使用很少,而时间复杂度可能遇到了限制了,我所知道几种排序方法最快也没有突破 n l o g n nlogn nlogn 这个限制。对于平均时间复杂度来说,快速排序已经是这种算法的佼佼者了,但是它的最坏情况恐怕是不能接受的, n 2 n^2 n2 ;这里推荐一种稍微复杂的排序,叫做堆排序,它基于一种数据结构——堆。才有了很小的时间复杂度,同时又有很稳定的特性。
先介绍一下堆的定义:
一颗二叉树,任意非叶子节点的左右子节点都同时比它大,或者同时比它小。
表现在数组中是这样的:
一串n个元素的序列,
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an ,下标由1开始;
a
i
≤
a
2
i
a
i
≤
a
2
i
+
1
a_i\le{a_{2i}} \\ a_i{\le{a_{2i+1}}}
ai≤a2iai≤a2i+1
或
a
i
≥
a
2
i
a
i
≥
a
2
i
+
1
a_i\ge{a_{2i}} \\ a_i{\ge{a_{2i+1}}}
ai≥a2iai≥a2i+1
( i = 1 , 2 , . . . , n / 2. ) (i=1,2,...,n/2.) (i=1,2,...,n/2.)
堆的定义有很明显的作用,最小的或者最大的数字永远在堆顶,对吗?这样子每次能够从堆顶拿出一个数字,并且从堆里面删去这个数字,再拿,如此往复,组成一个序列不就是一个有序的数组吗?并且用时是 n n n 。但是维护一个堆删除了堆顶的数字依然有序这是需要代价的,你需要调整几乎一个堆,从上至下。让数字的位置又满足堆的定义。
重新调整堆的方法叫做“筛选”,是这样的:
假设一个标准的堆,拿出了堆顶的元素,要删除堆顶的元素;
拿出13 ,把编号最后一个节点,也就是97放到堆顶。
97破坏了以1节点下的堆,所以交换编号1节点和它的子节点,选择编号2,3节点中小的那一个交换,那就是27这个编号3节点,这样子编号1节点下的堆就维护好了,但是破坏了编号3节点下的堆。所以依法炮制,换编号3节点和它的子节点。直到叶子节点。
接下来再拿出堆顶元素27,并且重复以上方式维护最小堆:
每一次从堆顶拿出元素的同时要将堆的大小减少1。
建立堆
还有个问题没解决的,就是初始的时候建立一个堆。
具体的操作就是:
对一棵二叉树由下往上操作,从第一个非叶子节点开始(也就是拥有至少一个子节点),逐步进行之前所说的“筛选”操作,直至根节点。
例如下面这棵二叉树,想要变化为最大堆;
编号最小的非叶子节点是编号3,值为40的那个节点;
然后是编号2节点,但它不需要筛选,下一个是编号1节点筛选;
再最后是筛选根节点;
完成后变成了最大堆。
这个图片是借用一下别人的,所以数字不同于上面的了,但是是一个思路,在这只做演示使用。(画图有点累啊)!!
很详细了。按图索骥都成了,所以贴代码了。
#include <iostream>
#include<cstring>
#include<cstdio>
using namespace std;
/*
* 调整为最大堆,以a[s]为根节点
* e的存在是为了限制数组避免越界
* e取以a[s]为根的堆的最大编号叶子节点的编号
*/
void HeapAdjust(int a[],int s,int e)
{
int length = e-s;
int rc=a[s];
// 每次取当前节点的左子节点
for(int i=2*s;i<=e;i*=2)
{
// 如果存在右节点,选择左右节点中大的那个
if(i<e &&a[i]<a[i+1])
i++;
// 子节点都比根节点小,退出循环
if(a[i]<rc)
break;
a[s] = a[i];
s=i;
}
// 交换根节点和最后一个不小于根节点的节点
a[s] = rc;
}
/*
* 利用最大堆,顺序排序
* 数组必须由下标1开始储存
*/
void HeapSort(int a[],int length)
{
// 先建立最大堆,length/2就是编号最大的非叶子节点下标
for(int i=length/2;i>0;i--)
{
HeapAdjust(a,i,length);
}
int t;
// 由最后一个节点开始,从堆中拿出元素放到堆尾
for(int i=length;i>1;i--)
{
t=a[i];
a[i]=a[1];
a[1]=t;
// i-1是因为堆的大小变成了i-1了
HeapAdjust(a,1,i-1);
}
}
int main()
{
int a[10]={0,49,38,65,97,76,13,27,49};
HeapSort(a,8);
for(int i=1;i<=8;i++)
printf("%d ",a[i]);
return 0;
}
更新
2021/1/4更新了一次,更加详细了。