引例:在搜索算法实现中,会遇到这样一个问题——我们并不希望按照入队的先后顺序出队,而是希望每次出队的都是队列中最大或最小的元素。
而要实现这样的方式,我们可以假设一种队列,取出元素的顺序以大小为准,我们往往将这样的队列称之为优先队列。
优先队列(Priority Queue):特殊的“队列”,依照元素的优先权(关键字)大小取出数据,而不是元素进入队列的先后顺序。
选择合适的数据结构
既然已经有了这样一个设想,那我们应该采用何种方式来实现呢?
使用数组或者链表
我们首先会想到以前学过的基本内容,以数组,链表等方式来储存,为了方便判断各种储存方法的优劣,我们将几种方法的时间复杂度进行比较。
普通数组
插入:我们只需要将普通数组的尾部即可 —— 时间复杂度
O(1)
删除:需要从数组中查找最大或最小的元素,进行一次遍历即可 —— 时间复杂度为
O(n)
链表
插入:元素总是插在链表的头部(头插法) —— 时间复杂度
O(1)
删除:同样需要遍历查找查找最大或最小元素 —— 时间复杂度
O(1)
有序数组:即在插入时就按照一定顺序插入
插入:找到合适位置 —— 时间复杂度
O(n)
或者O(log n)
【二分查找】 移动元素并插入 —— 时间复杂度O(n)
删除:删去最后一个元素 —— 时间复杂度
O(n)
有序链表:同有序数组插入时按照一定顺序插入
插入:找到合适的位置 —— 时间复杂度为
O(n)
插入元素 —— 时间复杂度O(1)
删除:删除首元素或者最后的元素 —— 时间复杂度为
O(1)
我们发现,以上四种实现方式的时间复杂度基本都是 O(n)
,那么可不可以找得到一种数据结构,来降低其时间复杂度呢?
那么就只有树了!我们可以用二叉树来实现,因为平衡二叉树的删除与插入时间复杂度都是与树高有关,时间复杂度是 logN
的。
二叉搜索树
二叉搜索树可以很好地进行插入与删除操作,删除时只需要将最左或者最右元素删除。但如果删除元素变多之后,可能会出现树”一边倒“ 的情况,这时会使时间复杂度变大,不再为 O(logN)
,所以使用二叉搜索树也不很合适。
完全二叉树
将最大值或者最小值置于根结点处,用完全二叉树来存储,在删除之时就可以避免树出现“一边倒”的情况 。
而这种数据结构,便被称作堆。堆在中文维基百科中的定义如下:
堆(英语:Heap)是计算机科学中的一种特别的树状数据结构。若是满足以下特性,即可称为堆:“给定堆中任意节点P和C,若P是C的母节点,那么P的值会小于等于(或大于等于)C的值”。若母节点的值恒小于等于子节点的值,此堆称为最小堆(min heap);反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。
实现堆
堆的两个特性 :
结构性:用数组表示的完全二叉树;
有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)
- “最大堆(
MaxHeap
)”,也称“大顶堆”:最大值 - “最小堆(
MinHeap
)”,也称“小顶堆” :最小值
堆的抽象数据类型描述
类型名称:最大堆(MaxHeap
)
数据对象集:完全二叉树,每个结点的元素值不小于其子结点的元素值
操作集:最大堆H 属于 MaxHeap
,元素item 属于 ElementType
主要操作有:
MaxHeap Create( int MaxSize ):
创建一个空的最大堆。Boolean IsFull( MaxHeap H ):
判断最大堆H
是否已满。Insert( MaxHeap H, ElementType item ):
将元素item
插入最大堆H
。Boolean IsEmpty( MaxHeap H ):
判断最大堆H
是否为空。ElementType DeleteMax( MaxHeap H ):
返回H
中最大元素(高优先级)。
堆的构建
首先应该了解怎样创建一个空的最大堆,但在这之前,我们首先应该设计出合适的结构体来表示堆 :
代码
1 | typedef struct HeapStruct *MaxHeap ; |
堆中元素的插入
插入分两步:
- 将元素插入到数组中元素之后,也就是末位,使之符合完全二叉树的结构
- 调整元素位置,使之成为堆
代码
1 | void Insert ( MaxHeap H, ElementType item ){ |
理解:
- 代码的主要部分为
for
循环及之后,for
循环在于将新插入至末尾的结点与其父节点进行比较,如新插入节点更大,则将其父节点与其交换位置,即可变为堆。
注意:
- 交换位置之时,在循环中直接将父节点覆盖子节点,而最后再将新需要插入的结点直接插入到相应位置之中,可以提高效率。
- 循环之时,如果在堆中不设哨兵,在循环之时还应进行判断,使其循环替换元素之时不能出堆
最大堆的删除
最大堆的删除操作也就是删除掉堆中的最大元素(即树根),并将之返回出去
删除之后堆中会少一个元素,因此我们的思路是:
- 在删除操作进行之时首先将树根元素复制并返回,之后直接将数组最后一个元素将树根元素直接覆盖。
- 然后就是将元素顺序再重新调整为一个堆。
代码
1 | ElementType DeleteMax( MaxHeap H ){ |
理解:代码之中重点就在于重新调整元素,也就是 for
循环中内容——从根节点开始,依次与最大子结点比较,如果小于就与其交换位置,直至调整好为止。
注:for
循环的前一个if是用来调整 child
的,使 child
指向子结点中最大结点,之后就是调整结点元素的过程