树的基本概念和堆
- 树的基本概念
- 堆的建立
- 堆的信息
- 堆的初始化
- 堆的push(数据插入后,仍要遵守堆的规则)
- 堆的pop
- 根的元素值
- 堆的大小
- 堆的销毁
- 演示一个过程,打印出该堆的前k个元素
- 全部代码
- Heap.h
- Heap.c
- Test.c
- 堆的应用(排序)
树的基本概念
2.完全二叉树:,最后一层要求至少要有一个结点
节点个数为N的完全二叉树的高度h为log以2为底N的对数。
在一颗度为3的树中,设度为i的节点个数为ni, 树总共有n个节点,则n=n0+n1+n2+n3. 有n个节点的树的总边数为n-1条.根据度的定义,总边数与度之间的关系为:n-1=0n0+1n1+2n2+3n3.
堆的基本概念
堆的建立
选用数组来表示堆的物理结构(存储结构)
堆的信息
用一个结构体来表示堆的信息:堆的空间,堆的大小,堆的容量
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
堆的初始化
断言,该结构体存在,其指针一定存在。为数组开辟空间,size和capacity初始化
void HPInit(HP* php)
{
assert(php);
php->a=(HPDataType*)malloc(sizeof(HPDataType) * 4);
if (php->a==NULL)
{
perror("malloc fail");
return;
}
php->size = 0;
php->capacity = 4;
}
堆的push(数据插入后,仍要遵守堆的规则)
先检查是否需要扩容;直接插到堆的尾部,再做向上调整;
怎么向上调整?和它的parent节点比较,如果比parent小,则不做任何动作,结束; 如果比parent大,则交换child和parent节点的值,再进行和parent节点比较的操作,直至child节点的下标小于等于0(则,parent节点必然不会再有)
void swap(HPDataType* p1, HPDataType* p2)
{
HPDataType temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void AdjustUp(HPDataType* a, int child)
{
//先找到parent和child
int parent = (child - 1) / 2;
while (child>0)
{
if (a[child]>a[parent])
{
//交换值,递进到下一个parent和child
swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
//符合堆的要求,跳出循环
break;
}
}
}
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size==php->capacity)
{
HPDataType* temp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
if (temp==NULL)
{
perror("realloc fail");
return;
}
php->a = temp;
php->capacity = php->capacity * 2;
}
//直接插入尾部
php->a[php->size++] = x;
//向上调整
AdjustUp(php->a, php->size - 1);
}
测试push的代码如下:
HP hp;
HPInit(&hp);
HeapPush(&hp, 1);
HeapPush(&hp, 12);
HeapPush(&hp, 9);
HeapPush(&hp, 20);
结果如下:
堆的pop
删除根,原因,现实世界,用于排序。
并不是直接删除,而是将根和尾节点进行交换,将尾节点删除,对根节点做向下调整。
怎么做向下调整?选择和较大的孩子节点作比较,如果比parent小,则不做任何动作,结束;如果比parent大,则交换child和parent节点的值,再进行和parent节点比较的操作,直至child节点的下标大于n(n为节点的个数size)
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child<n)
{
//找出最大的孩子
if (child+1<n && a[child+1]>a[child])
{
child++;
}
if (a[parent] < a[child])
{
swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
swap(&php->a[0],&php->a[php->size-1]);
//删尾
php->size--;
//向下调整
AdjustDown(php->a, php->size, 0);
}
测试代码如下:
HP hp;
HPInit(&hp);
HeapPush(&hp, 1);
HeapPush(&hp, 12);
HeapPush(&hp, 9);
HeapPush(&hp, 20);
HeapPop(&hp);
结果如下:
根的元素值
直接返回a[0]
HPDataType HeapTop(HP* php)
{
assert(php);
return php->a[0];
}
堆的大小
直接返回size
HPDataType HeapSize(HP* php)
{
assert(php);
return php->size;
}
堆的销毁
void HPDestroy(HP* php)
{
free(php->a);
php->a = NULL;
php->capacity = 0;
php->size = 0;
}
演示一个过程,打印出该堆的前k个元素
代码如下:
HP hp;
HPInit(&hp);
HeapPush(&hp, 1);
HeapPush(&hp, 12);
HeapPush(&hp, 9);
HeapPush(&hp, 20);
HeapPush(&hp, 27);
HeapPush(&hp, 20);
HeapPop(&hp);
int k = 3;
while (!HeapEmpty(&hp) && k--)
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
return 0;
结果如下:
全部代码
Heap.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void HPInit(HP* php);
void HPDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
HPDataType HeapSize(HP* php);
bool HeapEmpty(HP* php);
//向上调整
void AdjustUp(HPDataType* a,int child);
//向下调整
void AdjustDown(HPDataType* a, int n, int parent);
Heap.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
void HPInit(HP* php)
{
assert(php);
php->a=(HPDataType*)malloc(sizeof(HPDataType) * 4);
if (php->a==NULL)
{
perror("malloc fail");
return;
}
php->size = 0;
php->capacity = 4;
}
void swap(HPDataType* p1, HPDataType* p2)
{
HPDataType temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void AdjustUp(HPDataType* a, int child)
{
//先找到parent和child
int parent = (child - 1) / 2;
while (child>0)
{
if (a[child]>a[parent])
{
//交换值,递进到下一个parent和child
swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
//符合堆的要求,跳出循环
break;
}
}
}
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size==php->capacity)
{
HPDataType* temp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
if (temp==NULL)
{
perror("realloc fail");
return;
}
php->a = temp;
php->capacity = php->capacity * 2;
}
//直接插入尾部
php->a[php->size++] = x;
//向上调整
AdjustUp(php->a, php->size - 1);
}
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child<n)
{
//找出最大的孩子
if (child+1<n && a[child+1]>a[child])
{
child++;
}
if (a[parent] < a[child])
{
swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
swap(&php->a[0],&php->a[php->size-1]);
//删尾
php->size--;
//向下调整
AdjustDown(php->a, php->size, 0);
}
HPDataType HeapTop(HP* php)
{
assert(php);
return php->a[0];
}
HPDataType HeapSize(HP* php)
{
assert(php);
return php->size;
}
void HPDestroy(HP* php)
{
free(php->a);
php->a = NULL;
php->capacity = 0;
php->size = 0;
}
Test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
int main()
{
HP hp;
HPInit(&hp);
HeapPush(&hp, 1);
HeapPush(&hp, 12);
HeapPush(&hp, 9);
HeapPush(&hp, 20);
HeapPush(&hp, 27);
HeapPush(&hp, 20);
HeapPop(&hp);
int k = 3;
while (!HeapEmpty(&hp) && k--)
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
HPDestroy(&hp);
return 0;
}
堆的应用(排序)
对数组进行排序,比如排升序.
一般情况下,我们会认为排升序,需要建小堆,但这种想法是不正确的,小堆的第一个元素没有问题,但是从第二个节点开始关系可能会混乱,还需要再进行建小堆,时间复杂度会增加。我们这里需要建大堆。
这里可以通过两种方式来建立大堆,向上调整建堆和向下调整建堆。
- 向上调整建堆:从第二个元素开始,大的child和小的parent交换;
- 向下调整建堆:从最后一个叶子的parent开始调整,大的child和小的parent交换,依次倒数
建完大堆后如何实现排序,首先大堆的根一定是最最大,让它和最后一个元素交换位置,则升序的最后一个数字便确定了下来;对此时的首元素向下调整,可以找到次大的元素;依次循环便可以排好序。
向上调整建堆的代码
void HeapSort(int* a, int n)
{
//向上调整
for (int i=1;i<n;i++)
{
AdjustUp(a, i);
}
int end = n - 1;
while (end>0)
{
swap(&a[end], &a[0]);
AdjustDown(a, end, 0);
end--;
}
}
int main()
{
int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 };
HeapSort(a,10);//排升序
return 0;
}
结果如下:
向下调整建堆的代码
void HeapSort(int* a, int n)
{
//向上调整
//for (int i=1;i<n;i++)
//{
// AdjustUp(a, i);
//}
//向下调整
for (int i = (n-1-1)/2; i>=0; i--)
{
AdjustDown(a,n,i);
}
int end = n - 1;
while (end>0)
{
swap(&a[end], &a[0]);
AdjustDown(a, end, 0);
end--;
}
}
int main()
{
int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 };
HeapSort(a,10);//排升序
return 0;
}
结果如下: