终于把树简单学习完了,这里只是简单学习完了,以后树的用处还有很多,以后在补补,这里先往后走,二叉堆,这个我们在排序算法中堆排序里已经用过了,不过这里简单描述一下二叉堆。
9.1 二叉堆简介
9.1.1 简介
二叉堆本质上是一种完全二叉树,它分为两个类型。
- 最大堆:任何一个父节点的值,都大于或等于它左右孩子结点的值。
- 最小堆:任何一个父节点的值,都小于或等于它左右孩子结点的值。
图片来自:https://blog.csdn.net/varyall/article/details/81099681
二叉堆的根结点叫做堆顶,最大堆的堆顶是这个堆中的最大值,最小堆的堆顶是这个堆中的最小值。
9.1.2 二叉堆代码结构
二叉堆的存储方式不是链式结构,而是用顺序存储的,因为这个二叉堆就是一个完全二叉树,用数组存储也是合理利用空间。
这里以最小堆为例介绍,优先队列是最大堆,这样两个都可以介绍了。
我们看的时候还是要画成二叉树的风格,这样有利于我们理解,
这个就是最小堆,是不是很想二叉树,不过跟二叉树性质不一样,最小堆的父节点是最小值,排序二叉树的父节点是中间值。
代码中的存储结构:
就是这样的顺序结构,这里没有指向左右孩子的指针,那怎么找到左右孩子呢?
这个我们就要利用数组的下标了,
假设父节点的下标为parent,左孩子的下标是2 * parent + 1 ,右孩子的下标为 2 * parent + 2。
**假设孩子结点的下标为child,那么父节点的下标为 (child - 1)/ 2. **
这个很重要,在代码中就是移动的关键。
代码:
typedef Elemtype int;
typedef struct binHeap
{
int size; //data数组的长度
int len; //有对少有限数据
Elemtype *data; //data数据
}_binHeap;
9.1.3 二叉堆的创建
二叉堆的创建比较简单,跟创建一个栈差不多,都是申请数据,填充长度信息
/**
* @brief 创建binHeap对象
* @param
* @retval
*/
struct binHeap *binHeap_creat(int size)
{
struct binHeap *heap = (struct binHeap *)malloc(sizeof(struct binHeap));
assert(heap);
Elemtype *data = (Elemtype *)malloc(sizeof(Elemtype) *size);
assert(data);
heap->len = 0;
heap->data = data;
heap->size = size;
return heap;
}
/**
* @brief 销毁binHeap对象
* @param
* @retval
*/
int binHeap_destroy(struct binHeap *heap)
{
assert(heap);
if(heap->data)
free(heap->data);
if(heap)
free(heap);
return 0;
}
9.2 二叉堆插入
9.2.1 插入介绍
又来到我们插入环节,这个二叉堆的插入也是比较简单的,二叉堆的插入就两个步骤:
第一,插入到最后一个元素,二叉堆前面的元素都已经符合了最大堆或最小堆的性质,所以新添加的要加入到最后一个元素里。
第二,上浮,新加的一个元素不可能就一定会满足最大堆或最小堆的性质,所以需要调整,这个调整就是上浮。
9.2.2 插入
二叉堆的插入比较简单,从上面都已经看到二叉堆的存储结构是数据,所以只要找到数据最后一个元素插入即可。
代码:
/**
* @brief 二叉堆插入
* @param
* @retval
*/
int binHeap_insert(struct binHeap *heap, Elemtype data)
{
//判断数据元素是否已经超出
if(heap->size <= heap->len + 1) //可以实现扩容
return -1;
heap->data[heap->len] = data;
minHeap_upAdjust(heap);
return 0;
}
是不是看着很简单,minHeap_upAdjust这个函数下节讲,这才是插入的重点。
9.2.3 上浮
插入比较简单的话,那就是调整比较复杂了,像红黑树那样,调整起来多难受,不过这个二叉堆调整起来也简单,只要不断的把最小结点往上提即可。
简单的插入过程:
插入1,然后可以往上浮:
还不满足,继续上浮:
这样就完成了一个上浮操作,结点多的时候,也是这个道理。
代码:
/**
* @brief 最小堆调整
* @param
* @retval
*/
static int minHeap_upAdjust(struct binHeap *heap)
{
//调整的时候,就是不断的和父节点比较,然后判断是否替换父节点
int child = heap->len - 1;
int parent = (child - 1)/2;
Elemtype temp = heap->data[heap->len-1];
//开始循环比较
while(child)
{
//跟第一个父节点比较
if(temp < heap->data[parent]){
//上浮
heap->data[child] = heap->data[parent];
child = parent;
parent = (child - 1)/2;
}
}
heap->data[child] = temp;
return 0;
}
9.3 二叉堆删除
9.3.1 删除介绍
删除跟插入是反方向的,不过大体逻辑一样,也是先删除在调整,废话就不多说了,直接下节,删除。
9.3.2 删除
二叉堆的删除跟插入不一样,插入是插入到最后一个元素,但是删除是删除第一个元素,为什么删除第一个呢?因为第一个元素是这个堆中的最大值或者最小值,我们用了二叉堆这个数据结构,就是必然对最大值或最小值感兴趣,所以删除的是第一个元素。
代码:
/**
* @brief 二叉堆删除
* @param
* @retval
*/
int binHeap_delete(struct binHeap *heap)
{
//判断数据元素是否已经超出
if(heap->size <= 0) //可以实现扩容
return -1;
int head = heap->data[0];
heap->data[0] = heap->data[heap->len-1];
heap->len--;
minHeap_downAdjust(heap);
return head;
}
9.3.3 下沉
下沉步骤:
删除1,然后最后一个元素4替换到1的位置:
然后开始下沉:
然后下沉完成,我画的比较简单,不过原理确实这样。
代码:
/**
* @brief 最小堆下沉
* @param
* @retval
*/
static int minHeap_downAdjust(struct binHeap *heap)
{
//调整的时候,就是不断的和孩子结点比较,然后获取到那个孩子结点的值比父节点的小,替换父节点
int parent = 0;
int left_child = 2 * parent + 1;
int right_child = 2 * parent + 2;
int child = left_child;
Elemtype temp = heap->data[parent];
while(parent < heap->len)
{
//如果存在右孩子,并且右孩子小于左孩子,
if(right_child < heap->len && heap->data[right_child] < heap->data[left_child]) {
child = right_child; //父节点要跟右孩子比较
}
//判断父节点如果小于等于最小孩子结点,就不用移动
if(temp <= heap->data[child])
break;
//这个需要下沉
heap->data[parent] = heap->data[child];
parent = child;
left_child = parent * 2 + 1;
right_child = parent * 2 + 2;
child = left_child;
}
heap->data[parent] = temp;
return 0;
}
9.4 构建二叉堆
构建二叉堆其实就是堆排序,想看详细步骤可以看《二、排序算法下》中的堆排序,这里也是就不描述了。(本来是要写的,但是上面写的代码都是基于优先队列的写的,所以这里就不写了)
9.5 优先对列
队列我们前面已经讲过了,就是先进先出,入队操作,元素加到队尾,出队操作,首元素出队。
优先对垒有什么特殊的呢?
优先队列分为两种情况:
最大优先队列,无论入队顺序如何,都是当前最大的元素优先出队
最小优先队列,无论入队顺序如何,都是当前最小的元素优先出队
看到这两种情况就想到我们的二叉堆,二叉堆会把最小值或最大值调整到第一个元素。
出队的时候,获取到第一个元素即可,然后再调整,就想到上面写的删除操作。
入队操作:这个就更简单,直接插入到队尾,然后调整,这个跟上面的插入操作一样。
代码参考上面,这里就不写了。