堆排序算法及其c语言泛型编程

来源:互联网 发布:java中格式化日期 编辑:程序博客网 时间:2024/05/07 18:38
一:什么是堆?
1.堆: n个元素的序列{k1,k2,k3,.....kn}当且仅当满足以下关系是,称为堆. {ki <= k2i 且 ki <= k2i+1} 或
{k2i <= ki 且 k2i+1 <= ki} (i = 1,2,...[n/2] ).这也是堆的一个性质.

2.堆结构是一种数组对象堆,它可以被视为一棵完全二叉树如下图,  书中每个节点与数组中存放该节点中值的那个元素
对应,除了最后一层,其余的每个节点都是满的.因为堆可看成是一棵完全二叉树,那它就满足一些树的性质,
对于有n个结点的完全二叉树,对任一结点i 有:  
  1)如果 i = 1,则结点i是二叉树的根,无双亲;
    2)如果i > 1,则其双亲Parent(i)是[ i / 2];
    3)如果2i > n,则结点i 无左孩子(结点i为叶子结点);否则,其左孩子Lift_child(i) 是结点2i.
    4)如果2i + 1 > n,则结点i无右孩子;否则,其右孩子right_child(i)是结点2i + 1.
    5)其非终端结点是第[ n / 2]个元素,(非终端结点是指:完全二叉树中的最后一个带有叶子的结点).

            

二:堆的分类
堆包含: 1.大根堆. 2.小根堆.       
        1. 大根堆必须满足: {ki <= k2i , ki <= k2i+1}  (i = 1,2,...[n/2] ), 大根堆排序后元素是由小到大的顺序.
        2.小根堆必须满足:  {k2i <= ki , k2i+1 <= ki} (i = 1,2,...[n/2] ).  小根堆排序后元素是由大到小的顺序.
【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),
故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示:



在下面所举的例子或代码中默认的是数组中元素是从下标1开始存储的.

三:如何实现堆排序?
1.堆排序需要解决的两个问题就是:(1)如何由一个无序序列建成一个堆? (2)如何在输出堆顶元素之后,
   调整剩余元素成为一个新的堆?
      
2. 让一个无序序列建成一个堆的最重要的步骤就是堆调整(牢记堆性质).
    堆调整的思想:
       1).把待调整的记录当成根,然后比较此根和其左,右孩子这三个数的大小,如果左,右孩子中还中有一个最大的,
            则把此最大值与根值交换(这是大根堆调整法,小根堆调整只需找到最小的值并与根交换就行),
       2).接着从最大值下标开始,并又以此结点为根重复1)中的步骤直到最大值下标循环的叶子结点.
        3)如果1)中比较后得到的最大值(/最小值)仍为根,则表示以此根结点的子序列已满足堆性质,就已经是堆了,

下面就堆调整的伪代码:
    array[]可知是我们将要调整的序列数组,index 则表示的是将要调整的记录的下标.wait_adjust_length是待调整堆的长度.  

点击(此处)折叠或打开

  1. heap_adjust(NumType array[],int index ,int wait_adust_length)

  2.     lift_index = 2 * index ;//记录待调整记录的左孩子的下标
  3.     right_index = 2 * index + 1;//记录待调整记录的右孩子的下标
  4.    
  5.   if lift_index <</SPAN>= wait_adjust_lenghtand array[lift_index] >array[right_index]
  6.           then max_num_index = lift_index;
  7.           else max_num_index = right_index;
  8.   if right_index <</SPAN>= wait_adjust_lengthand array[right_index] >array[max_num_index]
  9.    then max_num_index = right_index;
  10.    if max_num_index != index
  11.         then exchange array[index]<</SPAN>---> array[max_num_index]
  12.           heap_adjust(array, max_num_index);
 heap_adjust的作用过程如下图:  此处wait_adjust_length = 10;
        图a) 改堆的初始构造,在节点i =2处array[2]违反了堆的性质,因为它不大于它的两个子女(此处是以 大根堆 举例).  
在图b)中通过交换array[2]与array[4],因此在结点2处恢复的堆的性质,但又在结点4处违反了堆的性质现在递归调用heap_adjust(array, 4)就置index=4.
图(c)中在交换了array[4]与array[9]后,结点,结点4处堆性质得到恢复,递归调用heap_adjust(array, 9)对该数据结构不再引起任何变化.        
    在算法的每一步里,从元素array[i],array[lift_index]和array[right_index]中找到最大的,并将其下标存在max_num_index中.如果array[index]是最大的,则以index为根的子树已是堆,算法结束.否则,
index的某个子结点中有最大元素,则换array[index]和array[max_num_index],从而使i及其子女
满足堆的性质,交换后的结点max_num_index中原先的array[index],以该结点为根的子树又有可能
 违反堆性质,因而,要对该子树递归调用heap_adjust.
  
此堆调整算法的时间复杂度:
        heap_adjust(..)作用在一棵以结点index为根的大小为n的子树上运行运行时间是O(1),在这个时间内完成调整元素array[index], array[lift_child(index)],array[right_child(index)]的关系,并对一index的某个子节点为根的子树递归调用heap_adjust(..). index的子节点的子树大小约为2n/3.最坏的情况发生在树的最底一层恰好为半满的时候,这时heap_adjust的运算时间可由下式描述:
        T(n) <= T(2n/3) +O(1)
    可得:T(n) = O(lg n);

3.建堆。
    可以自底向上地用heap_adjust来将一个数组array[1..n] (n = length[array])变成一个堆,因为,
子数组array[(n/2+1)  ....n ]中的元素都是树上中的叶子,每个叶子都可以看成是一个元素的堆,根据堆的性质知,根是一个最值,而自底向上调整数组,就是子根与子根或是子根与叶 再和父根比较 进而可保证越往上值越大(越小)。   
    以下使建堆的伪代码:
   

点击(此处)折叠或打开
build_heap(array[]) 

  1.     
  2. fori = array_length/ 2;i > 0;i--
  3.    heap_adjust(array,i);
下面是一个建堆的过程:  如图(a),这是当i = 2 ,也就是以array[2] = 4为根的子树调整,因为i是从i = 4开始调整堆的,然后i--,所以我们可以看到i = 4, i =3,时,其子树已构成
了一个堆,所以当i =2 ,调用heap_adjust(array,i),先让 index = 2 和2index = 4的结点比较,可得2i结点值大,然后将max_num_index=2index, 然后,用max_num_index和2index+1 = 5的结点比较,可知max_num_index没变,到此已知待调整结点和其子结点中的最大结点下标,然后判断max_num_index 是否等于 index(待排序结点,值为i ),可知max_num_index != index,则将 max_num_index 结点和 index结点进行交换如图(b); 因为,值交换后index = 4的结点破坏了堆的性质,所以,又要堆i = 4结点进行调整,所以在heap_adjust中开始递归调整最后得到(c)图,此时以i= 2为根的子树以满足堆的性质;然后,在build_heap中,i--, i =1,再调用heap_adjust(array,i),然后就重复i = 2是结点调整的步骤,最后就可得一个完整的堆.



此建堆函数的时间复杂度:
        T(n) = O(n);

4.堆排序.
        堆排序,首先应该是一个堆,即先让一个无序序列调整成一个堆,然后再进行排序.

排序的思想:
    1)首先要清楚堆的一个性质,那就是根为最值. 因为数组中最大(或最小)元素是在根array[1]上,因此通过访问根就可以到达排序的效果. 
    2)可通过把它与叶子结点array[n]进行交换来达到最终的正确位置.因为叶子都在array[n/2+1 ..n]上,让第一最值根和最后一个叶子交换,然后,再进行堆调整.
    3)第2)中的交换产生两种效果:
            其一,将第一最值保存到array的最后一个元素空间中(此时已进开始进行数组就地排序了!!)
            其二,叶子结点到根后就会破坏堆性质,那就会引起堆调整反应(但,此时的调整有点变化的就是待调整堆的长度,还是原来的数组长度吗? 那肯定不是,如果还是数组长度的话,就会把叶子上的最值又调到了根上,那就会重复访问根了,所以每次交换后,待排序的长度就要减一(待排序的堆大小),使得堆调整到达交换的叶子结点.),进而将第二最值调到了根array[1]的位置.
        然后就是将次最值和倒数第二个叶子交换(将此最值保存到第一最值的前一位置),就这样重复1)步骤,直到最后一个元素.

下面是排序的伪代码:
heap_length表示数组的长度, wait_adjust_length是待调整堆的长度.

点击(此处)折叠或打开

  1. heap_sort( array[])
  2.    
  3.    build_heap( array);
  4.    for i = heap_length; i > 1; i--
  5.         do exchange array[1]<</SPAN>--->array[i];
  6.              wait_adjust_length = i - 1;
  7.               heap_adjust(array, 1 ,wait_adjust_length);







此排序函数的时间复杂度:
    T(n) = O(nlgn);

由于前面内容较长,再在此处列出程序,会感到很杂乱.所以我把它上传到github上了,如有需要,可点击查看.
c语言的堆排序的泛型程序详见:git@github.com:zbqyexingkong/c_array_sort.git
0 0
原创粉丝点击