vlambda博客
学习文章列表

Perl算法实践---堆排序

今天我们介绍另一种排序方法--堆排序。堆排序具有空间原址性, 即任何时候, 我们只需要数个额外的元素空间来存储临时数据,就可以达到排序的结果。

堆排序使用的数据结构是二叉堆, 也可以理解成近似的完全二叉树, 在堆排序中, 我们往往使用的是最大堆, 也就是在二叉堆中, 每个结点都小于等于其父结点。

当我们将数据构造成最大堆时, 我们的数据就已经排序完成了。


在构造最大堆时, 最重要也是最核心的步骤就是维护堆, 在我们新添加一个数据进来时, 我们需要从根结点, 依次来对该数据进行比较, 如果该数据小于该结点, 则下沉继续比较,如果大于该结点, 则交换结点, 并重新维护。 通过维护堆这一过程, 来确保新的堆符合最大堆的特性。

整个堆排序过程, 其实就是逐步将数据加入堆中, 通过维护堆的过程, 来一步步的将数据插入堆中, 直至所有的数据都被存入最大堆中。


以下是具体的实现方法:


对于最大堆的数据结点下标, 为了方便, 我们这里下标统一从1开始计算, 实际应用中可以根据0开始, 但要小心处理:

当一个结点下标为i时, 他的父结点下标为 int(i/2), 他的左子结点下标为2i, 右子结点下标为2i+1。


#我们有如下数据:

my @nums = (0,44,1,2,27,5,19,4); #为了方便,我们从下标1开始计算, 因此第一位直接忽略

my $size = @nums - 1;


#首先我们实现维护堆的过程:

sub max_heapify{

my ($nums_ref, $index) = @_;

my $left  = 2 * $index;

my $right = 2 * $index + 1;

my $largest;

if ($left <= $size && $nums_ref->[$left] > $nums_ref->[$index]) {

$largest = $left;

}else{

$largest = $index;

}

if ($right <= $size && $nums_ref->[$right] > $nums_ref->[$largest] ){

$largest = $right;

}


if ($largest != $index ){

($nums_ref->[$index], $nums_ref->[$largest]) = ($nums_ref->[$largest], $nums_ref->[$index]);

max_heapify($nums_ref, $largest);

}

}


#构造最大堆:

sub build_max_heap{

my $nums_ref = shift;

my $size = @$nums_ref - 1;

for my $index(reverse (1...int($size / 2) )){

max_heapify($nums_ref, $index);

}

}


build_max_heap(\@nums);

#得到最大堆后, 我们只需要从根结点逐个取得每个元素, 即可得到排序完的内容, 

#需要注意的是, 每次我们取完一个结点时, 需要对最大堆来进行重新维护操作, 使剩余元素仍然满足最大堆的特性

for my $index(reverse (1...$size) ){

($nums[$index], $nums[1]) = ($nums[1], $nums[$index]);

$size--;

max_heapify(\@nums, 1);

}


print @nums;