1、树的基本概念
2、二叉树的基本概念
3、堆的基本概念与模拟实现
1、树的基本概念
树是由n个节点所组成的非线性结构,它是一个特殊的节点根节点和其余n-1个互不相交的节点相组成,如下图:
几个基本的概念:
节点的度:一个节点含有的子树的个数,如A的度为3。
叶子结点或者终端节点:没有子树的节点,如E、F、G、H、I。
父节点:一个节点若有子树则成为父节点,它的子树称为它的子节点,如B是E的父节点。
兄弟节点:有相同父节点的子节点称为兄弟节点,如E和F是兄弟节点。
树的度:树内节点最大的度为树的度,如上图的树的度为3。
节点的层次:根节点为第1层,往后层次+1。
树的高度或深度:树的高度和深度相同,不过高度是从下向上,深度是从上向下。
2、二叉树的基本概念
二叉树是一种特殊的树,它的每个子树都只有至多2个节点,如下图:
满二叉树和完全二叉树:
满二叉树是二叉树的每一层的节点都达到最大值:
而完全二叉树则是要求除最后一层外节点达最大值,而最后一层节点要依次从左向右排列:
关于二叉树的一些有用的性质:
二叉树度为0的节点永远比度为2的节点多1个,即n0 = n2 + 1。
高度为h的完全二叉树节点数为[2^(h - 1), 2^h - 1]。
满二叉树:n = 2^h - 1,h = log2(n + 1)。
用数组实现二叉树
数组可以模拟实现二叉树的存储结构,但仅适用于满二叉树或完全二叉树。
存在数组中的二叉树的值通过其所在下标位置可以表示一颗满二叉树或完全二叉树中父与子的关系:
parent = (child - 1) / 2
lchild = parent * 2 + 1
rchild = parent * 2 + 2
3、堆的基本概念与模拟实现
堆就是在满二叉树或完全二叉树的基础上对其数据进行一些规则限定:
小根堆:树中所有父亲都小于或等于孩子
大根堆:树中所有父亲都大于或等于孩子
如图就是一个大根堆。
堆的模拟实现(大堆):
模拟一个堆需要包含以下函数:
typedef int hpDataType;
typedef struct Heap {
hpDataType* a;
int size;
int capacity;
}hp;
hpDataType HeapTop(hp* h);
bool HeapEmpty(hp* h);
void HeapInit(hp* h);
void HeapPush(hp* h, hpDataType x);
void AdjustUp(hpDataType* a, int child); //向上调整
void HeapPop(hp* h); //删除堆顶元素
void AdjustDown(hpDataType* a, int n, int parent); //向下调整
void Swap(hpDataType* a, hpDataType* b);
取堆顶元素和判空比较简单,这里的堆顶元素就是这颗二叉树的根节点:
hpDataType HeapTop(hp* h)
{
assert(h);
return h->a[0];
}
bool HeapEmpty(hp* h)
{
assert(h);
return h->size == 0;
}
堆的初始化和Swap函数:
void HeapInit(hp* h)
{
h->a = (hpDataType*)malloc(sizeof(hpDataType) * 4);
h->size = 0;
h->capacity = 4;
}
void Swap(hpDataType* a, hpDataType* b)
{
hpDataType x = *a;
*a = *b;
*b = x;
}
接下来两个函数是模拟实现堆的难点,堆的插入和删除。
先从插入来看,已知有了一个大堆:
此时插入一个数据:
要想让它调整为大堆,那么就要对这个新插入的节点进行处理,根据大堆的规则,这里就要用到向上调整的方法,就是让它依次与它的父节点相比较,如果大于父节点则交换,直到它不大于父节点或调整到根节点为止。
看代码:
void AdjustUp(hpDataType* a, int child) //大堆
{
int parent = (child - 1) / 2;
while (child > 0) //child等于0时已经调整到根位置
{
if (a[parent] < a[child])
{
Swap(&a[parent], &a[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(hp* h, hpDataType x)
{
assert(h);
if (h->size == h->capacity) //扩容操作
{
h->capacity = 2 * h->capacity;
hpDataType* tmp = (hpDataType*)realloc(h->a, sizeof(hpDataType) * h->capacity);
h->a = tmp;
}
h->a[h->size] = x; //插入数据
h->size++;
AdjustUp(h->a, h->size - 1);
}
而删除操作删除的是堆顶的元素,此时若对整棵树再进行调整就比较困难,所以选择让堆顶元素与最后一个元素互换,然后让这个元素从堆顶开始向下调整,这个过程和向上调整类似,直接看代码:
void HeapPop(hp* h) //删除堆顶元素
{
assert(h);
assert(!HeapEmpty(h));
Swap(&h->a[0], &h->a[h->size - 1]); //将最后一个元素与堆顶交换
h->size--;
AdjustDown(h->a, h->size, 0);
}
void AdjustDown(hpDataType* a, int n, int parent) //向下调整
{
int child = parent * 2 + 1; //默认孩子是左孩子
while (child < n)
{
//选出左右孩子中最大的一个
if (child + 1 < n && a[child] < a[child + 1]) //注意越界,child+1如果等与n就越界
{
child = child + 1;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
完整代码如下:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int hpDataType;
typedef struct Heap {
hpDataType* a;
int size;
int capacity;
}hp;
hpDataType HeapTop(hp* h)
{
assert(h);
return h->a[0];
}
bool HeapEmpty(hp* h)
{
assert(h);
return h->size == 0;
}
void HeapInit(hp* h)
{
h->a = (hpDataType*)malloc(sizeof(hpDataType) * 4);
h->size = 0;
h->capacity = 4;
}
void Swap(hpDataType* a, hpDataType* b)
{
hpDataType x = *a;
*a = *b;
*b = x;
}
void AdjustUp(hpDataType* a, int child) //大堆
{
int parent = (child - 1) / 2;
while (child > 0) //child等于0时已经调整到根位置
{
if (a[parent] < a[child])
{
Swap(&a[parent], &a[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(hp* h, hpDataType x)
{
assert(h);
if (h->size == h->capacity) //扩容操作
{
h->capacity = 2 * h->capacity;
hpDataType* tmp = (hpDataType*)realloc(h->a, sizeof(hpDataType) * h->capacity);
h->a = tmp;
}
h->a[h->size] = x; //插入数据
h->size++;
AdjustUp(h->a, h->size - 1);
}
void HeapPop(hp* h) //删除堆顶元素
{
assert(h);
assert(!HeapEmpty(h));
Swap(&h->a[0], &h->a[h->size - 1]); //将最后一个元素与堆顶交换
h->size--;
AdjustDown(h->a, h->size, 0);
}
void AdjustDown(hpDataType* a, int n, int parent) //向下调整
{
int child = parent * 2 + 1; //默认孩子是左孩子
while (child < n)
{
//选出左右孩子中最大的一个
if (child + 1 < n && a[child] < a[child + 1]) //注意越界,child+1如果等与n就越界
{
child = child + 1;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}