《算法图解》学习笔记-02

阅读上一篇文章

6.堆

大家在学习C++时应该在书中看到过堆的身影,但当时我们只是认为堆是由new申请的内存空间,不存在自动变量的自动销毁问题,创建和销毁都是软件工程师决定的;现在我们就来看看堆的详细特征;

堆是一种图的树形结构,被用于实现“优先队列”(文章后面会提到);优先队列是一种可以自由添加数据的结构,但取出数据时要从最小值开始按顺序取出;

堆的特性:

  • 每个节点最多有两个子节点,节点的顺序为从上到下,同一行里为从左到右
  • 在队中存储数据时必须保证子节点大于父节点,因此新数据会添加在最下面一行靠左的位置,或当本行满时重起一行(但这只是放置新元素的规则,并不代表最终位置);

下面是一个堆,让我们尝试将5添加到图中:

1
3
6
4
8
7
5

添加之后:

1
3
6
4
8
7
5

但这个图是有问题的,因为5成为了6的子节点,但5是小于6的,这和“子节点大于父节点”的定义不符合;于是我们设计这样一套算法规则:

  • 从下到上(也可以反着来)整个图,当子节点小于父节点时,父子交换位置;此过程循环进行;
  • 当本循环结束时没有进行任何操作时,循环终止;

这样,我们就能得到最终的结果:

1
3
5
4
8
7
6

那么如何取出数据呢?从堆中取出数据时,必须从最上面取出,也就是拿掉图的顶点;这样会导致一个新的问题:如何选出一个新的顶点?

我们的策略是:将最后一行最右边的数据拿出来作为新的顶点;所以我们先拿掉顶点:

顶点空缺
3
5
4
8
7
6

拿最后一行最右侧节点补位:

6
3
5
4
8
7

然后运行上文提到的算法,调整树的结构:

3
4
5
6
8
7

堆顶端的数据最小,所以无论数据有多少,取出最小值的复杂度都是O(1),另外取出后需要重新调整树的结构,假设树的节点数是 n n n,那么树的高度为 log ⁡ 2 n \log_{2}{n} log2n而重构树的时间复杂度为 O ( log ⁡ 10 n ) O(\log_{10}{n}) O(log10n);

吐槽一点:看到有些算法书将 l o g 10 n log_{10}{n} log10n简写为 log ⁡ n \log{n} logn,这实际上是完全不对的;正确的简写应该是 lg ⁡ n \lg{n} lgn;

这种数据结构可能乍一看想不出用途,我们会在文章后面用到它;狄克斯特拉算法是一种需要频繁取出最小值的寻路算法,可以将堆用在它中用以加速程序运行速度;

7.二叉查找树

我们在堆中熟悉了树这个概念,而现在的二叉查找树是一种类似的结构;它的性质有:

  • 每个节点最多有两个子节点
  • 每个节点的值均大于左分支的所有节点
  • 每个节点的值均小于右分支的所有节点

因此,查找数据时的策略是:检测本节点是否为目标数据;如果不是,当目标数据大于此节点时,转向右侧的子节点访问,当目标数据小于此节点时,转向左侧的子节点访问;如果仍未找到目标数据,重复执行此过程;

这个例子其实在将基本的编程语言的书中出现过,笔者所了解的最早的版本是The C Programming Language的第二版中提到的一个检索文章中是否有某个单词的小程序;

下面是一个已经排好序的二叉树,需要向其中插入一个新的元素:

15
9
23
3
12
17
28
8
1

假设我们要添加1,可以采用这样的思路:假装我们要寻找1,按照查询数据的思路从顶端开始向下访问,当遇到一个应当出现1但没有出现1的位置时,这就是1应当放置的位置;

简单的做一点思考,我们发现1应当作为3的子节点,并且为3的左支:

15
9
23
3
12
17
28
8
1

添加数据相对来说是比较简单的工作,但从二叉树中删除数据的工作相对繁琐;这里先讨论过程复杂的办法:

  • 当删除没有子节点的节点时,直接删除即可
  • 当删除有一个子节点的节点时,删除后将子节点放到该节点的位置上

要删除两个子节点的节点就相对比较麻烦了;当删除有两个子节点的节点时:

  1. 在节点的左子树寻找最大节点,将最大节点节点放到原节点的位置上
  2. 如果左子树还有子节点,就递归执行这个过程

下面讨论简单的方法:
实际上如果开发任务重或时间紧,完全可以在取走某个节点后将节点的子树打散,然后逐个重新添加到树中;这种方式耗费时间长,但是一种快速开发的策略;当程序正常运行后,再重构这部分的代码;

一点拓展知识:

常规的二叉树可能出现一段密度高一段密度低的情况,“平衡二叉树”是针对这个问题设计的一种数据结构;它可以平衡二叉树的重心,小幅度加速读写速度;

继续阅读

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值