树与二叉树
1. 基本概念
树是一种非线性结构,其严格的数学定义是:如果一组数据中除了第一个节点(第一个节点称为根 节点,没有直接前驱节点)之外,其余任意节点有且仅有一个直接前驱,有零个或多个直接后继, 这样的一组数据形成一棵树。这种特性简称为一对多的逻辑关系。即用于描述具有层次关系,类似组织架构关系的一种数据结构。
树的组成: 根,分支,叶子
2. 常见例子
日常生活中,很多数据的组织形式本质上是一棵树。比如一个公司中的职员层级关系,一个学校中 的院系层级关系,淘汰赛中的各次比赛队伍,一个家族中的族谱成员关系等,这些都是树状逻辑结 构。由于树状结构表现出来都是具有层次的,因此也被称为层次结构。
3. 相关术语
通常,在逻辑上表达一棵抽象的树状结构的时候,习惯于将树根放在顶部,树枝树杈向下生长(倒 树),如下图所示。
对于一棵树来说,有如下基本术语:
-
结点:
树中的元素及其子树
-
根( 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。
-
有序树与无序树:
一棵树中,如果某个节点的孩子节点之间是有次序的,则称这棵树为有序树,反之称为无序树。
4. 二叉树
在各种不同的树状结构中,最常见也最重要的是二叉树(Binary Tree),下面是二叉树的定义:
- 有序树
- 任意节点的度小于等于2
比如如下这棵树就是一棵二叉树。其中8是根节点,14是10的右孩子(因为二叉树是有序树,因此 严格区分左右),而13则是14的左孩子。
为了方便对二叉树进行操作,通常会对一棵树进行标号:从上到下,从左到右进行标号:
注意: 没有孩子节点的地方也要标出来
对于二叉树而言,有如下特性:
- 前𝑖层上,最多有个节点。
- 高度为𝑘的二叉树,最多有 个节点。
- 假设叶子数目为𝑛0,度为2的节点数目为𝑛2,则有:n0=n2+1
二叉树的一般结构:
-
满二叉树
一棵深度为k,且有 个结点的二叉树,称为满二叉树。这种树的特点是每一层上的结点数 都是最大结点数。 简单理解: 除了叶子节点之外,其余节点的度都为2;其特点是: 如果深度/高度为 K,则节点 数为 。
-
完全二叉树
在一棵二叉树中,除最后一层外,若其余层都是满的,或者最后一层是满的,或者是最后一层 在右边缺少连续若干结点,则此二叉树为完全二叉树。 简单理解:除最后一层叶子节点外。是一颗满二叉树,最后一层由右向左有连续缺省的0个,1个 或多个节点。
-
二叉搜索树 (BST-Binaryn Search Tree)
特点:
-
如果节点具有左子树,则左子树上所有节点都不大于(<=)该节点的值; 子树,则右子树上所有节点都不小于(>=)该节点的值;
子树又是二叉搜索数
-
二叉搜索树( 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);
示例图:
-
二叉树数据遍历
二叉搜索树的遍历分为 深度优先遍历(前序遍历、中序遍历、后续遍历)=和广度优先遍历(层序遍历)。
-
前序遍历(先序遍历,即 1 根左右 ))ABDHIEJCFG
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
-
中序遍历(即 左根右 )HDIBJEAFCG
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
-
后序遍历(即 左右根 )HIDJEBFGCA
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时,输出 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);
-
二叉树数据删除
int btree_delete(NODE** root,data_t data);
原则:将待删除的节点尽量转换为删除叶子节点,因为删除叶子节点对BST树影响是最小的; 分析:
关于二叉树(BST)的删除,有以下3种情况:
① 删除的节点本身就是叶子节点
② 删除的节点拥有左侧子节点(可含右侧子节点)
③ 删除的节点仅拥有右侧子节点
思路:
① 从根节点开始遍历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;
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</