飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 4930|回复: 1

《算法三部曲》 之 排序和查找算法(中篇)

[复制链接]

该用户从未签到

发表于 2011-3-26 18:43:02 | 显示全部楼层 |阅读模式
本帖最后由 Knuth学徒 于 2011-3-26 19:09 编辑

排序和查找算法目录:
      

     算法类的帖子曾经我写过不少, 由于进来专业课繁忙,  一直没时间更新, 我非常愧疚。   

     今天抽空讲一讲另一个比较高效的排序算法 —— 堆排序 .

     堆排序, 顾名思义 和堆有关,  这里的堆指的是 二叉堆 , 如果对于堆这个数据结构不是很熟悉的朋友可以参见我写的这篇帖子:  
     https://www.chinapyg.com/viewthr ... &extra=page%3D1

     于是, 我先说下堆排的原理:

     首先, 根据 堆的性质 ,堆顶元素一定是整个堆中最(大)小的元素 , 于是呢, 我们可以把堆顶元素放到数组的首位,  然后将堆顶元素从堆中去除, 记得在去除的时候堆会自动进行调整, 使它保持堆的性质.

     然后, 再将堆顶元素放到数组的第二位 , 再将这个堆顶元素从堆中去除,重新调整堆结构.

     ……

     这样进行 N 次 后,N个数据都有序的存放在 数组中了.
     也许有人会问, 为什么这样就是有序的呢?   

     我证明如下:  假设堆中初始有 N 个元素 ,分别是 (a1 , a2 , a3 , .... , aN)
     每次我们拿出 堆中的最小的元素, 即 Min{a1,a2,a3.....aN},将它放在数组的第i个位置
     那么,可以保证 最后数组从第i+1位到第N位 都比 第i位要大!

     因为 i 具有任意性, 所以最后线性表中的每个元素都比它后面的元素要大, 就相当于是从小到大有序了.(证毕 #)

     既然这种算法可以被证明是正确的, 我们就可以拿来使用.

     注: 这里堆的实现方法我并不采用 C++ 中的STL , 因为也许有朋友认为我是借助了库函数, 所以, 这里的堆实现我全部是手写的.

     首先在 Heap.h 头文件中加入如下的代码:

  1.      #ifndef Heap_H
  2.      #define Heap_H

  3.      class MinHeap{
  4.             private:
  5.                    int count;            
  6.                    int val [MAX];     
  7.                    void down(int position);     //最小堆的调整函数
  8.                    //void up(int position)        最大堆的调整函数
  9.             public:
  10.                   MinHeap();                       //构造函数
  11.                   void Insert(int data );      
  12.                   int  Top_Remove() ;      
  13.                   bool  is_empty();           
  14.                   int size();                       
  15.      };

  16.      #endif

  17.      
复制代码
以上都是堆结构的类声明, 这些都可以在我之前发的帖子里找到, 这里就做一下搬运了, 保持这篇文章的完整性。

     接着就是这个 堆结构的 实现部分:

     一下代码放在 Heap.cpp 文件中:

  1.     //这里的代码风格借鉴了 Nisy老师上次共享的 DebuggerVC代码中的风格,好的代码风格就应该模仿。

  2.      #include"Heap.h"

  3.      ///////////////////////////////////////////////////////////
  4.      //     函数名:MinHeap()
  5.      //     描述:构造函数,生成一个空堆
  6.      //     参数:None
  7.      //     前提:None
  8.      //     用法:MinHeap  对象名
  9.      ///////////////////////////////////////////////////////////
  10.      MinHeap :: MinHeap(){
  11.                count = 0;
  12.      }
  13.      ///////////////////////////////////////////////////////////
  14.      //     函数名:is_empty()
  15.      //     描述:判断堆是否为空
  16.      //     参数:None
  17.      //     前提:None
  18.      //     用法:对象名.is_empty()
  19.      ///////////////////////////////////////////////////////////
  20.      bool  MinHeap :: is_empty(){
  21.                  return (count == 0);
  22.      }
  23.      ///////////////////////////////////////////////////////////
  24.      //     函数名:size()
  25.      //     描述:返回堆的大小
  26.      //     参数:None
  27.      //     前提:None
  28.      //     用法:对象名.size()
  29.      ///////////////////////////////////////////////////////////
  30.     int  MinHeap :: size(){
  31.                    return count;
  32.     }
  33.      ///////////////////////////////////////////////////////////
  34.      //     函数名:Insert()
  35.      //     描述:往堆中插入一个元素,并重新调整堆结构
  36.      //     参数:Data 表示插入元素的键值
  37.      //     前提:None
  38.      //     用法:对象名.Insert( xx )
  39.      ///////////////////////////////////////////////////////////
  40.      void MinHeap :: Insert( int data)
  41.      {
  42.                     val [++ count ] = data;       //先将元素放入堆尾
  43.                     int  p = count ;                    //调整用指针
  44.                     while(p > 1) {                      //在到达根节点之前
  45.                               if( val[ p>>1 ] > val [p] ){     //如果p的父节点键值>p,进行交换
  46.                                          int tmp = val [p] ;
  47.                                          val [p] = val [ p>>1 ] ;
  48.                                          val[p>>1] = tmp;
  49.                                          p >> = 1;                //指针向上移
  50.                              }
  51.                              else break;                          //如果没有破坏堆结构直接跳出。
  52.                     }
  53.       }
  54.      ///////////////////////////////////////////////////////////
  55.      //     函数名:down(int position)
  56.      //     描述:将当前破坏了的堆 通过下降调整为堆
  57.      //     参数:position 表示需要调整的位置
  58.      //     前提:None
  59.      //     用法:None
  60.      ///////////////////////////////////////////////////////////   
  61.      void MinHeap :: down(int position)
  62.      {
  63.                while( (position << 1) <= size )   //如果位置还没下降到叶子节点
  64.                {
  65.                           int tmp = position << 1;
  66.                            if( ( position << 1 ) + 1 <= size && val[ (position<<1)+1] < val[tmp] )
  67.                                     //如果当前节点的右孩子小于当前节点的左孩子
  68.                                            tmp = ( possition<<1 )+ 1;
  69.                            if( val [tmp] < val[position] ) {
  70.                                     //如果当前节点的右孩子 小于当前节点
  71.                                     int temp = val [tmp] ;
  72.                                     val [tmp ] = val[position];
  73.                                     val [position ] = temp;
  74.                                     position = tmp;
  75.                          }
  76.                          else break;
  77.                }
  78.      }
  79.      ///////////////////////////////////////////////////////////
  80.      //     函数名:Top_Remove()
  81.      //     描述:返回堆顶元素,并删除之,再重新调整堆结构
  82.      //     参数:None
  83.      //     前提:None
  84.      //     用法:对象名.Top_Remove()
  85.      ///////////////////////////////////////////////////////////
  86.      int MinHeap :: Top_Remove(){
  87.                    int temp = val[1] ; //保存堆顶元素
  88.                    val [1] = val [ count];
  89.                    val [count ] = temp;
  90.                    count --;
  91.                    down(1);  调整整个堆
  92.                    return val[count + 1];
  93.     }
  94.    
复制代码
这样, 堆的结构都声明完了, 接着就是排序了.

    其实按照先前那个算法, 我们可以很容易的写出排序的方法:


  1. #include<cstdio>
  2. #include"Heap.h"

  3. int main()
  4. {
  5.       MinHeap heap;  // 声明heap作为MinHeap类
  6.       int n;
  7.       int data[1000];  //1000在这里作为示范的大小
  8.       printf(" Input the number of the Arrays ! \n");
  9.       scanf("%d",&n);
  10.       printf(" One by one Input the Arrays! \n");
  11.       for( int i = 0 ; i < n ;++ i )
  12.       {
  13.             scanf("%d", data + i );
  14.             heap.Insert( data[i] );
  15.       }

  16.       // 以下是堆排序的过程
  17.       for( int k = 0; k < n ; ++k )
  18.       {
  19.             data[k] = Top_Remove();
  20.            //将堆顶元素放在数组中, 并删除堆顶元素重新调整堆结构
  21.       }
  22.       
  23.       //此时 data 数组已经有序!
  24.   
  25.       // 将排序后的 data 数组输出
  26.       for(int j = 0 ; j < n ; ++ j , printf("%d ",data[j]));

  27.       printf("\n");
  28.       return 0;
  29. }
  30.    
复制代码
嗯,写到这里,家人催我吃晚饭了, 所以不能再写太多了, 把查找放在 (下篇)中吧, 接着就接个尾了.

    算法分析: (这一章个人觉得有点重要,当然如果不感兴趣就不用仔细看了)

    时间复杂度分析:

    令 T(N) 为排序 N 个数组所需要的时间.
    则:
    T(N) = N + N * log2(N) ~ O( N log2(N) )

    其速度和快排在同一个阶上的, 但是由于堆结构比较复杂, 所以在操作上会有比较大的常数 , 实际上比 快速排序要慢一点.
   
    但是由于 二叉堆 的平衡性 , 所以可以保证它最慢的情况 不会超过 O( Nlog2(N) ) , 比快排要快!

    ///////////////////////////////////////////////////////////
    //   堆排序就讲到这里了, 谢谢大家.
    //////////////////////////////////////////////////////////
PYG19周年生日快乐!
  • TA的每日心情
    开心
    2016-6-1 22:50
  • 签到天数: 1 天

    [LV.1]初来乍到

    发表于 2014-12-22 20:32:16 | 显示全部楼层
    好好学习天天向上
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表