算法6.堆与堆排序

堆:

二叉堆满足二个特性:

1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。

2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆

一般用数组实现二叉堆

当下标是i,左孩2*i+1,右孩2*i+2,父节点(i-1)/2

最后一个非叶节点 n/2-1 (n是数组长度)  推导过程: 堆排序(完全二叉树)最后一个非叶子节点的序号是n/2-1的原因 - 为了得到而努力 - 博客园

堆实现优先队列有两种关键操作:

1)插入   O(logN)

将新元素加到数组尾,然后把这个新元素上浮(swim)到合适的位置

2)删除   O(logN)

删除一般就是删除最大元素,也就是根节点 A, 数组第一个

一般采取的方法是 将根节点与数组最后一个  交换,然后 下沉(sink)新根节点,   把数组最后一个给删掉,--N

最大堆的 sink和swim

// n代表构成大根堆的数组长度
void sink(int *a, int n, int i) 
{

  int left = 2*i + 1;   // 左孩子的下标

  while (left < n) // 下方还有孩子的时候
  {

    int largest = left + 1 < n && a[left] < a[left + 1]? left+1: left; // 取最大孩子的下标
    if (a[i] >= a[largest])
      break;
    std::swap(a[i], a[largest]);
    i = largest;
    left = 2*i + 1;
  }
}


void swim(int *a, int n, int i)
{
  // 父节点的下标  0的父节点是0 (0-1)/2  向下取整等于0
  int father = (i-1) / 2; 
  while (a[i] > a[father])
  {
    std::swap(a[i], a[father]);
    i = father;
    father = (i-1)/2 ;
  }
}

堆排序

基本思想是,先把一个数组构造成一个最大堆,然后再反复删除最大元素。

1)构造堆有个小窍门,从所有叶子节点不管(也就是下标为[N/2, N-1]的不动),只sink所有非叶节点,并且从最后一个非叶节点开始下沉,也就是从N/2-1开始下沉,最后是0节点。

构造堆的方法:
方法1: 从左到右 挨个swim 时间复杂度 O(nlogn)

时间复杂度 O(nlogn)推导过程:

log(1)+log(2)+...log(n) = log(n!) = O(nlogn)

方法2: 从右到左 挨个sink 。时间复杂度 O(n)

另外还有一个优化,所有叶子节点不管(也就是下标为[N/2, N-1]的不动),只sink所有非叶节点,并且从最后一个非叶节点开始下沉,也就是从N/2-1开始下沉,最后是0节点。

时间复杂度 O(n)推导过程: 

最后n/2的节点只需要1次操作(因为都是叶子节点), 前n/2的节点的后一半只需要2次操作。。。。所以T(n) = N/2+2N/4+3N/8...

而2T(n) = N+2N/2+3N/4...

相减可得 T(n) = N+N/2+N/4....   也就是等比数列求和, 最后可得T(n) = 2n = O(n)
 

2)然后再删除最大元素,第一次将A[0]与A[n - 1]交换,再对A[0]下沉   重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最大的数据并入到后面的有序区间,故操作完成后整个数组就有序了

由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面构造堆的复杂度是O(N)。二次操作时间相加还是O(N * logN),故堆排序的时间复杂度为O(N * logN), 空间复杂度为O(1)。

堆排序代码

void heap_sort(int *a, int n)
{

  if (n < 2)
    return;
  
  // 构建最大堆  时间复杂度O(N)
  // 进一步优化 
  // for(int i=n/2-1;i>=0;--i)
  for (int i = n - 1; i >= 0; i--)
  {
    sink(a, n, i);
  }

  // 排序 时间复杂度O(NlogN)
  for (int i = n - 1; i > 0; i--)
  {
    std::swap(a[0], a[i]); // 把堆顶(堆的最大值) 扔到数组尾
    sink(a, i, 0); // 堆的大小变为了i ,所以形参n这边传了i
  }

}

最小堆的c++实现

参考资料:白话经典算法系列之七 堆与堆排序_MoreWindows Blog-CSDN博客_堆和堆排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值