排序算法 -- 堆排序

前言

面试中被问到了一个题目(http://www.voidcn.com/blog/u010943214/article/p-3808842.html),然后重温了一遍堆排序。

问题:

给你一个双向链表,有序输出,

限制:

  空间复杂度O1,

  时间复杂度nlogn,最坏不能退化n2

思路:

  1 根据双向链表构建一个二叉树

  2 对二叉树进行堆排序

堆的定义

  定义:堆如果用一个二叉树表示的话,那应该是具有以下特性的二叉树

    a 树的形状要求 -- 这颗二叉树是基本完备的(或者简称完全二叉树),这意味着树的每一层都是满的,最后一层最右边的孩子节点有可能空缺

    b 父母优势要求 -- 键的要求 ,父母的键必须大于等于所有子树节点(对于任何叶子节点认为是自动满足的)

堆的特性

1 树的根总是包含了最大元素

2 分形特性, 堆的一个节点以及该节点的子孙也是一个堆

3 可以用数组实现一个堆,方法是从上到下,从左到右记录堆的元素,从1...n的位置记录n个元素,在这种表示法中,

  a 父母节点的键将位于前 n/2的位置上,叶子节点占据后 n/2个位置

  b 对于一个位于位置i的节点, 它的子女将会位于2i 和 2i+1上,对于一个位于位置i的键来说,它的子女将会位于i/2上。

如何构造一个堆

构造堆的过程叫做堆化,这个过程的输入是一个无序数组,输出是一个满足堆特性的数组。

这个过程是从最后一个父母开始,直到堆根为止,检查这个节点是否满足堆的父母优势,如果不满足,交换父母和其中一个子女节点,在新的子女节点位置上,同样检查父母优势,直到满足了父母优势 或者 节点到达了叶子节点

上树过程有两个直到,就是两个循环。

堆化的算法如下

void HeapfyBottomeUp(int a[], int len)

{

  forint i = len/2; i >= 1; i--)

  {

    int k = i;
    int temp = a[i];
    bool isHeap = false;
    while(!isHeap && 2*k<=len)
    {
      int j = 2*k;
      if( j+1 <= len && H[j] < H[j+1])
        j++;
      if(temp >= H[j])
        isHeap = true;
      else
      {
        H[k] = H[j];
        k = j;
      }
    }
    H[k] = temp ;
  }

}

构造一个堆的时间复杂度

  堆化可以在O(n)时间复杂度内完成。

  具体思路如下,第i层有2的i次个元素(i从0计数), 那最坏情况下,第i层需要对比2(h-i)次,每一个对比都要从当前位置到叶子节点, h 是数的深度。

  具体推导如下: 

    

如何从堆中删除最大的键值

  1 把堆根和最后一个叶子节点交换

  2 堆的数量-1

  3 剩余的堆进行堆化的过程。

时间复杂度 O(logn)

堆排序

  再来介绍堆排序就简单了

  堆排序本质上是说,不断的对堆根实施删除,堆化的过程,直到堆的大小变成1 停止,时间复杂度是 O(nlogn)

  算法:

    1 构造一个规模是n的堆

    2 对这个堆 执行 n-1 次 堆根删除操作。

时间复杂度对比

  和 合并排序(归并排序)有同样的时间复杂度,堆排序是在位的,不需要额外的空间, 针对随机文件的计时试验指出(无参考,可以自行对比),堆排序比快排运行的慢,但是和 合并排序有竞争力。

堆排序使用场景

 面试题  笔试题。。。。

原文地址:https://www.cnblogs.com/bianzeming/p/6538638.html