赞
踩
堆其实就是个完全二叉树
先了解一下什么是树
就像链表中的节点,不过树中的节点至少指向一个或多个节点,但是,一个结点不能被多个节点指向
堆是完全二叉树,那完全二叉树什么样呢?
先看看二叉树
像这样的,与普通的树不同在于二叉树中的每个结点只能指向两个不同的结点
完全二叉树:
接下来就要看看一下完全二叉树的样子
堆分为大堆和小堆
上面用图描述的堆实际上是逻辑模型,而真实的存放这些数据是用数组来存放,这也叫物理模型
90存在数组下标为0的位置,75存在数组下标为1的位置,80存在数组下标为2的位置,以此类推
好像明明可以用链表存储,为什么非要用数组呢?
因为根据堆的特点,是可以对无序的数组进行排序,并且时间复杂度是O(N*logN)级别的,这样的速度是很优秀的。
//堆的初始化
void HeapInit(Heap* hp);
//堆的销毁
void HeapDestory(Heap* hp);
//向堆中插入数据
void HeapPush(Heap* hp, HDataType x);
//删除堆顶元素
void HeapPop(Heap* hp);
//返回堆顶数据
HDataType HeapTop(Heap* hp);
//判断堆是否为空
bool HeapEmpty(Heap* hp);
//向上调整
void AdjustUp(int* arr, int n);
//向下调整
void AdjustDown(int* arr, int n, int pos);//n是数组的个数,pos是开始调整的位置
堆虽然是个完全二叉树,但它是为了服务数组实现高效排序,因此我们就用数组来实现堆
typedef int HDataType;
typedef struct Heap
{
HDataType* arr;
int size;//数组的有效存储个数
int capacity;//数组的容量
}Heap;
给堆初始化时我并没有给其开辟空间,所以后面插入数据时才会开辟,那么capacity和size自然也是0,给指针arr置空
void HeapInit(Heap* hp)
{
assert(hp);
hp->arr = NULL;
hp->capacity = 0;
hp->size = 0;
}
首先就是判断空间够不够,size如果等于capacity,那就表示已经满了,需要先扩容,这里是扩容,所以要用realloc,如果用malloc,那么之前的数据就丢失了,因为malloc的作用是重新开辟一块新的空间,而不是扩容,扩容是需要使原来的数据保存下来,并且有了新的空间区存放新的数据
接下来就是到了重要的时刻,向上调整AdjustUp,我用小堆来讲解
因为我们要在插入数据的同时要保证数组中的数据存储顺序与逻辑模型中的堆保持一致,新的数据是插在数组末尾的,现在整体看来已经不满足小堆了
void HeapPush(Heap* hp, HDataType x) { assert(hp); if (hp->size == hp->capacity) { int new_capacity = hp->capacity == 0 ? 4 : hp->capacity * 2; HDataType* p = (HDataType*)realloc(hp->arr, sizeof(HDataType) * new_capacity); if (p == NULL) { perror("realloc fail"); return; } hp->arr = p; hp->capacity = new_capacity; } hp->arr[hp->size] = x; hp->size++; AdjustUp(hp->arr, hp->size);//当前个数 }
void AdjustUp(int* arr, int n) { int child = n - 1; int parent = (child - 1) / 2; while (child > 0) { if (arr[child] < arr[parent]) { Swap(&arr[child], &arr[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } }
向下调整:
总结:
当父亲之和小孩子比较大小时,要么交换一次要么不交换;与大孩子比较时,交换两次,交换一次,和不交换;相比之下,如果只与小孩子比较就会节省交换两次的操作,因此父亲与小孩比较大小就行。
void HeapPop(Heap* hp)
{
assert(hp);
Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
hp->size--;
AdjustDown(hp->arr, hp->size,0);//传的是当前的个数
}
思路:
void AdjustDown(int* arr, int n,int pos) { int parent = pos; int child = parent * 2 + 1; while (child < n) { if (((child + 1) < n) && (arr[child] > arr[child + 1])) { child = child + 1; } if (arr[parent] > arr[child]) { Swap(&arr[parent], &arr[child]); parent = child; child = parent * 2 + 1; } else { break; } }
HDataType HeapTop(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
return hp->arr[0];
}
bool HeapEmpty(Heap* hp)
{
assert(hp);
return hp->size == 0;
}
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->arr);
hp->arr = NULL;
hp->capacity = 0;
hp->size = 0;
}
意义:将乱序的数组调整成堆
虽然数组已经有了数据,但我们把建堆的过程看做插入数据,
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。