堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。
(2)大根堆排序算法的基本操作:
①建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
②调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
③堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。堆排序过程的时间复杂度是O(nlgn)。因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(nlgn)


php代码实现如下:

<?php
class heap_sort
{
    private $arr;
    
    function __construct(&$arr)
    {
        if(!is_array($arr))
        {
            return FALSE;
        }
        $this->arr = $arr;
        $this->heapSort();
        $this->output();
    }
    
    private function buildMaxHeap()
    {
        $length = count($this->arr);
        for($i=floor($length/2)-1; $i>=0; $i--)
        {
            $this->heapAdjust($i, $length);
        }
    }
 
    private function swap(&$i, &$j)
    {
        $tmp = $i;
        $i = $j;
        $j = $tmp;
    }
 
    private function heapSort()
    {
        $length = count($this->arr);
        $this->buildMaxHeap();
        for($i=$length-1; $i>0; $i--)
        {
            $this->swap($this->arr[0], $this->arr[$i]);
            $this->heapAdjust(0, $i);
        }
        return $this->arr;
    }
 
    private function heapAdjust($i, $j)
    {
        $largest = $i;
        $left = 2*$i+1;
        $right = 2*$i+2;
         
        if($left<$j && $this->arr[$largest]<$this->arr[$left])
        {
            $largest = $left;
        }
         
        if($right<$j && $this->arr[$largest]<$this->arr[$right])
        {
            $largest=$right;
        }
         
        if($largest!=$i)
        {
            $this->swap($this->arr[$i], $this->arr[$largest]);
            $this->heapAdjust($largest, $j);
        }
    }

    private function output()
    {
        $arr = $this->arr;
        echo iconv('utf-8', 'gbk', "整数序列经选择排序后的结果如下:\n");
        $str = '';
        foreach($arr as $number)
        {
            $str .= $number.', ';
        }    
        echo rtrim($str, ', ')."\n\n";
    }
}

function read()
{
    $input = trim(fgets(STDIN));
    return $input;
}

function test()
{
    $str = '49, 38, 65, 97, 76, 13, 27, 49, 55, 04';
    $arr = explode(', ', $str);
    new heap_sort($arr);
}

function main()
{
    $flag = TRUE;
    while($flag)
    {
        echo iconv('utf-8', 'gbk', "请输入整数序列,以英文半角逗号和空格分隔,例如49, 38, 65(退出请输入exit或quit)\n");
        $str = read();
        if($str == 'exit' || $str == 'quit')
        {
            echo 'Bye';
            break;    
        }
        $arr = explode(', ', $str);
        $validity = TRUE;
        foreach($arr as $number)
        {
            if(!is_numeric($number))
            {
                echo iconv('utf-8', 'gbk', "数字序列输入有误,请重新输入\n");
                $validity = FALSE;
                break;
            }
        }
        if(!$validity)
        {
            continue;    
        }
        new heap_sort($arr);
    }
}

if(!empty($argv[1]) && $argv[1]=='test')
{
    test();    
}
else
{
    main();
}