数据结构与算法—堆(heap)
Posted 为了维护世界和平_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法—堆(heap)相关的知识,希望对你有一定的参考价值。
目录
堆
1、堆是一个完全二叉树。(除最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列)
2、堆中一个节点的值都必须大于等于(或者小于等于)其子树中每个节点的值。对应的是大顶堆和小顶堆。
堆排序是一种原地的,时间复杂度为O(nlogn)的排序算法
大顶堆:每个节点的值都大于等于子树中每个节点值
小顶堆:每个节点的值都小于等于子树中每个节点值
堆的操作 插入与删除
1、插入
顺着节点所在的路径,向上或者向下对比(先跟最后一个数据比,依次向上),然后交换。让新插入的节点与父节点对比大小,如果满足子节点小于等于父节点,相互交换,一直重复。
2、删除
大顶堆,堆顶元素是最大元素。当我们删除堆顶元素之后,就需要把第二大的元素放到堆顶,依次迭代。直到叶子节点被删除。
问题:数组空洞,不满足完全二叉树定义。
改进 :把最后一个节点放到堆顶,然后用同样的方法比较。这就是从上往下的堆化方法。
建堆
1、堆化
从最后一个非叶子节点i = heap->elements / 2开始,使得数据满足堆的两个特性。建堆时间复杂度O(n)
void heapify(struct heap *heap, int parent)
struct element **elem = heap->elem;
int elements = heap->elements;
int left, right, max;
while (true)
left = parent * 2;
right = left + 1;
max = parent;
if (left <= elements && elem[max]->data < elem[left]->data)
max = left;
if (right <= elements && elem[max]->data < elem[right]->data)
max = right;
if (max == parent)
break;
swap(heap, max, parent);
parent = max;
void build_heap(struct heap *heap)
int i;
for (i = heap->elements / 2; i >= 1; i--)
heapify(heap, i);
2、排序
按照大顶堆:数组中的第一个元素就是堆顶,我们把它跟后一个元素交换,那么最大元素就放到了下标为n的位置。再次堆化,取堆顶元素,放在n-1位置上。这样交换完数据就排序好了
void swap(struct heap *heap, int i, int j)
struct element *tmp;
tmp = heap->elem[j];
heap->elem[j] = heap->elem[i];
heap->elem[i] = tmp;
int heap_sort(struct heap *heap)
int elements = heap->elements;
while (heap->elements)
swap(heap, 1, heap->elements);
heap->elements--;
heapify(heap, 1);
return elements;
排序时间复杂度 O(logn)
堆排序是不稳定的排序算法,在排序过程中,存在将堆的最后一个节点跟堆顶互换。有可能改变相同数据的原始相对顺序。
与快速排序比较
在实际开发中,为什么快速排序要比堆排序性能好
1、堆排序的数据访问方式没有快速排序友好
2、对于相同的数据,堆排序数据交换次数多余快速排序
堆的应用
一、优先级队列
队列最大特点先进先出,而优先级队列,数据的出队顺序不是先进先出,而是按照优先级来,优先级高的先出队。
堆和优先级队列非常相似,一个堆看作一个优先级队列。
1、合并有序小文件:
100个小文件,每个文件大小是100MB,每个文件中存储的都是有序的字符串。
使用优先级队列,从各文件拿出第一个string,将小文件中取出来的字符串放入到小顶堆中,100个文件的string组成的小顶堆,堆顶的元素,就是优先级队列首元素,就是最小的字符串。
将这个小字符串从堆中删除,再从小文件中取出下一个字符串,放入堆中。循环执行
时间复杂度:O(logn)
2、高性能定时器
定时器中维护很多定时任务,每个任务都设定了一个要触发执行的时间点。定时器定时扫描任务,查看任务设置的执行时间,如果到了,就拿出来执行。
每过1秒扫描一遍任务列表做法低效
1、任务定时可能很久,1s扫描很多都是徒劳
2、每次都扫描整个任务列表,如果任务表很大,必然会很耗时。
使用优先级队列
按照任务设定的执行时间,将这些任务存储在优先级队列中,队列首部存储的是最先执行的任务。
拿队首任务的执行时间点,与当前时间相减,得到时间间隔T;T就是需要等待多久。
时间到,从队首取出任务,然后再计算新的任务执行时间与当前时间点的差值。
二、求Top K和中位数
1、TopK问题
1、静态数据,数据事先确定,不会改变
维护一个大小为K的小顶堆,顺序遍历数组,从数组中取出数据与堆顶元素比较。如果比堆顶元素大,把堆顶元素删除,并且将这个元素插入到堆中;
如果比堆顶元素小,则不做处理。等数据都遍历完后,堆数据就是前K大数据。
时间复杂度:遍历数组O(n)时间复杂度,一次堆化操作需要O(logk)的时间复杂度,最坏情况下,n个元素都入堆一次,时间复杂度O(nlogk)
2、动态数据
两个操作:1、添加数据;2、询问当前的K大数据
一直维护一个K大小的小顶堆。
当有数据添加到集合中,拿堆它与堆顶元素对比。如果比堆顶元素大,就把堆顶元素删除,并将这个元素加入到堆中;如果比堆顶元素小,不变。
无论任何时候都需要查询当前的K大数据,都可以得到。
时间复杂度:如果每次询问前K大数据,基于当前的数据重新计算的话,时间复杂度O(nlogk),n表示当前数据的大小。
2、利用堆求中位数(动态数据)
先将数据排序,再将数据一分为二,维护两个堆,一个大顶堆,一个小顶堆。
当有数据插入时,如果数据小于等于大顶堆的元素,就将这个数据添加到大顶堆中;否则,小顶堆。
调整:数据插入,破坏了数据均分的特性;将一个堆顶元素,移动到另一个堆中。
插入数据涉及堆化,时间复杂度为O(logn),查找时间复杂度O(1)
以上是关于数据结构与算法—堆(heap)的主要内容,如果未能解决你的问题,请参考以下文章