博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
堆和堆排序
阅读量:5950 次
发布时间:2019-06-19

本文共 3786 字,大约阅读时间需要 12 分钟。

在讨论「堆排序」之前,先复习一下「选择排序」。

void SelectionSort(int a[], size_t n) {  for (size_t i = 0; i < n; ++i) {    // 在剩余元素中找出最小的一个,然后与 a[i] 交换。    size_t k = i;    for (size_t j = i+1; j < n; ++j) {      if (a[j] < a[k]) {        k = j;      }    }    if (k != i) {      std::swap(a[i], a[k]);    }  }}

选择排序的空间效率很高(O(1)),但是时间效率很低(O(N^2)),主要花在了「从剩余元素中找出最小元素」,每次都要遍历剩余的全部元素。

有没有一种数据结构,能够方便的拿到 最小 元素?

如果重写 SelectionSort,改为反向遍历,每次「从剩余元素中找出最大元素」:

void SelectionSort(int a[], size_t n) {  for (size_t i = n-1; i > 0; --i) {    // 在剩余元素中找出最大的一个,然后与 a[i] 交换。    size_t k = i;    for (size_t j = 0; j < i; ++j) {      if (a[j] > a[k]) {        k = j;      }    }    if (k != i) {      std::swap(a[i], a[k]);    }  }}

那么问题就可以改成:有没有一种数据结构,能够方便的拿到 最大 元素?

堆,就是这样一种数据结构。

其实堆有「最大堆」和「最小堆」之分,但是差别不大,这里以最大堆为例,是为了便于实现堆排序,这也是改写 SelectionSort 的原因。

堆是一种在数组上实现的几乎完全的二叉树,子节点小于父节点,所以根节点总是最大。

H[1..n] 为堆,以 H[i] 表示数组中第 i 个元素,它的父节点位于 i/2,子节点分别位于 i*2i*2+1

这种通过数组下标的关系来连接父子节点的方式,比一般的树型节点省了两个指针的空间。

给定一个堆 [ 30, 26, 13, 17, 11, 7, 8, 10, 4, 3 ],那么对应的树型结构为:

30            /      \         26          13      /     \      /   \     17      11   7     8   /   \    / 10     4  3

假如有一个数组 [ 4, 3, 7, 10, 11, 13, 8, 26, 17, 30 ],怎么把它转换成堆呢?

4           /      \         3          7      /     \    /    \    10      11  13     8  /   \    / 26   17  30

从最小最靠近叶节点的子树开始,如果根节点比子节点小,就与之交换,依次按如下步骤进行调整。

第一步:

11*             30  /       -->     / 30             11*

第二步:

10*            26  /   \   -->    /   \ 26   17        10*   17

第三步:

7*             13  /   \   -->    /   \ 13    8        7*    8

第四步:

3*      /     \     26      30   /   \    /  10   17  11-->         30      /     \     26      3*   /   \    / 10    17  11-->         30      /     \     26      11   /   \    / 10    17  3*

第五步:

4*           /      \         30        13      /     \     /  \     26      11  7    8   /   \    / 10    17  3-->              30           /      \         4*        13      /     \     /  \     26      11  7    8   /   \    / 10    17  3-->              30           /      \         26        13      /     \     /  \     4*      11  7    8   /   \    / 10    17  3-->              30           /      \         26        13      /     \     /  \     17      11  7    8   /   \    / 10    4*  3

经过这五步,堆就建好了。下面以 C++ 代码示范。

MakeHeap 把一个数组转换成堆:

void MakeHeap(A& a) {  for (size_t i = a.size() / 2; i > 0; --i) {    SiftDown(a, a.size(), i);  }}

类型 A 就是 std::vector。当然用 C 数组也可以,只是后续讨论插入操作时会比较麻烦。

typedef std::vector
A;

MakeHeap 通过 SiftDown 把每棵子树的根节点向下调整。

// SiftDown 把堆 h[1..n] 的第 i 个元素向下调整(i 从 1 打头)。void SiftDown(A& h, size_t n, size_t i) {  while (true) {    i = i * 2;    if (i > n) {      break;    }    if (i < n && h[i] > h[i-1]) {      ++i;    }    if (h[i-1] > h[i/2-1]) {      std::swap(h[i-1], h[i/2-1]);    } else {      break;    }  }}

MakeHeap 的用法:

int data[10] = { 4, 30, 8, 17, 26, 13, 7, 3, 10, 11 };A a(data, data + 10);MakeHeap(a);

堆排序:

void HeapSort(A& a) {  MakeHeap(a);  // 首先把数组 a 转换成堆。  // 反向遍历,每次把堆的根与第 i 个元素交换。  // 每次交换后,用 SiftDown 把新的根向下调整。  for (size_t i = a.size(); i > 1; --i) {    std::swap(a[0], a[i-1]);    SiftDown(a, i-1, 1);  }}

堆排序与选择排序极为相似,空间效率一样,时间效率更优(O(N*logN))。

SiftDown 相反的操作为 SiftUp

// SiftUp 把堆 h[1..n] 的第 i 个元素向上调整(i 从 1 打头)。void SiftUp(A& h, size_t i) {  while (i > 1) {    if (h[i-1] > h[i/2-1]) {      std::swap(h[i-1], h[i/2-1]);    }    i = i / 2;  }}

插入操作依赖于 SiftUp:先添加到数组末尾,然后通过 SiftUp 把新元素向上调整。

void Insert(A& h, int x) {  h.push_back(x);  SiftUp(h, h.size());}

删除操作依赖于 SiftDown:先把要删除的第 i 个元素与最后一个元素交换,然后收缩数组,再通过 SiftDown 把交换上来的元素向下调整。

void Delete(A& h, size_t i) {  size_t n = h.size();  if (n == 0 || i == 0 || i > n) {    return;  }  if (i == n) {    h.resize(n - 1);    return;  }  std::swap(h[i - 1], h[n - 1]);  h.resize(n - 1);  SiftDown(h, n - 1, i);}

转载地址:http://khsxx.baihongyu.com/

你可能感兴趣的文章
敏捷开发方法综述
查看>>
Hadoop数据操作系统YARN全解析
查看>>
Django 运行报错 ImportError: No module named 'PIL'
查看>>
修改数据库的兼容级别
查看>>
Windows下同时安装两个版本Jdk
查看>>
uoj#228. 基础数据结构练习题(线段树)
查看>>
JS键盘事件监听
查看>>
ios开发周期之--(向上,向下,四舍五入)取整
查看>>
加油!
查看>>
拦截导弹问题(动态规划)
查看>>
iOS 单元测试(Unit Test 和 UI Test)
查看>>
[linux小技巧]
查看>>
文件下载_中文乱码:"Content-disposition","attachment; filename=中文名
查看>>
HBase 笔记3
查看>>
2017.11.23 display fun --STM8
查看>>
深入学习jQuery选择器系列第八篇——过滤选择器之伪子元素选择器
查看>>
一个关于log4j的悲伤的故事
查看>>
PCA
查看>>
ajax上传文件
查看>>
java中通过绝对路径将图片存入数据库
查看>>