算法入门(十):堆结构

147 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情

我们首先讲一下堆这种数据结构,他远远比堆排序重要得多

image.png

最后那个补充一下,优先级队列就是堆结构,只不过通常拿来作优先级使用,不是队列结构

完全二叉树

堆这种结构其实是一种完全二叉树结构,什么是完全二叉树(从左往右 依次遍历满的二叉)

image.png

上图不是完全二叉树,按顺序左侧少了一个

image.png

上图则是完全二叉树

数组转换完全二叉树

接着数组怎么转化为或者对应为完全二叉树呢?-- 把数组从0出发的连续一段依次放入

image.png

接着我们需要找到数组对应二叉树位置的关系,以及二叉树的size:

image.png

0位置,左孩子就是20+1 = 1位置 右孩子就是20+2=2位置

...

记住这个下标变换

引入堆

堆是一棵比较特殊的完全二叉树,其次分为大根堆和小根堆

大根堆:

image.png

以xx为头的整个树最大值是自己(子树最大值就是根):

以6为头整个树,最大的是6

以5为头整个树,最大的是5

小根堆则相反,每一棵子树的最小值是根

大根堆维护 - 存数字 - heapinsert过程

假设我们开辟一段数组空间,初始heapSize=0,用户输入数字,我们如何保证数组对应的二叉树一直是一个大根堆?

用户初始输入5,这时候heapsize++,然后对应的二叉树满足大根堆条件

接着用户输入3:

image.png

我们拿3和其父节点进行比较,父节点0.5*(i - 1)为0位置就是5,没有比较过,不动

接着输入6,我们拿6和0位置比较,>5,和根节点交换:

image.png

交换之后仍然保证是大根堆.....:

image.png

上述的过程就是一个heap insert的过程!对应这一个过程的代码:

image.png

还是比较容易理解的,说的就是当前index位置的数经过while和swap的过程最后确定他的位置,如果此index是最大的数,最后应该出现在根处。

而while里面包含了两种截止条件,第一个是比父小停止,第二个是到0位置停止

现在我们已经解决了用户给我们一个数字,我们都可以存为大根堆结构的目的了!

大根堆维护 - 取数字 - heapify堆化过程

现在用户有了新的需求,想要取大根堆最大的数字,之后将其移除并且保证剩下还是大根堆结构

取最大数字很简单,直接拿arr[0]就是最大数字,问题是如何保证用户取完该数字后剩余数字还是一个大根堆?

这里就需要从上到下进行比较和交换了!

首先第一步,将arr[0]和arr[length - 1]最后一个数字swap一下,并且heapSize--,这样其实就相当于首位最大值已经断开连接没用了。

image.png

接着就需要从上到下进行父与子比较,首先从0位置判断左右子谁大,谁大跟谁比较,如果小于孩子就交换。并且被交换的一侧继续执行上述逻辑,最后1移动到了右下方,并且目前还是一个大顶堆:

image.png

简单来说每次操作完之后都保证了每一个树和子树都是大顶堆了!

对应的过程叫做heapify堆化,我们看一下代码:



public static void heapify(int[] arr, int index, int heapSize){

    // heapSize是需要的,因为他要管着堆边界

    int left = index * 2 + 1; // 左孩子下标

    while(left < heapSize){ // 还存在左孩子时(没有左孩子肯定没有右孩子)

        // 选择左右孩子大的值 - left+1是右孩子下标

        int largest = left + 1 < heapSize && arr[left+1] > arr[left] ? left + 1 : left;

        // 父亲和孩子再比较,选择最大的

        largest = arr[largest] > arr[index] ? largest: index;

        // 判断父节点是连同孩子环境在内的最大,跳出结束堆化过程

        if(largest == index){

            break;

        }

        // index还需要往下走,走之前先交换

        swap(arr, largest, index);

        index = largest;

        left = index * 2 + 1;

    }

}

大根堆维护 - 任意一个位置值变化

用户有一个需求,希望0-heapSize-1的位置上有一个值从a变为?号,这个时候应该怎么做呢?

其实结合上面的方式,我们知道首先应该判断?和a的值谁大,如果i位置更新值变小了,就应该往下进行堆化过程,如果更新值更大,就应该往上执行堆插入过程,具体这两个过程之前都分析过了

image.png

记住,堆结构最重要的就是存和取,heapinsert和heapify这两个操作,其他都是从这两个操作演化来的

复杂度计算

这里我们看看上面操纵的复杂度怎么算?

首先对于完全二叉树的高度要有一个印象,N个节点的完全二叉树的高度是logN级别!

用户新增一个值,heapinsert过程就是向上过程,复杂度是O(logN)

用户移除一个值也是同理,复杂度是O(logN)