算堆排序

Posted 番茄技术小栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算堆排序相关的知识,希望对你有一定的参考价值。

主要借助上一篇博文实现的堆, 进行堆排序,然后考虑堆排序可以优化的点。

简单的堆排序

定义(wiki)

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

简单思路

有了前一篇博客实现的堆,自然而然的想到,将给出的数组先形成一个大根堆,然后逐一的取出,后逆序的返回即可。

代码实现

 
   
   
 
  1. <?php

  2. // require('../Library/SortTestHelper.php');

  3. require('../SortingAdvance/QuickSort.php');

  4. /**

  5. * 最大堆

  6. */

  7. class MaxHeap{

  8.    private $data;

  9.    private $count;

  10.    public function __construct(){

  11.        $this->data = array();

  12.        $this->count = 0;

  13.    }

  14.    // public function __construct($arr){

  15.    // }

  16.    public function insert($item){

  17.        //从1开始

  18.        $this->data[$this->count + 1] = $item;

  19.        $this->_shiftUp($this->count+1);

  20.        $this->count++;

  21.    }

  22.    public function  extractMax(){

  23.        $ret = $this->data[1];

  24.        swap( $this->data, 1 , $this->count);

  25.        $this->count--;

  26.        $this->_shiftDown(1);

  27.        return $ret;

  28.    }

  29.    public function getMax(){

  30.        return $this->data[1];

  31.    }

  32.    public function isEmpty(){

  33.        return $this->count == 0;

  34.    }

  35.    public function getData(){

  36.        return $this->data;

  37.    }

  38.    /**

  39.     * [_shiftUp 新加入到堆中的元素直接放在数组后面,再与父元素比较后交换位置,直到根节点]

  40.     * @param  [type] $k [description]

  41.     * @return [type]    [description]

  42.     */

  43.    private function _shiftUp($k){

  44.        //如果叶子节点的值比父元素大交换位置,并更新k的值

  45.        while( $k > 1 && $this->data[(int)($k/2)] < $this->data[$k] ){

  46.            // swap( $this->data[(int)($k/2)], $this->data[$k] );

  47.            swap( $this->data, (int)($k/2) , $k);

  48.            $k = (int)($k/2);

  49.        }

  50.    }

  51.    /**

  52.     * [_shiftDown 元素出堆的时候,需要维护此时的堆依然是一个大根堆, 此时将数组元素的最后一个值与第一个值交换,后从上往下维护堆的性质]

  53.     * @param  [type] $k [description]

  54.     * @return [type]    [description]

  55.     */

  56.    private function _shiftDown($k){

  57.        //2k代表该节点的左子节点

  58.        while( 2*$k <= $this->count ){

  59.            $j = 2*$k;

  60.            //判断右节点是否存在,并且右节点大于左节点

  61.            if( $j+1 <= $this->count && $this->data[$j+1] > $this->data[$j] ) $j ++;

  62.            if( $this->data[$k] >= $this->data[$j] ) break;

  63.            // swap( $this->data[$k] , $this->data[$j] );

  64.            swap( $this->data, $k , $j );

  65.            $k = $j;

  66.        }

  67.    }

  68. }

  69. //简单的堆排序

  70. function headSort1(&$arr, $n){

  71.    $head_obj = new MaxHeap();

  72.    for ($i=0; $i < $n; $i++) {

  73.        $head_obj->insert($arr[$i]);

  74.    }

  75.    //逆序输出

  76.    for ($i=$n-1; $i >= 0; $i--) {

  77.        $arr[$i] = $head_obj -> extractMax();

  78.    }

  79. }

  80. $n = 10000;

  81. $arr = generateRandomArray($n, 0, $n);

  82. $copy_arr1 = $arr;

  83. $copy_arr2 = $arr;

  84. $copy_arr3 = $arr;

  85. $copy_arr4 = $arr;

  86. // $arr = generateNearlyOrderedArray($n, 100);

  87. testSort("headSort1", "headSort1", $arr, $n);

  88. testSort("mergeSort", "mergeSort", $copy_arr1, $n);

  89. testSort("quickSort", "quickSort", $copy_arr2, $n);

  90. testSort("quickSort2", "quickSort2", $copy_arr3, $n);

  91. testSort("quickSort3", "quickSort3", $copy_arr4, $n);

  92. ?>

结果

 
   
   
 
  1. headSort1运行的时间为:0.70801091194153s

  2. mergeSort运行的时间为:0.94017505645752s

  3. quickSort运行的时间为:0.30204510688782s

  4. quickSort2运行的时间为:0.19032001495361s

  5. quickSort3运行的时间为:0.36022400856018s

结果分析

 
   
   
 
  1. for ($i=0; $i < $n; $i++) {

  2.        $head_obj->insert($arr[$i]);

  3.    }

  4. //逆序输出

  5. for ($i=$n-1; $i >= 0; $i--) {

  6.    $arr[$i] = $head_obj -> extractMax();

  7. }

这段代码进行了两次O(NlogN)时间复杂度的运算,并且是将已有的数组去生成一个堆,然后借助这个堆重新赋值给数组,其实可以直接用数组去形成一个堆,然后我们就可以进行一次O(NlogN)复杂度的运算。我们只需要重写一个构造函数,通过一个数组去构造它!

堆排序的优化

代码实现

 
   
   
 
  1. <?php

  2. require('../SortingAdvance/QuickSort.php');

  3. /**

  4. * 根据已知数组最大堆

  5. */

  6. class HeapSort{

  7.    private $data;

  8.    private $count;

  9.    public function __construct($arr, $n){

  10.        $this->data = array();

  11.        $this->count = $n;

  12.        for ($i=0; $i < $n; $i++) {

  13.            //从1开始

  14.            $this->data[$i+1] = $arr[$i];

  15.        }

  16.        //叶子节点已经是一颗大根堆了,从最后一个非叶子节点进行_shiftDown,知道根节点

  17.        for ($i= (int)($n/2); $i >= 1 ; $i--) {

  18.            $this->_shiftDown($i);

  19.        }

  20.    }

  21.    // public function __construct($arr){

  22.    // }

  23.    public function insert($item){

  24.        //从1开始

  25.        $this->data[$this->count + 1] = $item;

  26.        $this->_shiftUp($this->count+1);

  27.        $this->count++;

  28.    }

  29.    public function  extractMax(){

  30.        $ret = $this->data[1];

  31.        swap( $this->data, 1 , $this->count);

  32.        $this->count--;

  33.        $this->_shiftDown(1);

  34.        return $ret;

  35.    }

  36.    public function getMax(){

  37.        return $this->data[1];

  38.    }

  39.    public function isEmpty(){

  40.        return $this->count == 0;

  41.    }

  42.    public function getData(){

  43.        return $this->data;

  44.    }

  45.    /**

  46.     * [_shiftUp 新加入到堆中的元素直接放在数组后面,再与父元素比较后交换位置,直到根节点]

  47.     * @param  [type] $k [description]

  48.     * @return [type]    [description]

  49.     */

  50.    private function _shiftUp($k){

  51.        //如果叶子节点的值比父元素大交换位置,并更新k的值

  52.        while( $k > 1 && $this->data[(int)($k/2)] < $this->data[$k] ){

  53.            // swap( $this->data[(int)($k/2)], $this->data[$k] );

  54.            swap( $this->data, (int)($k/2) , $k);

  55.            $k = (int)($k/2);

  56.        }

  57.    }

  58.    /**

  59.     * [_shiftDown 元素出堆的时候,需要维护此时的堆依然是一个大根堆, 此时将数组元素的最后一个值与第一个值交换,后从上往下维护堆的性质]

  60.     * @param  [type] $k [description]

  61.     * @return [type]    [description]

  62.     */

  63.    private function _shiftDown($k){

  64.        //2k代表该节点的左子节点

  65.        while( 2*$k <= $this->count ){

  66.            $j = 2*$k;

  67.            //判断右节点是否存在,并且右节点大于左节点

  68.            if( $j+1 <= $this->count && $this->data[$j+1] > $this->data[$j] ) $j ++;

  69.            if( $this->data[$k] >= $this->data[$j] ) break;

  70.            // swap( $this->data[$k] , $this->data[$j] );

  71.            swap( $this->data, $k , $j );

  72.            $k = $j;

  73.        }

  74.    }

  75. }

  76. function headSort2(&$arr, $n){

  77.    $head_obj = new HeapSort($arr, $n);

  78.    for ($i=$n-1; $i >= 0; $i--) {

  79.        $arr[$i] = $head_obj -> extractMax();

  80.    }

  81. }

  82. $n = 10000;

  83. $arr = generateRandomArray($n, 0, $n);

  84. $copy_arr1 = $arr;

  85. $copy_arr2 = $arr;

  86. $copy_arr3 = $arr;

  87. $copy_arr4 = $arr;

  88. testSort("headSort2", "headSort2", $arr, $n);

  89. testSort("mergeSort", "mergeSort", $copy_arr1, $n);

  90. testSort("quickSort", "quickSort", $copy_arr2, $n);

  91. testSort("quickSort2", "quickSort2", $copy_arr3, $n);

  92. testSort("quickSort3", "quickSort3", $copy_arr4, $n);

  93. ?>

结果

 
   
   
 
  1. quickSort2运行的时间为:0.12423086166382s

  2. quickSort3运行的时间为:0.051468849182129s

  3. headSort2运行的时间为:0.033907890319824s

  4. mergeSort运行的时间为:0.020761013031006s

  5. quickSort运行的时间为:0.016165018081665s

  6. quickSort2运行的时间为:0.01316499710083s

  7. quickSort3运行的时间为:0.026669025421143s

heapify的算法复杂度(重要)

  • 将n个元素逐个插入到一个空堆中,算法复杂度是O(NlogN)

  • heapify(shiftup和shifton)(将一个数组变成堆的过程)的过程, 算法复杂度为O(N)


以上是关于算堆排序的主要内容,如果未能解决你的问题,请参考以下文章

初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段

以下代码片段的时间复杂度是多少?

markdown 数组排序片段

Java排序算法 - 堆排序的代码

Realm和RecyclerView项目排序和自动ViewPager片段通信

LeetCode810. 黑板异或游戏/455. 分发饼干/剑指Offer 53 - I. 在排序数组中查找数字 I/53 - II. 0~n-1中缺失的数字/54. 二叉搜索树的第k大节点(代码片段