想知道这些app叫啥名字,对我非常重要,拜托拜托了?

堆是生产中非常重要也很实用的一种数据结构,也是面试中比如求 Top K 等问题的非常热门的考点,本文旨在全面介绍堆的基本操作与其在生产中的主要应用,相信大家看了肯定收获满满!本文将会从以下几个方面来讲述堆:

我们在生产中经常碰到以下常见的问题:

  1. 优先级队列的应用场景很广,它是如何实现的呢
  2. TP99 是生产中的一个非常重要的指标,如何快速计算

可能你已经猜到了,以上生产上的高频问题都可以用堆来实现,所以理解堆及掌握其基本操作十分重要!接下来我们就来一步步地来了解堆及其相关操作,掌握了堆,上面三个生产上的高频问题将不是问题。

  1. 堆是一颗完全二叉树,这样实现的堆也被称为二叉堆
  2. 堆中节点的值都大于等于(或小于等于)其子节点的值,堆中如果节点的值都大于等于其子节点的值,我们把它称为大顶堆,如果都小于等于其子节点的值,我们将其称为小顶堆。

简单回顾一下什么是完全二叉树,它的叶子节点都在最后一层,并且这些叶子节点都是靠左排序的。从堆的特点可知,下图中,1,2 是大顶堆,3 是小顶堆, 4 不是堆(不是完全二叉树)

从图中也可以看到,一组数据如果表示成大顶堆或小顶堆,可以有不同的表示方式,因为它只要求节点值大于等于(或小于等于)子节点值,未规定左右子节点的排列方式。堆的底层是如何表示的呢,从以上堆的介绍中我们知道堆是一颗完全二叉树,而完全二叉树可以用数组表示

如图示:给完全二叉树按从上到下从左到右编号,则对于任意一个节点来说,很容易得知如果它在数组中的位置为 i,则它的左右子节点在数组中的位置为 2i,2i + 1,通过这种方式可以定位到树中的每一个节点,从而串起整颗树。一般对于二叉树来说每个节点是要存储左右子节点的指针,而由于完全二叉树的特点(叶子节点都在最后一层,并且这些叶子节点都是靠左排序的),用数组来表示它再合适不过,用数组来存储有啥好处呢,由于不需要存指向左右节点的指针,在这颗树很大的情况下能省下很多空间!

堆有两个基本的操作,构建堆(往堆中插入元素)与删除堆顶元素,我们分别来看看这两个操作

往堆中插入元素后(如下图示),我们需要继续满足堆的特性,所以需要不断调整元素的位置直到满足堆的特点为止(堆中节点的值都大于等于(或小于等于)其子节点的值),我们把这种调整元素以让其满足堆特点的过程称为堆化(heapify)

由于上图中的堆是个大顶堆,所以我们需要调整节点以让其符合大顶堆的特点。怎么调整?不断比较子节点与父节点,如果子节点大于父节点,则交换,不断重复此过程,直到子节点小于其父节点。来看下上图插入节点 11 后的堆化过程

这种调整方式是先把元素插到堆的最后,然后自下而上不断比较子节点与父节点的值,我们称之为由下而上的堆化。有了以上示意图,不难写出插入元素进行堆化的代码:

// 超过堆大小了,不能再插入元素 // 先将元素插入到队尾中 // 由于我们构建的是一个大顶堆,所以需要不断调整以让其满足大顶堆的条件

时间复杂度就是树的高度,所以为 O(logn)。

由于堆的特点(节点的值都大于等于(或小于等于)其子节点的值),所以其根节点(堆项)要么是所有节点中最大,要么是所有节点中最小的,当删除堆顶元素后,也需要调整子节点,以让其满足堆(大顶堆或小顶堆)的条件。假设我们要操作的堆是大顶堆,则删除堆顶元素后,要找到原堆中第二大的元素以填补堆顶元素,而第二大的元素无疑是在根节点的左右子节点上,假设是左节点,则用左节点填补堆顶元素之后,左节点空了,此时需要从左节点的左右节点中找到两者的较大值填补左节点...,不断迭代此过程,直到调整完毕,调整过程如下图示:

但是这么调整后,问题来了,如上图所示,在最终调整后的堆中,出现了数组空洞,对应的数组如下

怎么解决?我们可以用最后一个元素覆盖堆顶元素,然后再自上而下地调整堆,让其满足大顶堆的要求,这样即可解决数组空洞的问题。

看了以上示意图,代码实现应该比较简单,如下:

// 堆中如果没有元素,也就是不存在移除堆顶元素的情况了 * 自上而下堆化以满足大顶堆的条件 // 左节点比其父节点大 // 右节点比左节点或父节点大 // 说明当前节点值为最大值,无需再往下迭代了 * 交换数组第 i 和第 j 个元素

时间复杂度和插入堆中元素一样,也是树的高度,所以为 O(logn)。

用堆怎么实现排序?我们知道在大顶堆中,根节点是所有节点中最大的,于是我们有如下思路:假设待排序元素个数为 n(假设其存在数组中),对这组数据构建一个大顶堆,删除大顶堆的元素(将其与数组的最后一个元素进行交换),再对剩余的 n-1 个元素构建大顶堆,再将堆顶元素删除(将其与数组的倒数第二个元素交换),再对剩余的 n-2 个元素构建大顶堆...,不断重复此过程,这样最终得到的排序一定是从小到大排列的,堆排序过程如下图所示:

从以上的步骤中可以看到,重要的步骤就两步,建堆(堆化,构建大顶堆)与排序。先看下怎么建堆,其实在上一节中我们已经埋下了伏笔,上一节我们简单介绍了堆的基本操作,插入和删除,所以我们可以新建一个数组,遍历待排序的元素,每遍历一个元素,就调用上一节我们定义的 insert(int value) 方法,这个方法在插入元素到堆的同时也会堆化调整堆为大顶堆,遍历完元素后,最终生成的堆一定是大顶堆。

用这种方式生成的大顶堆空间复杂度是多少呢,由于我们新建了一个数组,所以空间复杂度是 O(n),但其实堆排序是原地排序的(不需要任何额外空间),所以我们重点看下如何在不需要额外空间的情况下生成大顶堆。

其实思路很简单,对于所有非叶子节点,自上而下不断调整使其满足大顶堆的条件(每个节点值都大于等于其左右节点的值)即可,遍历到最后得到的堆一定是大顶堆!同时调整堆的过程中只是不断交换数组里的元素,没有用到额外的存储空间。那么非叶子节点的范围是多少呢,假设数组元素为 n,则数组下标为 1 到 n / 2 的元素是非叶子节点。下标 n / 2 + 1 到 n 的元素是叶子节点。画外音:假设下标 n/2+1 的节点不是叶子节点,则其左子节点下标为 (n/2 + 1) * 2 = n + 2,超过了数组元素 n,显然不符合逻辑,由此可以证明 n / 2 + 1 开始的元素一定是叶子节点示意图如下:

如图示:对每个非叶子节点自上而下调整后,最终得到大顶堆。有了以上思路,不难写出如下代码:

* 对 1 妻 n/2 的非叶子节点自上而下进行堆化,以构建大顶堆

这样建堆的时间复杂度是多少呢,我们知道对每个元素进行堆化时间复杂度是 O(log n),那么对 1 到 n/2 个元素进行堆化,则总的时间复杂度显然是 O(n log n)(实际上如果详细推导,时间复杂度是 O(n),这里不作展开,有兴趣的同学建议查一下资料看下 O(n) 是怎么来的)。知道怎么建堆,接下来排序就简单了,对 n 个元素来说,只要移除堆顶元素(将其与最后一个元素交换),再对之前的 n-1 个元素堆化,再移除堆顶元素(将其与倒数第二个元素交换)...,不断重复此过程即可,代码如下:

// 将堆顶元素放到第 i 个位置 // 重新对 1 到 i 的元素进行堆化,以让其符合大顶堆的条件

时间复杂度上文已经分析过了,就是 O(n log n),居然和快排一样快!但堆排序实际在生产中用得并不是很多,Java 默认的数组排序(/p/1f16fc7f5e98
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

}

这是什么(:一个森波西你噶)
您有时间吗?(:西赶你是你噶)
傻瓜: 怕不 (加感叹词: 怕不呀)
你说什么?: 木孙素里啊?
你说慌!/骗人!: 阔几满!
无赖/没教养: 撒嘎几
臭混蛋: 望杂个几 (智恩老爱这么叫英宰...汗!)
你疯了吗?: 弄皮差搜?!
操(骂人D): 西吧儿 (非常D脏, 不要随便骂~哈哈)
怎么回事?: 温泥里呀?
怎么了?: 为以类? (或者, 为古类?)
怎么/怎么办: 哦提开
知道了: 啊拉 (啊拉搜哟)
起来!: 以罗那 (智恩老是爱说: 以罗那挖哟!)
说说看/说吧!: 马类吧
真的: 虫么儿 (也可以说"亲加")
很想你: 不过西破 (加感叹次可以是 "就" 或者 "搜")
没事吧/不要紧吧?: 捆察那哟? (可以回答: 捆察那=我没事!)
过分!: 诺满达! (真过分= 亲加诺满达!)
你死定了!: 出过以西!
我走了!: 那儿看达!
爸爸:啊爸(几)  
妈妈:哦妈(泥)  
姐姐(女生叫的):哦你
姐姐(男生叫的):努那  
★★韩语中文读法★★^_^
这是什么(:一个森波西你噶)
您有时间吗?(:西赶你是你噶)
傻瓜: 怕不 (加感叹词: 怕不呀)
你说什么?: 木孙素里啊?
你说慌!/骗人!: 阔几满!
无赖/没教养: 撒嘎几
臭混蛋: 望杂个几 (智恩老爱这么叫英宰...汗!)
你疯了吗?: 弄皮差搜?!
操(骂人D): 西吧儿 (非常D脏, 不要随便骂~哈哈)
怎么回事?: 温泥里呀?
怎么了?: 为以类? (或者, 为古类?)
怎么/怎么办: 哦提开
知道了: 啊拉 (啊拉搜哟)
起来!: 以罗那 (智恩老是爱说: 以罗那挖哟!)
说说看/说吧!: 马类吧
真的: 虫么儿 (也可以说"亲加")
很想你: 不过西破 (加感叹次可以是 "就" 或者 "搜")
没事吧/不要紧吧?: 捆察那哟? (可以回答: 捆察那=我没事!)
过分!: 诺满达! (真过分= 亲加诺满达!)
你死定了!: 出过以西!
我走了!: 那儿看达!

爱你: 撒浪嘿 (这都该知道了吧~~)
傻瓜: 怕不 (加感叹词: 怕不呀)
你说什么?: 木孙素里啊?
你说慌!/骗人!: 阔几满!
无赖/没教养: 撒嘎几
臭混蛋: 望杂个几 (智恩老爱这么叫英宰...汗!
你疯了吗?: 弄皮差搜?!
操(骂人D): 西吧儿 (非常D脏, 不要随便骂~哈哈)
怎么回事?: 温泥里呀?
怎么了?: 为以类? (或者, 为古类?)
怎么/怎么办: 哦提开
知道了: 啊拉 (啊拉搜哟)
起来!: 以罗那 (智恩老是爱说: 以罗那挖哟!)
说说看/说吧!: 马类吧
真的: 虫么儿 (也可以说"亲加")
就先说这么多吧, 再说说搭配法~偶发现呢, 有几个语气词是比较重要的, 就好象我们的"吗,吧,啊,啦"一样!
嘿-------------这不是语气词, 而是用在第二人称时候...大家肯定知道" 撒浪嘿" 这个"嘿"我觉得就是"对你" 或者 "给你" 的意 思...学过曰语的朋友应该会知道, 在写信的时候首先开头是: XX へ。这里的 "へ” (HE) 和韩语中的"HAE"应该是同一种意思吧。
还有就是, 我觉得韩语很好配~~大家把一个词拆开来想就会知道是说什么了.
比如: 哦提开啊拉搜哟? = 哦提开 + 啊拉 +搜 +哟 = "你怎么会知道?" 的意思.
(怎么) (知道) (问句语气词)
三以曰出卡嘿哟! = 三以曰 + 出卡嘿 +哟 = "生曰快乐" 的意思.
摆里那挖哟! = 摆里 + 那挖 + 哟 = "快点出来"的意思!

}

我要回帖

更多关于 拜托了冰箱推荐的做菜app 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信