什么是堆?
-
一段连续的内存,一般用数组描述堆,用二叉树的方式分析
-
堆中的数据一般是无序的
-
用数组描述堆,一般情况下,第一个空间(下标为 [0] 的空间)不使用,如果使用了第一段内存就会不满足序号的特性
-
上层节点(父节点)比下层节点(子节点)大--->大顶堆
-
上层节点(父节点)比下层节点(子节点)小--->小顶堆
-
大顶堆 || 小顶堆在数组中是无序的,按照二叉树的排布:父节点的值大于子节点的值(大顶堆)
-
完全二叉树:中间不能有空,满足数组的连续性的特点
-
堆的插入和删除根据序号的特性去实现:从第一个顶点开始编序号,依次能够对应数组中的序号,满足序号的需求,从1开始编序号,子节点的序号/2 == 父节点的序号
-
用二叉树的方式去分析,实质上还是一段内存,还是一个数组
堆的插入(向上渗透)
-
插入的元素通常放在当前堆的最后面:插入1,把1放在当前堆的最后面,把 1 和它父节点的值相比较,如果满足大顶堆的需求:当前节点小于父节点的值,就不需要调整;
-
插入100,把100放在当前堆的最后面,100和35做比较,不满足大顶堆的需求,元素向上渗透,把当前节点的值和父节点的值相比较(需要知道父节点的序号:curSize/2),100大于35,大于78,大于89,一直向上渗透,交换位置;
-
不是按照相邻的去做比较,而是按照二叉树的特性去做比较
堆的结构体描述
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
typedef struct
{
int curSize; //记录当前元素个数
int* mem; //堆的内存
int maxSize;
}HEAP,*LPHEAP;
创建堆--->数组实现 用结构体指针表示堆
LPHEAP createHeap(int maxSize)
{
LPHEAP heap = (LPHEAP)malloc(sizeof(HEAP)); //动态内存申请
assert(heap);
//数据初始化
heap->curSize = 0;
heap->maxSize = maxSize;
heap->mem = (int*)malloc(sizeof(int) * heap->maxSize); //堆存的数据 二次申请内存
assert(heap->mem);
return heap;
}
万金油函数
//大小
int size(LPHEAP heap)
{
return heap->curSize;
}
//判断堆是否为空
int empty(LPHEAP heap)
{
return heap->curSize == 0;
}
调整堆
-
调整堆把元素移到正确的位置
-
要插入的元素在当前位置 curPos
-
把当前位置(子节点 curPos) 与 当前位置/2(父节点 curPos/2) 的值做比较:比较到 父节点的值 >当前节点的值 就结束;如果 父节点的值 < 当前节点的值,比较到第一个位置就结束;当前节点的值成为树的根节点为止;如果 父节点的值 == 当前节点的值,没必要向上渗透
//要调整的堆 当前元素的下标
void move(LPHEAP heap, int curPos)
{
while (curPos > 1) //>1 一直往上冒
{
int max = heap->mem[curPos]; //假设当前位置是最大的
//子节点/2=父节点
int parentPos = curPos / 2; //求出父节点的下标
if (max > heap->mem[parentPos]) //比较两个元素 当前元素>父节点中的元素就向上渗透
{
heap->mem[curPos] = heap->mem[parentPos];
heap->mem[parentPos] = max; //交换父节点和子节点的值
curPos = parentPos; //下标向1靠近
}
else //小于的情况说明放在了合适的位置 不用往上冒
{
break;
}
}
}
堆的插入
//要插入的堆 要插入的数据
void insertHeap(LPHEAP heap, int data)
{
if (heap->curSize == heap->maxSize-1) //放进去要考虑满的状态 第1个位置不存数据需要减1
{
return;
}
//存数据:直接放在当前数组后面即可 前置++第一个位置不存数据 存第1个元素放在[1]下标中
heap->mem[++heap->curSize] = data; //heap->mem[0] 第1个位置永远都用不到
//满足大顶堆的要求: 父节点的值大于子节点的值
//向上渗透 调整堆
move(heap, heap->curSize);
}
有序性出堆--->堆排序
-
一般情况下从第1个元素开始出堆:在下一层找一个最大的(注意要找最大的),和第一个元素交换位置,第1个元素向下渗透,把第一个元素(要出堆的元素)放在数组的最后面,curSize--,相当于把元素删除了--->数组的伪删除
-
每次出的都是最大的元素
-
返回要出堆的元素
-
向下渗透注意要把元素和最后一个位置的元素交换
特殊情况
//要出的堆
int popHeap(LPHEAP heap)
{
int max = heap->mem[1]; //第一个元素肯定是最大的 下标为[1]的元素
int curPos = 1; //当前下标
int childPos = curPos * 2; //子节点下标--->每一组中的第一个元素
while (childPos <= heap->curSize)
{
int temp = heap->mem[childPos]; //子节点的值
//横向比较找最大值 只要比较横向的2个值 childPos + 1为右边的值
if (childPos + 1 <= heap->curSize && temp < heap->mem[childPos + 1])
{
temp = heap->mem[++childPos]; //如果左边的值<右边的值 需要往右走
}
heap->mem[curPos] = temp; //如果往左边走 接着往下找即可
curPos = childPos; //当前pos往下走
childPos *= 2;
}
heap->mem[curPos] = heap->mem[heap->curSize]; //找到最终交换的元素
move(heap, curPos); //存在不满足规则的情况做调整
heap->curSize--;
return max;
}
测试代码
int main()
{
srand((unsigned int)time(NULL)); //设置随机函数种子
LPHEAP heap = createHeap(11); //创建堆
for (int i = 0; i < 11; i++)
{
insertHeap(heap, rand() % 100);
}
printf("堆中数据:\n");
for (int i = 1; i < 11; i++)
{
printf("%d\t",heap->mem[i]);
}
printf("\n");
printf("堆排序:\n");
while (!empty(heap))
{
printf("%d\t", popHeap(heap));
}
printf("\n");
return 0;
}
//测试
堆中数据:
76 76 59 48 11 14 56 43 47 2
堆排序:
76 76 59 56 48 47 14 43 11 2
76 76 59 56 48 47 43 14 11 2 //调整堆