<知识整理>树--堆及其应用

时间:2021-06-06 21:10:14

<知识整理>树--堆及其应用

预备知识: 

  完全二叉树的定义:一个深度为k数的二叉树(设根节点的深度为1),若二叉树深度从1到k-1层都是满的,而第k层的节点都集中在左边(即第k层不存在两节点之间有空缺),那么此数就被叫做完全二叉树。

  完全二叉树有几个重要的性质(编号从根节点由1开始广度优先从左到右排):

  编号为i的节点左儿子(如果有的话)的编号为2*i,右儿子(如果有的)的编号为2*i+1,父节点(如果有的话)的编号为i/2。由此还可知道i节点有父亲的充要条件是i/2>=1,有儿子的充要条件是i*2<=size(size为该完全二叉树最大点编号)

堆的概述:

  堆(heap)用数组存储,可看做一个完全二叉树,数组元素的下标即树节点的编号,故可由上性质知a[i]的父亲(如果有的话)为a[i/2],左儿子和右儿子(如果有的话)分别为a[i*2]和a[i*2+1]。

  大根堆:父亲>=儿子;小根堆:父亲<=儿子。由大小根堆定义知堆顶一定是大根堆里最大、小根堆里最小的元素。

  堆的核心操作为put操作(向堆里添一个元素)和get操作(取出堆顶并删除)。

以小根堆为例讲解二操作:

put:1.在堆尾插入一个元素,设其为当前节点;

   2.当前节点与其父亲(如果有的话,否则直接结束)比较;

   3.若比父亲小,与父亲交换值,更新当前节点为父节点,并继续重复第2步:否则结束。

  核心代码:

 void put(int x)
{
heap[++heap_size]=x;
int now,next;//当前节点,父亲节点
now=heap_size;
while(now>)
{
next=now>>;
if(heap[now]>=heap[next]) return;
swap(heap[now],heap[next]);
now=next;
}
}

get:1.保存堆顶元素,让最后一个元素覆盖堆顶元素,堆的大小减一,设当前节点为根节点;

  2.当前节点与其儿子(如果有的话,否则之间结束、返回保存的堆顶元素)中值最小的比较;

  3.若比儿子大,交换它们的值,更新当前节点为该儿子节点,继续第2步比较;否则结束,并返回保存的堆顶元素。

核心代码:

int get()
{
int ans=heap[];
heap[]=heap[size--];
int now=,next;
while(now*<=size)
{
next=now*;
if(next<size&&heap[next+]<heap[next]) next++;//找到最小的儿子
if(heap[now]<=heap[next]) break;
swap(heap[next],heap[now]);
now=next;
}
return ans;
}

  如为大根堆,只需比较时让大的“下潜”就行。

  时间复杂度:都是O(log n)

应用:

  1.堆排序:

    由大小根堆定义知堆顶一定是大根堆里最大、小根堆里最小的元素,便可将待排元素一个个put到一个队里,并一个个get出来,时间复杂度为O(n log n)。

  2.STL优先队列(实质是堆):特点是可以自动排序,详情见大佬博客:

  https://blog.csdn.net/qq_19656301/article/details/82490601

~~~

最后BB:

  可见堆的时间复杂度都是最坏情况的复杂度,个人认为数据极好的话堆排序应该比sort快。。。 不过普通数据用sort准没错。。。

(日后可能更新)