【排序】堆、完全二叉树、堆排序、PriorityQueue、TopK
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了【排序】堆、完全二叉树、堆排序、PriorityQueue、TopK相关的知识,希望对你有一定的参考价值。
参考技术A可以利用堆、外排序的方法来做多个处理单元的结果合并
堆,其实就是一个完全二叉树;
完全二叉树,要么是一个满二叉树,要么叶子节点层为从左到右;
堆,实际中是可以用数组实现的;
规律,把数组脑补成完全二叉树
节点 n 的左孩子为 2*n + 1 ,也就是 n<<1 +1
节点 n 的左孩子为 2*n + 2 ,也就是 n<<1 +2
节点 n 的父节点为 (n-1)/2 ,也就是 (n-1)>>2
堆分为小根堆、大根堆
小根堆:在完全二叉树中, 任何一个子树 的最小值都在头部,小根堆,数组最小值在 arr[0]
大根堆:在完全二叉树中, 任何一个子树 的最大值都在头部,大根堆,数组最大值在 arr[0]
思想:1、根据大根堆的特点,先将一个数组构建成为一个大根堆,这样堆顶就是最大的
2、再将堆顶和最后一个数互换,将最大的数放到最后,随即调整维持大根堆
继续互换、调整。
java实现的堆结构、优先级队列, PriorityQueue()
PriorityQueue当size达到了初始的initialCapacity容量后会进行扩容,每次容量加1。
1->1
1,2->1.5
1,2,3->2
...
准备一个大根堆,一个小根堆
把小于等于大根堆对顶的树插入大根堆,
把大于大根堆对顶的树插入小根堆;
同时,要调整大根堆和小根堆的数量,数量差值不要超过1;
如果大根堆的size大了,就把大根堆 的堆顶取出插入小根堆;
如果小根堆的size大了,就把小根堆 的堆顶取出插入大根堆;
这样就能达到,大根堆,小根堆数量保持平衡,同时大根堆中的小的n/2个数,小根堆中是大的n/2个数,
(大根堆堆顶+小根堆堆顶)/2 就是流的中位数
假设我们有 100 个小文件,每个文件的大小是 100MB,每个文件中存储的都是有序的字符串。我们希望将这些 100 个小文件合并成一个有序的大文件。
思路: 利用一个小根堆,把每个文件的第一条记录取出来,放入小根堆中,那么小根堆的堆顶就是这100条记录中顺序最小的记,它为记录A,也是这100个文件中顺序最小的,将这个堆顶追加到准备的大文件。从记录A原来所在的文件中再取出一条记录,放入小根堆,取出堆顶,如此重复...
思路: 利用一个size为k的堆,求最大的Top K 用小根堆 ,求最小的Top k用大根堆。
我们可以维护一个大小为 K 的小顶堆,顺序遍历数组,从数组中取出取数据与堆顶元素比较。如果比堆顶元素大,我们就把堆顶元素删除,并且将这个元素插入到堆中;如果比堆顶元素小,则不做处理,继续遍历数组。这样等数组中的数据都遍历完之后,堆中的数据就是前 K 大数据了。
遍历数组需要 O(n) 的时间复杂度,一次堆化操作需要 O(logK) 的时间复杂度,所以最坏情况下,n 个元素都入堆一次,所以时间复杂度就是 O(nlogK)。
使用map<key,count>计数,count放入小根堆中,多个小根堆合并求top10.
分析:假设一条搜索50个字节,10亿条那么就占用了50GB的大小,所以一次对所有数据进行统计是不可行的。
0、准备10个空文件
1、采用hash 算法将日志进行计算之后得到hash值,模10,存放入对应的文件中。那么每个文件大概会有1亿条记录,假设平均每条关键词重复10次,那么就是1000万条关键词,大概500MB,1G的内存是可以存下的
2、使用hashmap对每个小文件中的关键词进行数量统计,对每个小文件的统计的结果存入一个Size为10 的小根堆。
3、对这10个小根堆再进行合并得到最终的Top 10 的搜索关键词
推荐阅读:
数据结构和算法|堆的应用
堆和堆排序
参考技术A 1,堆是一个完全二叉树;完全二叉树要求除了最后一层,其他层的节点都是满的,最后一层的节点都靠左排列。
2,堆中每个节点都必须大于等于(或小于等于)其子树中每个节点的值。
堆中每个节点的值都大于等于(或者小于等于)其左右子节点的值。
3,对于每个节点的值都大于等于子树中每个节点值的堆,叫作“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,叫“小顶堆”。
要实现一个堆,要先知道堆都支持哪些操作,已及如何存储一个堆。
1,如何存储一个堆:
完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。
2,往堆中插入一个元素
往堆中插入一个元素后,需要继续满足堆的两个特性
(1)如果把新插入的元素放到堆的最后,则不符合堆的特性了,于是需要进行调整,让其重新满足堆的特性,这个过程叫做 堆化(heapify)
(2)堆化实际上有两种,从下往上和从上往下
(3)从下往上的堆化方法:
堆化非常简单,就是顺着节点所在的路径,向上或者向下,对比,然后交换。
(1)从堆的定义的第二条中,任何节点的值都大于等于(或小于等于)子树节点的值,则堆顶元素存储的就是堆中数据的最大值或最小值。
(2)假设是大顶堆,堆堆顶元素就是最大的元素,但删除堆顶元素之后,就需要把第二大元素放到堆顶,那第二大元素肯定会出现在左右子节点中。然后在迭代地删除第二大节点,以此类推,直到叶子节点被删除。
但这种方式会使堆化出来的堆不满足完全二叉树的特性
(3)可以把最后一个节点放到堆顶,然后利用同样的父子节点对比方法,对于不满足父子节点大小关系的,互换两个节点,并且重复进行这个过程,直到父子节点之间满足大小关系为止,这是从上往下的堆化方法。
一个包含n个节点的完全二叉树,树的高度不会超过log2n。堆化的过程是顺着节点所在路径比较交换的,所以堆化的时间复杂度跟树的高度成正比,即O(log n)。插入数据和删除堆顶元素的主要逻辑就是堆化,所以往堆中插入一个元素和删除堆顶元素的时间复杂度都是O(log n)。
(1)排序方法有时间复杂度是O(n^2)的冒泡排序,插入排序,选择排序,有时间复杂度是O(nlogn)的归并排序,快速排序,线性排序。
(2)借助堆这种数据结构实现的排序算法就叫作堆排序,这种排序方法的时间复杂度非常稳定,是O(nlogn),并且它还是原地排序算法。
堆排序的过程大致分解为两大步骤:建堆和排序
(3)建堆:
1,首先将数组原地建成一个堆。“原地”:是指不借助另一个数组,就在原地数组上操作。
2,建堆有两种思路:
第一种:在堆中插入一个元素的思路。
尽管数组中包含n个数据,但是可以假设起初堆中只包含一个数据,就是下标为1的数据。然后,调用插入方法,将将下标从2到n的数据依次插入到堆中,这样就将包含n个数据的数组,组织成了堆
第二种:是从后往前处理数组,并且每个数据都是从上往下堆化。
第二种和第一种思路截然相反,第一种建堆思路的处理过程是从前往后处理数据,并且每个数据插入堆中时,都是从下往上堆化。
对下标从n/2开始到1的数据进行堆化,下标是n/2 + 1到n的节点,是叶子节点,不需堆化
3,建堆的时间复杂度
每个节点堆化的时间复杂度是O(logn),则n/2+1个节点堆化的总时间复杂度是O(n)。
①:因为叶子节点不需要堆化,所以需要堆化的节点从倒数第二层开始。每个节点堆化的过程中,需要比较和交换的节点个数,跟这个节点高度k成正比。
(4)排序:
建堆结束后,数组中的数据已是按照大顶堆的特性来组织的。数组中的第一个元素就是堆顶,也就是最大的元素。
将它和最后一个元素交换,最大元素就放到了下标为n的位置
这个过程有点类似“删除堆顶元素”的操作,当堆顶元素移除后,把下标为n的元素放到堆顶,然后在通过堆化的方法,将剩下的n-1个元素重新构建成堆。堆化完成之后,在取堆顶元素,放到下标是n-1的位置,一直重复这个过程,直到最后堆中只剩下标为1的一个元素,排序工作就完成了。
(5)时间,空间复杂度,以及稳定性分析
①:整个堆排序的过程,都只需要极个别临时存储空间,所以堆排序是原地排序算法。
②:堆排序包括建堆和排序两个操作,建堆过程的时间复杂度是O(n),排序过程的时间复杂度是O(nlogn),所以堆排序的时间复杂度是O(nlogn)
③:堆排序不是稳定的排序算法,可能改变值相等的数据原始相对顺序。
以上是关于【排序】堆、完全二叉树、堆排序、PriorityQueue、TopK的主要内容,如果未能解决你的问题,请参考以下文章