zoukankan      html  css  js  c++  java
  • 排序算法的c++实现——堆排序

    我们利用最大堆可以实现数组从小到大的原址排序,利用最小堆的可以实现对数组从大到小的原址排序。

    1  二叉堆的简单介绍:

        最大堆与最小堆可以当作通过数组来实现的一个完全二叉树,除了最底层之外其它层都是满的,并且最底层也是从左到右填充的。在最大堆中,父结点的值大于或等于子结点的值;在最小堆中,父结点的值小于或等于子结点的值。

        当堆的元素在数组中下标从1开始时,很容易计算出父结点/左子结点/右子结点的下标:当父结点的下标为 i 时,左孩子下标为2i, 右孩子的下标为2i+1;当孩子结点为j时,父结点的下标为 j/2。

        但是呢,数组的下标都是从0开始的,所以呢,关于父结点与子结点的关系要稍微绕一下:当父结点的下标为 i 时,左孩子下标为2i+1, 右孩子的下标为2(i+1);当孩子结点为j时,父结点的下标为 (j-1)/2。

    2 使用最大堆或最小堆实现排序

    以使用最大堆进行从小到大的排序为例,假设最大堆使用长度为N的数组表示,数组下标为0(对应堆根结点)的值最大。因此呢,我们可以把数组下标为0的值与数组下标为N-1的值进行交换,并把堆的大小由N减小为N-1并维护最大堆的性质;接下来不断重复以上过程,直到堆的大小变为1为止。

    说明几点:

    1 堆排序为为原址排序,它不需要额外的内存空间;

    2 通常使用最大堆实现从小到大的排序,使用最小堆来实现从大到小的排序;

    3 堆排序不是稳定的排序算法;

    4 堆排序的时间复杂度为O(NlogN);

    代码如下:

      1 /***********************************************************************
      2 *   Copyright (C) 2019  Yinheyi. <chinayinheyi@163.com>
      3 *   
      4 * This program is free software; you can redistribute it and/or modify it under the terms
      5 * of the GNU General Public License as published by the Free Software Foundation; either 
      6 * version 2 of the License, or (at your option) any later version.
      7 
      8 *   Brief:    
      9 *   Author: yinheyi
     10 *   Email: chinayinheyi@163.com
     11 *   Version: 1.0
     12 *   Created Time: 2019年05月07日 星期二 21时41分02秒
     13 *   Modifed Time: 2019年05月09日 星期四 22时12分52秒
     14 *   Blog: http://www.cnblogs.com/yinheyi
     15 *   Github: https://github.com/yinheyi
     16 *   
     17 ***********************************************************************/
     18 
     19 
     20 // 堆使用一个数组表示, 它可以当作一个完全二叉树,除了最底层之外,其它层都是满的,
     21 // 并且最底层也是从左到右填充的。
     22 //
     23 // 当堆的下标(数组的下标)从1开始时比较好计算。因为:
     24 //      1. 当父结点为i时, 左孩子为2i,  右孩子为2i+1;
     25 //      2. 当孩子结点的下标为j时,父结点的下标为j/2 (根结点为父结点);
     26 //      3. 一个结点的下标为k时, 则它所有的深度为 floor(logK);
     27 //      4. 当一个堆共n个元素时,它的高度为floor(logN).
     28 //
     29 //                    1
     30 //                /       
     31 //             2             3
     32 //           /            /    
     33 //         4      5       6       7
     34 //        /     /      /     /   
     35 //      8    9  10  11  12   13 14   15
     36 //
     37 // 
     38 // 但是呢,数组的下标都是从0开始的,所以呢,我们下代码时,还是需要从0开始,而此时:
     39 //     1. 当父结点的下标为i时,左孩子为2i+1, 右孩子为2*(i+1)
     40 //     2. 当孩子结点的下标为j时,父结点的下标为(j-1)/2.
     41 //
     42 //                    0
     43 //                /       
     44 //             1             2
     45 //           /            /    
     46 //         3      4       5       6
     47 //        /     /      /     /   
     48 //      7    8  9  10  11   12 13   14
     49 //
     50 //
     51 //
     52 /**************************     代码如下      ****************************/
     53 
     54 // 定义三个宏,分别用于求左孩子/右孩子/父结点的下标。
     55 #define LEFT(i) (((i) << 1) + 1)
     56 #define RIGHT(i) (((i) + 1) << 1)
     57 #define PARENT(i) (((i) - 1) >> 1)
     58 
     59 // 小于比较函数
     60 bool less(int lhs, int rhs)
     61 {
     62     return lhs < rhs;
     63 }
     64 
     65 // 大于比较函数
     66 bool greate(int lhs, int rhs)
     67 {
     68     return lhs > rhs;
     69 }
     70 typedef bool (*Comp)(int, int);
     71 
     72 // 交换两个元素的值
     73 static inline void swap(int& lhs, int & rhs)
     74 {
     75     int _nTemp = lhs;
     76     lhs = rhs;
     77     rhs = _nTemp;
     78 }
     79 
     80 // 假设一个节点的左子树与右子树都满足堆的性质,而该节点不满足最大堆或最小堆的性质,该
     81 // 函数实现调整节点位置,维护堆的性质。
     82 // 输入参数分别表示: 堆的数组/数组长度/节点i的下标/表示比较的二元谓词
     83 // 复杂度为O(logN).
     84 void Heapify(int array[], int nLength_, int nIndex_, Comp CompFunc)
     85 {
     86     if (array == nullptr || nIndex_ >= nLength_ || nIndex_ < 0 || CompFunc == nullptr)
     87         return;
     88 
     89     int _nLeft = LEFT(nIndex_);
     90     int _nRight = RIGHT(nIndex_);
     91 
     92     // 初始化最大值节点的下标;
     93     int _nLargest = nIndex_;
     94     if ( _nLeft < nLength_ && !CompFunc(array[_nLargest], array[_nLeft]))
     95     {
     96         _nLargest = _nLeft;
     97     }
     98     if (_nRight < nLength_ && !CompFunc(array[_nLargest], array[_nRight]))
     99     {
    100         _nLargest = _nRight;
    101     }
    102 
    103     /* 此时不需要维护堆的性质,直接返回   */
    104     if (_nLargest == nIndex_)
    105     {
    106         return;
    107     }
    108 
    109     swap(array[nIndex_], array[_nLargest]);
    110     Heapify(array, nLength_, _nLargest, CompFunc);
    111 }
    112 
    113 // 使用一个数组建立一个最小堆或最大堆。
    114 // 输入参数为:一维数组/数组的长度/表示比较的二元谓词,用于控制建最小堆还是最大堆
    115 // 复杂度为O(N).
    116 void BulidHeap(int array[], int nLength_, Comp CompFunc)
    117 {
    118     if (array == nullptr || nLength_ <= 1 || CompFunc == nullptr)
    119         return;
    120 
    121     // 从最后一个元素的父节点开始调用Heapify()函数来建堆。
    122     for (int i = PARENT(nLength_ - 1); i >=0; --i)
    123     {
    124         Heapify(array, nLength_, i, CompFunc);
    125     }
    126 }
    127 
    128 // 堆排序的函数
    129 // 说明:1. 通过建立[最大堆]可以实现[从小到大]的排序;
    130 //       2. 通过建立[最小堆]可以实现[从大到小]的排序
    131 //       3. 堆排序为原址排序,它不需要借助额外的空间.(归并排序不是原址排序)
    132 //       4. 堆排序的复杂度为O(NlogN).
    133 //
    134 // 堆排序的思想 (以从小到大排序说明):
    135 //       第一步:建立一个最大堆;
    136 //       第二步:把首元素与最后一个元素进行交换;
    137 //       第三步:把堆的大小减1,对新的堆进行维护维的性质;
    138 //       第四步:把首元素与倒数第二个元素进行交换;
    139 //       第五步:把堆的大小减1,对新的堆进行维护维的性质;
    140 //       .......
    141 //
    142 void HeapSort(int array[], int nLength_, Comp CompFunc)
    143 {
    144     if (array == nullptr || nLength_ <=1 || CompFunc == nullptr)
    145         return;
    146 
    147     BulidHeap(array, nLength_, CompFunc);
    148     for (int i = nLength_; i >= 2; /* 循环内 */)        // i表示当前堆的大小
    149     {
    150         swap(array[0], array[--i]);
    151         Heapify(array, i, 0, CompFunc);
    152     }
    153 }
    154 
    155 
    156 /************    测试     *****************/
    157 #include <iostream>
    158 
    159 // 打印数组函数
    160 void PrintArray(int array[], int nLength_)
    161 {
    162     if (nullptr == array || nLength_ <= 0)
    163         return;
    164 
    165     for (int i = 0; i < nLength_; ++i)
    166     {
    167         std::cout << array[i] << " ";
    168     }
    169 
    170     std::cout << std::endl;
    171 }
    172 
    173 // 主函数
    174 int main(int argc, char* argv[])
    175 {
    176     int array[10] = { 100, 1, 1, -1243, 0, 223, 443, 123, -12, -129};
    177 
    178     PrintArray(array, 10);
    179     HeapSort(array, 10, greate);
    180     PrintArray(array, 10);
    181 
    182     return 0;
    183 }
  • 相关阅读:
    mysql基础学习
    Linux退出状态码
    python psutil简单示例
    linux systemctl 常用用法简介
    (转)linux进程的地址空间,核心栈,用户栈,内核线程
    (转)NAT原理与NAT穿越
    (转)蜜果私塾:http协议学习系列——协议详解篇
    (转)Windows 7下安装配置PHP+Apache+Mysql环境教程
    (转)蜜果私塾:http协议学习和总结系列 ——协议详解篇
    (转)Linux Futex的设计与实现
  • 原文地址:https://www.cnblogs.com/yinheyi/p/10836167.html
Copyright © 2011-2022 走看看