您的位置:首页 > 其它

排序算法——堆排序

2017-04-07 14:50 357 查看
做为一个非专业的娱乐型选手,虽然工作了这么多年,但是和专业型选手的差距还是不能忽视,要想有进步,就必须要加倍努力。注重内在的修养。相信广大非专业选手和我一样,虽然现在也偶尔写写博客,但是以前,我觉得写博客不是我这种靠自学的人该写的。其实写博客不是为了给别人看,而是为了自己更好的理解,更好的熟悉。

真的开始

什么是堆

图解建堆

代码实现

真的开始

也不知道为什么会有这么大一段感慨,可能比较念旧吧。网上关于描述堆排序的Blog,相信多如浩瀚星辰,正如上述所说,再写这个算法,仅仅是为了自己更好的熟悉。也是因为自己太闲,想找点事做吧。

什么是堆

堆其实是一种自下向上的结构,我们可以用著名的汉诺依塔来做想像,这样一堆数据,自下向上堆积,就是堆。我在这里指的一般是二叉堆,即用一棵二叉树来描述的堆。这样说来,二叉堆,其实是一棵完全二叉树。

堆的学术定义:堆是这样一种结构,堆顶的数据大于或小于堆底的数据,并且对于堆中的其他点,依然满足前述条件,如果最大的数据处于堆顶,那么该堆则称为大顶堆,如果最小的数据处于堆顶,则相应的称为小顶堆。

那么堆排序就很显而易见了,就是通过建立大顶堆或小顶堆来给数据进行排序的一种排序算法。

图解建堆

在C++的stdlib中,有一个库函数,就是堆排序,原型如下:

int heapsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *));


base:表示数据的起点

nel:表示每个元素的大小

width:表示每个元素的

compar:表示一个元素比较方法,用以确定顺序

比如有一个int数组,那么heapsort的使用方法如下:

int elems[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
heapsort(elems, sizeof(int), sizeof(elems) / sizeof(int), compare);


其中compare就是一个 int ()(const void, const void*)类型的函数指针

要理解堆排序算法,最好是用一个实例来演示,在此,我用上述数组来进行一次排序算法的模拟。

按顺序建立一个初始堆,如下所示:



根据堆的特性可以知道,叶子结点的无论如何都是满足特性的,所以我们从后往前,开始处理。那么第一个处理的点应该是16,该点满足堆的特性,然后处理第二点2,因为14>8>12, 所以14应处理堆顶,交换2和14的位置,如下图所示:



接下来是处理3这一点,同理10>9>3, 所以交换10和3的位置,如下所示:



然后是处理1, 同理16>14>1,所以应该交换16和1的位置,但是交换之后,下面一个堆又违反了规则,所以要继续处理该堆,最终1的位置如下:



最后处理4,处理结束之后,第一次的堆处理就已经完成了,最终处理结束的结果如下:



经过处理之后,我们可以看到,对于大顶堆来说,其中最大的值一定是位于堆顶,我们拿出顶端元素16,然后重新从数组中建立一个初始堆,重新调整数据,直至调整结束,全部数据被取出为止。

代码实现

可以看到堆的调整其实也是一个递归的过程,我这里用两个函数来实现,核心代码如下:

typedef int (*CmpFunc)(const void*, const void*);

template<CmpFunc F, typename T>
void adjust(T* elems, size_t nwidth, int pos)
{
int left = pos * 2 + 1;
int right = (pos + 1) * 2;

if(left >= nwidth && right >= nwidth)
return ;

int largest = pos;
if(left < nwidth && F(elems + left, elems + pos) > 0)
largest = left;

if(right < nwidth && F(elems + right, elems + largest) > 0)
largest = right;

if(largest != pos)
{
T tmp = elems[largest];
elems[largest] = elems[pos];
elems[pos] = tmp;

adjust<F>(elems, nwidth, largest);
}

return ;
}

template<CmpFunc F, typename T>
void heapsort(T* elems, size_t nelem, size_t nwidth)
{
if(nwidth == 0)
return ;

for(int i = int(nwidth) - 1; i >= 0; --i)
adjust<F>(elems, nwidth, i);

heapsort<F>(++elems, nelem, --nwidth);
return ;
}


首先从heapsort函数开始,这个函数用于建立堆,至于是大顶堆还是小顶堆,则由CmpFunc决定,将堆中无任何元素做为出口,然后从最后一个元素开始调整,直至结束。完成调整之后,堆顶必是最大或最小的值,取出堆顶元素,重新建堆。

先从heapsort函数开始,这个函数用于建立堆,至于是大顶堆还是小顶堆,则由CmpFunc决定,共有以下几步:

1. 定义出口,若堆中元素为0, 则表示排序结束。

2. 从最后一个元素开始调整,一次调整结束之后,堆顶元素为最大值。

3. 取出堆顶元素,剩下的元素重新建堆。

可以在建堆函数中递归之前加入输出代码,来查看整个建堆的过程,如下:

for(int i = 0; i != nwidth; ++i)
std::cout<< elems[i] << ",";
std::cout<<std::endl;


然后是adjust函数,该函数用于调整堆,以使其符合规则,共有以下几步:

1. 取当前点的左子结点和右子结点

2. 左子结点的值和当前点值比较,取较大者(largest)

3. 右子结点的值和较大者比较,取较大者(largest)

4. 如果当前点在堆中最大,那么无需元素交换,否则,交换元素

5. 如果有元素交换,堆可能会被破坏,重新调整该点,使其符合规则。

这样,一个堆排序的函数就已经实现了,下面是一些测试数据:

第一个测试:

<
c43c
pre class="prettyprint">
int compare(const void* elem1, const void* elem2)
{
return *((int*)elem1) - *((int*)elem2);
}

int elems[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};

heapsort<compare>(elems, sizeof(int), sizeof(elems) / sizeof(int));

for(auto elem : elems)
std::cout<< elem << ",";
std::cout<<std::endl;


结果如下所示:



第二个测试:

char alpha[] = {'z', 'x','c','v','b','n','m','a','s','d','f','g','h','j','k','l','q','w','e','r','t','y','u','i','o','p'};

heapsort<alcompare>(alpha, sizeof(char), sizeof(alpha) / sizeof(char));

for(auto elem : alpha)
std::cout<< elem << ",";
std::cout<<std::endl;


输出结果如下:



堆排序的平均时间复杂度是O(nlgn),虽然有比他更好的快速排序,但不可否认堆排序是一个优秀的排序算法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  排序算法 堆排序