堆的概念及结构
堆的创建
可以定义一个结构体,其中包含一个存放堆数据的一维数组,堆的容量,堆的元素个数
typedef int heapdatesype;
typedef struct heap
{
heapdatesype* date;
int size;
int capacity;
}heap;
堆元素顺序存储原理:
堆的初始化
堆的初始化只需要将堆的容量,堆元素的个数置为零,再将数组首个元素地址置为空即可。
//堆的初始化:
void initheap(heap* hpp)
{
assert(hpp);
hpp->date = NULL;
hpp->size = hpp->capacity = 0;
}
堆的销毁
堆的销毁也只需要将堆的容量,堆元素的个数置为零,将数组空间free释放,再将数组首个元素地址置为空即可。
//堆的销毁:
void heapdestory(heap* hpp)
{
assert(hpp);
hpp->capacity = hpp->size =0;
free(hpp->date);
hpp->date = NULL;
}
堆数据的向上调整:
1.child为需要调整的元素下标,根据二叉树的性质,我们知道其父亲的下标parent为
(child-1)/2;
2.将child与parent比较,若child>parent则交换两个元素(大堆)(小堆为child<parent交换);
3.然后child=parent;parent=(parent-1)/2;再进行步骤2;直到(child<=parent大堆)
(child>=parent小堆)时停下或者child<=0(即child已经没有parent)时停下。
//对数据向上调整:
void adjustup(heapdatesype* p, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (p[parent] < p[child])
{
swap(&(p[parent]), &(p[child]));
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
堆数据的向上调整总结:
原理图:
向上调整前提(重要):数组前面的元素时堆;
时间复杂度:log以2为底N的对数;
堆元素的插入:
1.首先要对堆中存放元素的数组进行容量检查(size=capacity时数组元素已满)
2.若数组已经满了则需要扩容,定义一个newcapacity=(capacity ==0 ? 5 : capacity*2)
为新的容量。括号中三目操作符的意思是,若capacity==0则newcapacity=5,否则newcapacity=
capacity*2;然后用realloc进行扩容;
3.尾插入新的元素x,然后size++;
4.对尾插的新元素进行向上调整到合适位置;
//堆的插入:
void heappush(heap* hpp, heapdatesype x)
{
assert(hpp);
//检查容量:
if (hpp->capacity == hpp->size)
{
int newcapticy = (hpp->capacity == 0 ? 4 : hpp->capacity * 2);
heapdatesype* tmp = (heapdatesype*)realloc(hpp->date, newcapticy * sizeof(heapdatesype));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
hpp->capacity = newcapticy;
hpp->date = tmp;
}
//插入:
hpp->date[hpp->size] = x;
hpp->size++;
//对插入的数据进行向上调整:
adjustup(hpp->date, hpp->size - 1);
}
堆的打印:
堆的打印只需要将存放堆的数组遍历一遍打印即可
//堆的打印:
void heapprintf(heap* hpp)
{
assert(hpp);
int i = 0;
for (i = 0; i < hpp->size; i++)
{
printf("%d ", hpp->date[i]);
}
printf("\n");
}
返回堆顶的元素:
直接返回存放堆元素数组的首个元素即可:
//返回堆顶的元素:
heapdatesype heaptop(heap* hpp)
{
assert(hpp);
assert(hpp->size > 0);
return hpp->date[0];
}
判断堆是否为空:
判断size是否==0即可;
//判断堆是否为空:
bool emptyheap(heap* hpp)
{
assert(hpp);
return (hpp->size == 0);
}
堆元素的向下调整:
1.parent为需要调整的元素的下标,n为堆元素的个数,p是存放堆元素的数组。
2.根据所给的parent可以计算出其左儿子的下标child=2*parent+1;
3.此处以大堆为例,需要判断左右两个儿子那个更大(小堆判断更小),将更大的那个
儿子的下标作为child即:若左儿子更大child=child;右儿子更大则child++;此时还需要注意左右
两个儿子的下标都要小于堆元素的个数n(child <n && child+1 < n);
4.比较此时child与parent的值,若child>parent(小堆child < parnet),则交换child与
parent的值。
5.parent=child,child=2*child+1循环3,4步骤;直到parent被调整到合适的位置或者
child >=n时结束循环;
//堆(大堆)的向下调整:
void adjustdown(heapdatesype* p, int n, int parent)
{
int child = 2 * parent + 1;
while (child < n)
{
//找出更大的那个孩子:(需要保证child+1 < n)
if ( child+1 < n && p[child] < p[child + 1])
{
child++;
}
if (p[parent] < p[child])
{
swap(&(p[child]), &(parent));
//往下再次调整:
parent = child;
child = 2 * child + 1;
}
else
{
break;
}
}
}
总结:
时间复杂度:log以2为底N的对数;
调整前提:被调整元素的左右子树仍然时堆;
堆顶元素的删除:
1.交换堆顶元素和堆中最后一个元素;
2.size--即删除堆顶的元素;
3.将此时的堆顶元素进行向下调整;
原理图:
//堆的删除:
void heappop(heap* hpp)
{
assert(hpp);
assert(hpp->size > 0);
//交换第一个数据和最后一个数据
swap(&(hpp->date[0]), &(hpp->date[hpp->size - 1]));
hpp->size--;
//对数据向下调整:
adjustdown(hpp->date, hpp->size, 0);
}