第4章 二叉树和BST
树与二叉树
-
基本概念
- 树是一种非线性结构,其严格的数学定义是:如果一组数据中除了第一个节点(第一个节点称为根节点,没有直接前驱节点)之外,其余任意节点有且仅有一个直接前驱,有零个或多个直接后继,这样的一组数据形成一棵树。这种特性简称为一对多的逻辑关系。即用于描述具有层次关系,类似组织架构关系的一种数据结构。
- 树的组成:根,分支,叶子
- 树是一种非线性结构,其严格的数学定义是:如果一组数据中除了第一个节点(第一个节点称为根节点,没有直接前驱节点)之外,其余任意节点有且仅有一个直接前驱,有零个或多个直接后继,这样的一组数据形成一棵树。这种特性简称为一对多的逻辑关系。即用于描述具有层次关系,类似组织架构关系的一种数据结构。
-
常见例子
- 日常生活中,很多数据的组织形式本质上是一棵树。比如一个公司中的职员层级关系,一个学校中的院系层级关系,淘汰赛中的各次比赛队伍,一个家族中的族谱成员关系等,这些都是树状逻辑结构。由于树状结构表现出来都是具有层次的,因此也被称为层次结构。
- 日常生活中,很多数据的组织形式本质上是一棵树。比如一个公司中的职员层级关系,一个学校中的院系层级关系,淘汰赛中的各次比赛队伍,一个家族中的族谱成员关系等,这些都是树状逻辑结构。由于树状结构表现出来都是具有层次的,因此也被称为层次结构。
-
相关术语
-
通常,在逻辑上表达一棵抽象的树状结构的时候,习惯于将树根放在顶部,树枝树杈向下生长,如下图所示。
-
对于一棵树来说,有如下基本术语:
- 结点:树中的元素及其子树。
- 根(root):树的第一个节点,没有直接前驱。如上图中的A。
- 双亲节点/父节点(parent):某节点的直接前驱称为该节点的双亲节点,或成为父节点。例如上图中A是B的父节点。
- 孩子节点/子节点(child):某节点的直接后继称为该节点的孩子节点。例如上图中B、C、D均为A的孩子节点。
- 节点的层次(level):根节点所在的层次规定为第1层,其孩子所在的层次为第2层,后代节点以此类推。比如上图中节点E的层次是3。
- 节点的度(degree):一个节点拥有的孩子节点的总数,称为该节点的度。比如上图中节点B的度为2。
- 叶子(leaf):一棵树中度等于0的节点,被称为叶子,又称为终端节点。比如上图中K、L、F、G、M、I、J均为叶子。
- 树的高度(height):一棵树中所有节点的层次的最大值,称为这棵树的高度,又称为树的深度。比如上图的树的高度为4。
- 有序树与无序树:一棵树中,如果某个节点的孩子节点之间是有次序的,则称这棵树为有序树,反之称为无序树。
-
二叉树
-
定义
- 在各种不同的树状结构中,最常见也最重要的是二叉树(Binary Tree),下面是二叉树的定义:
- 有序树,任意节点的度小于等于2。
- 比如如下这棵树就是一棵二叉树。其中8是根节点,14是10的右孩子(因为二叉树是有序树,因此严格区分左右),而13则是14的左孩子。
-
特性
- 第𝑖层上,最多有2𝑖−1个节点。
- 高度为𝑘的二叉树,最多有2𝑘−1个节点。
- 假设叶子数目为𝑛0,度为2的节点数目为𝑛2,则有:
二叉树的一般结构
-
满二叉树
- 一棵深度为k,且有2^k - 1个结点的二叉树,称为满二叉树。这种树的特点是每一层上的结点数都是最大结点数。
- 简单理解:除了叶子节点之外,其余节点的度都为2;其特点是:如果深度为 K,则节点数为 2^K - 1。
-
完全二叉树
- 在一棵二叉树中,除最后一层外,若其余层都是满的,或者最后一层是满的,或者是最后一层在右边缺少连续若干结点,则此二叉树为完全二叉树。
- 简单理解:除最后一层叶子节点外。是一颗满二叉树,最后一层由右向左有连续缺省的0个,1个或多个节点。
二叉搜索树(BST)
-
特点
- 如果节点具有左子树,则左子树上所有节点都不大于该节点的值;
- 节点具有右子树,则右子树上所有节点都不小于该节点的值;
- 子树又是二叉搜索数。
-
逻辑上的 内存中的
- 二叉树 二叉树
-
二叉搜索树(BST)的组成
- 根指针:指向根节点的指针变量。
- 节点:
- 数据域(存储的实际数据)。
- 指针域 (左,右指针)。
结构设计
typedef int data_t;
typedef struct _node
{
data_t data; // 数据域
struct _node *left; // 左子树指针
struct _node *right;// 右子树指针
}NODE;
二叉树 (BST) 的算法
- 创建二叉树
int btree_create(NODE** root, data_t data);
- 二叉树数据添加
int btree_add(NODE** root, data_t data);
- 示例图
二叉树数据遍历
- 前序遍历 (先序遍历,即 根左右)
void Preorder(const NODE* root);
-
前序遍历通俗的说就是从二叉树的根结点出发,先输出根结点数据,然后输出左结点,最后输出右结点的数据。
-
从根结点出发,则第一次到达结点 A,故输出 A;继续向左访问,第一次访问结点 B,故输出 B;按照同样规则,输出 D,输出 H;当到达叶子结点 H,返回到 D,此时已经是第二次到达 D,故不在输出 D,进而向 D 右子树访问,D 右子树不为空,则访问至 I,第一次到达 I,则输出 I;I 为叶子结点,则返回到 D,D 左右子树已经访问完毕,则返回到 B,进而到 B 右子树,第一次到达 E,故输出 E;向 E 左子树,故输出 J;按照同样的访问规则,继续输出 C、F、G。
-
前序遍历输出结果:ABDHIEJCFG
- 中序遍历 (即 左根右)
void Midorder(const NODE* root);
-
中序遍历通俗的说就是从二叉树的根结点出发,先输出左结点数据,然后输出根结点,最后输出右结点的数据。
- -
从根结点出发,则第一次到达结点 A,不输出 A,继续向左访问,第一次访问结点 B,不输出 B;继续到达 D,H;
-
到达 H,H 左子树为空,则返回到 H,此时第二次访问 H,故输出 H;H 右子树为空,则返回至 D,此时第二次到达 D,故输出 D;由 D 返回至 B,第二次到达 B,故输出 B;按照同样规则继续访问,输出 J、E、A、F、C、G;
-
中序遍历输出结果:HDIBJEAFCG
- 后序遍历 (即 左右根)
void Postorder(const NODE* root);
-
后序遍历通俗的说就是从二叉树的根结点出发,先输出左结点数据,然后输出右结点,最后输出根结点的数据。
-
从根结点出发,则第一次到达结点 A,不输出 A,继续向左访问,第一次访问结点 B,不输出 B;继续到达 D,H;
-
到达 H,H 左子树为空,则返回到 H,此时第二次访问 H,不输出 H;H 右子树为空,则返回至 H,此时第三次到达 H,故输出 H;由 H 返回至 D,第二次到达 D,不输出 D;继续访问至 I,I 左右子树均为空,故第三次访问 I 时,输出;返回至 D,此时第三次到达 D,故输出 D;按照同样规则继续访问,输出 J、E、B、F、G、C,A;
-
后序遍历输出为:HIDJEBFGCA
- 层序遍历
void Levelorder(const NODE* root);
二叉树数据查询
NODE* btree_find(const NODE* root, data_t data);
- 从根结点出发
- 如果比根节点小,那么就去其左子树找
- 如果比根节点大就去其右子树找
- 找到叶子都没找到, 就代表查找失败
二叉树数据更新
隐藏过程
复制
int btree_update(const NODE* root, data_t old, data_t newdata);
二叉树回收
隐藏过程
复制
void btree_destroy(NODE** root);
二叉树数据删除
-
原则:将待删除的节点尽量转换为删除叶子节点,因为删除叶子节点对 BST 树影响是最小的。
-
思路
:
- int btree_delete(NODE** root, data_t data);
- 从根节点开始遍历 BST 找到待删除的节点;
- 对待删除的节点状态进行判断,如果节点有左子树,找到左子树中最大的节点,然后利用左子树中最大的节点数据替换待删除的节点数据,删除左子树中最大的节点;左子树中最大的节点大概率是叶子节点。
- 如果节点只有右子树,找到右子树中最小的节点,然后利用右子树中最小的节点数据替换待删除的节点数据,删除右子树中最小的节点;右子树中最小的节点大概率是叶子节点。
- 如果待删除节点是叶子节点,直接删除。
二叉树 (BST) 完整实现
队列实现
SQueue.h
#ifndef __SQUEUE_H
#define __SQUEUE_H
#include "btree.h"
typedef NODE* type_t;
typedef struct
{
type_t *pData;
int size;
int head;![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/df354bd58cc5428aa589a2e8aae6fada.png#pic_center)
int tail;
}SQueue;
int SQ_init(SQueue *q, int num);
int SQ_isfull(SQueue *q);
int SQ_isempty(SQueue *q);
int SQ_push(SQueue *q, type_t data);
int SQ_pop(SQueue *q, type_t *data);
int SQ_free(SQueue *q);
#endif
SQueue.c
#include <stdlib.h>
#include "SQueue.h"
int SQ_init(SQueue* q, int num)
{
q -> pData = (type_t*)calloc(sizeof(type_t), num);
if(q -> pData == NULL)
return -1;
q -> size = num;
q -> head = q -> tail = 0;
return 0;
}
int SQ_isfull(SQueue *q)
{
return (q -> tail + 1) % q -> size == q -> head;
}
int SQ_isempty(SQueue *q)
{
return q -> tail == q -> head;
}
int SQ_push(SQueue *st, type_t data)
{
if(SQ_isfull(st))
return -1;
st -> pData[st -> tail] = data;
st -> tail = (st -> tail + 1) % st -> size;
return 0;
}
int SQ_pop(SQueue *st, type_t *data)
{
if(SQ_isempty(st))
return -1;
*data = st -> pData[st -> head];
st -> head = (st -> head + 1) % st -> size;
return 0;
}
int SQ_free(SQueue *st)
{
if(st -> pData)
{
free(st->pData);
st -> pData = NULL;
}
st -> head = st -> tail = 0;
}
树的实现
btree.h
#ifndef __BTREE_H
#define __BTREE_H
typedef int data_t;
typedef struct _node
{
data_t data; // 节点上的数据
struct _node *left; // 该节点左侧子节点的地址
struct _node *right;// 该节点右侧子节点的地址
}NODE;
// 创建搜索二叉树
int btree_create(NODE** root, data_t data);
// 二叉树数据添加
int btree_add(NODE** root, data_t data);
// 二叉树数据删除
int btree_delete(NODE** root, data_t data);
// 二叉树前序遍历
void Preorder(const NODE* root);
// 二叉树中序遍历
void Midorder(const NODE* root);
// 二叉树后序遍历
void Postorder(const NODE* root);
// 二叉树层序遍历
void Levelorder(const NODE* root);
// 二叉树数据查询
NODE* btree_find(const NODE* root, data_t data);
// 更新二叉树数据old 为 newdata
int btree_update(const NODE* root, data_t old, data_t newdata);
// 二叉树回收
void btree_destroy(NODE** root);
#endif
btree.c
#include "btree.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "SQueue.h"
@function: int btree_create(NODE** root, data_t data)
@function:
@ret
@function :
@ret
@brief: 创建搜索二叉树
@argument: root: 根指针地址
@argument: data: 存储的数据
成功
-1 失败
int btree_create(NODE** root, data_t data)
{
if(*root)
return -1;
NODE* p = (NODE*)malloc(sizeof(NODE));
if(!p)
return -1;
p -> data = data;
p -> left = NULL;
p -> right = NULL;
*root = p;
return 0;
}
/*
@function: int btree_add(NODE** root, data_t data)
@brief: 二叉树数据添加
@argument: root: 根指针地址
@argument: data: 添加的数据
成功
-1 失败
*/
int btree_add(NODE** root, data_t data)
{
NODE* pNew = (NODE*)malloc(sizeof(NODE));
if(!pNew)
return -1;
pNew -> data = data;
pNew -> left = NULL;
pNew -> right = NULL;
NODE* p = *root;
if(!p)
{
*root = pNew;
return 0;
}
while(p)
{
NODE* q = p;
if(memcmp(&data, &(p -> data), sizeof(data_t)) < 0)
p = p -> left;
else
p = p -> right;
if(memcmp(&data, &(q -> data), sizeof(data_t)) < 0)
q -> left = pNew;
else
q -> right = pNew;
}
return 0;
}
/*
@function: int btree_delete(NODE** root, data_t data)
@brief: 二叉树数据删除
@argument: root: 根指针地址
@argument: data: 待删除的节点数据
成功
-1 失败
*/
int btree_delete(NODE** root, data_t data)
{
/*
原则: