1.二叉树的存储结构介绍
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
1.1顺序存储
顺序结构存储就是使用
数组来存储
,一般使用数组
只适合表示完全二叉树
,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储
二叉树顺
序存储在物理上是一个数组,在逻辑上是一颗二叉树。
1.2 链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,三叉链广泛应用于高阶数据结构如红黑树等等中会使用到
1.2.1二叉链以及三叉链的表示
typedef int BTDataType;
二叉链
struct BinaryTreeNode
{
struct BinTreeNode* _pLeft; 指向当前节点左孩子
struct BinTreeNode* _pRight; 指向当前节点右孩子
BTDataType _data; 当前节点值域
}
三叉链
struct BinaryTreeNode
{
struct BinTreeNode* _pParent; 指向当前节点的双亲
struct BinTreeNode* _pLeft; 指向当前节点左孩子
struct BinTreeNode* _pRight; 指向当前节点右孩子
BTDataType _data; 当前节点值域
};
2.二叉树的顺序结构及实现
2.1 二叉树的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。
现实中我们通常把堆
(
一种二叉树
)
使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统
虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
//二叉树顺序结构
typedef int HPDataType;
typedef struct HP
{
HPDataType* a;
size_t size;
size_t capacity;
}HP;
2.2 堆的概念及结构
如果有一组数据的集合,如K={},
把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足:
且
且
i = 0
,
1
, 2…,则称为小堆
(
或大堆
)
。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2.2.1堆的性质
- 堆中某个节点的值总是不大于或者不小于其父节点的值
- 堆总是一颗完全二叉树或者满二叉树
2.3 堆的实现
2.3.1堆的向上及向下调整算法
给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下或向上调整算法可以把它调整成一个小堆。向下和向上调整算法有一个前提:左右子树必须是一个堆,才能调整。
向下调整算法
向下调整实现
void ADjustdown(HPDataType* ps, size_t parent, size_t size)
{
assert(ps);
默认孩子为左子节点
size_t child = parent * 2 + 1;
如果子节点下标不大于数组长度下标,并且满足大于或小于父节点则与父节点进行对比交换
while (child < size)
{
1.两个孩子节点选最(小/大)的那个,并且右孩子不能超过数组长度范围
if (child+1<size && ps[child] > ps[child + 1])
{
child++;
}
2.如果父亲满足比孩子(小 / 大), 进行交换, 交换后把孩子位置赋予父亲, 并以此为根
求出新的子节点进行新一轮比较
if (ps[parent] > ps[child])
{
Swap(&ps[parent], &ps[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
向上调整算法
向上调整实现
void ADjustup(HPDataType* ps, int child)
{
assert(ps);
int parent = (child - 1) / 2;
如果孩子不=0,则与父节点做对比交换
while (child)
{
如果满足再做交换,不满足则直接结束
if (ps[parent] > ps[child])
//if (ps[parent] < ps[child])
{
Swap(&ps[parent], &ps[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
2.4 堆的创建
向下调整建堆
给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆
现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?
这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
向下调整建大堆
void ADjustdown(HPDataType* ps, size_t parent, size_t size)
{
assert(ps);
默认孩子为左子节点
size_t child = parent * 2 + 1;
如果子节点下标不大于数组长度下标,则与父节点进行对比交换
while (child < size)
{
1.两个孩子节点选最(小/大)的那个,并且右孩子不能超过数组长度
if (child+1<size && ps[child] > ps[child + 1])
{
child++;
}
2.如果父亲满足比孩子(小 / 大), 进行交换, 交换后把孩子位置赋予父亲, 并以此为根
求出新的子节点进行新一轮比较
if (ps[parent] > ps[child])
{
Swap(&ps[parent], &ps[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
int main()
{
int a[] = { 1,5,3,8,7,6 };
int size = sizeof(a) / sizeof(a[0]);
int parent = size - 2 / 2;
父亲节点小于0就代表已经全部构造完毕,结束循环
while (parent >= 0)
{
ADjustdown(a, parent, size);
parent--;
}
}
向上调整建堆
和向下调整一样,给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆
这里我们把除根节点以外的值,都当做末端节点进行向上调整,逐一调整后就完成向上调整建堆
向上调整实现建大堆
void ADjustup(HPDataType* ps, int child)
{
assert(ps);
int parent = (child - 1) / 2;
如果孩子不=0,则与父节点做对比交换
while (child)
{
如果满足再做交换,不满足则直接结束
if (ps[parent] < ps[child])
{
Swap(&ps[parent], &ps[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
int main()
{
int a[] = { 1,5,3,8,7,6 };
int size = sizeof(a) / sizeof(a[0]);
int child = 1;
while (child < size)
{
ADjustup(a, child);
child++;
}
}
2.5 建堆的复杂度
向下调整建堆的时间复杂度
向上调整建堆的时间复杂度
以上两个公式分析中可以看出,虽然两个调整算法原本的时间复杂度都为:O(logN)
但在建堆时细分出向下调整建堆的时间复杂度为O(N),向上调整建堆的时间复杂度为O(N*logN)
因此可以得出结论,在建堆时,使用向下调整算法建堆较优
2.6 堆的插入删除
堆的插入
先插入一个
10
到数组的尾上,再进行向上调整算法,直到满足堆。
堆的插入实现
void HPInit(HP* ps)
{
assert(ps);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
void HPPush(HP* ps, HPDataType x)
{
assert(ps);
if (ps->size == ps->capacity)
{
size_t newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(ps->a, sizeof(HPDataType) * newcapacity);
assert(tmp);
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->size] = x;
ps->size++;
确保每次push过后栈都是有序的
ADjustup(ps->a, ps->size - 1);
}
堆的删除
删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
堆初始化
void HPInit(HP* ps)
{
assert(ps);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
堆的删除实现
void HPPop(HP* ps)
{
assert(ps);
if (HPEmpty(ps))
return;
Swap(&ps->a[0], &ps->a[ps->size - 1]);
ps->size--;
保证每次删除堆顶数据后堆仍然有序
ADjustdown(ps->a, 0, ps->size);
}
2.7 顺序存储二叉树的实现代码
头文件
头文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
二叉树堆结构
typedef int HPDataType;
typedef struct HP
{
HPDataType* a;
size_t size;
size_t capacity;
}HP;
打印
void HPPrint(HP* ps);
堆初始化
void HPInit(HP* ps);
堆销毁
void HPDestory(HP* ps);
堆插入
void HPPush(HP* ps, HPDataType x);
堆删除
void HPPop(HP* ps);
返回堆中数据数量
size_t HPSize(HP* ps);
返回堆顶数据
HPDataType HPTop(HP* ps);
栈是否为空
bool HPEmpty(HP* ps);
void ADjustup(HPDataType* ps, int child);
void ADjustdown(HPDataType* ps, size_t parent, size_t size);
函数文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
return;
}
向上调整
void ADjustup(HPDataType* ps, int child)
{
assert(ps);
int parent = (child - 1) / 2;
如果孩子不=0,则与父节点做对比交换
while (child)
{
如果满足再做交换,不满足则直接结束
if (ps[parent] < ps[child])
{
Swap(&ps[parent], &ps[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
向下调整
void ADjustdown(HPDataType* ps, size_t parent, size_t size)
{
assert(ps);
默认孩子为左子节点
size_t child = parent * 2 + 1;
如果子节点下标不大于数组长度下标,则与父节点进行对比交换
while (child < size)
{
1.两个孩子节点选最(小/大)的那个,并且右孩子不能超过数组长度
if (child+1<size && ps[child] > ps[child + 1])
{
child++;
}
2.如果父亲满足比孩子(小 / 大), 进行交换, 交换后把孩子位置赋予父亲, 并以此为根
求出新的子节点进行新一轮比较
if (ps[parent] > ps[child])
{
Swap(&ps[parent], &ps[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
打印
void HPPrint(HP* ps)
{
assert(ps);
int i = 0;
while (i < ps->size)
{
printf("%d ", ps->a[i]);
i++;
}
printf("\n");
}
堆初始化
void HPInit(HP* ps)
{
assert(ps);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
堆销毁
void HPDestory(HP* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
堆插入
void HPPush(HP* ps, HPDataType x)
{
assert(ps);
if (ps->size == ps->capacity)
{
size_t newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(ps->a, sizeof(HPDataType) * newcapacity);
assert(tmp);
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->size] = x;
ps->size++;
确保每次push过后栈都是有序的
ADjustup(ps->a, ps->size - 1);
}
堆删除
void HPPop(HP* ps)
{
assert(ps);
if (HPEmpty(ps))
return;
Swap(&ps->a[0], &ps->a[ps->size - 1]);
ps->size--;
保证每次删除堆顶数据后堆仍然有序
ADjustdown(ps->a, 0, ps->size);
}
返回堆中数据数量
size_t HPSize(HP* ps)
{
assert(ps);
return ps->size;
}
返回堆顶数据
HPDataType HPTop(HP* ps)
{
assert(ps);
return ps->a[0];
}
栈是否为空
bool HPEmpty(HP* ps)
{
assert(ps);
return ps->size == 0;
}