二叉树和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,则有:n0 = n2 + 1
二叉树的一般结构:
满二叉树
一棵深度为k,且有2^k-1个结点的二叉树,称为满二叉树。这种树的特点是每一层上的结点数都是最大结点数。
简单理解: 除了叶子节点之外,其余节点的度都为2;其特点是: 如果深度为 K,则节点数为 2^K-1
完全二叉树
在一棵二叉树中,除最后一层外,若其余层都是满的,或者最后一层是满的,或者是最后一层在右边缺少连续若干结点,则此二叉树为完全二叉树。
简单理解:除最后一层叶子节点外。是一颗满二叉树,最后一层由右向左有连续缺省的0个,1个或多个节点。
二叉搜索树(BST)
特点:
- 如果节点具有左子树,则左子树上所有节点都不大于该节点的值;
- 节点具有右子树,则右子树上所有节点都不小于该节点的值;
- 子树又是二叉搜索数
二叉搜索树(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时,输出
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找到待删除的节点;
② 对待删除的节点状态进行判断,如果节点有左子树,找到左子树中最大的节点,然后利用
左子树中最大的节
点数据替换待删除的节点数据,删除左子树中最大的节点;左子树中最大的节点大概率
是叶子节点。
③ 如果节点只有右子树,找到右子树中最小的节点,然后 利用右子树中最小的节点数据替
换待删除的节点数
据,删除右子树中最小的节点;右子树中最小的节点大概率是叶子节点。
④ 如果待删除节点是叶子节点,直接删除。二叉树(BST)完整实现
队列实现
- SQueue.h
Queue.h SQueue.c #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; 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
ree.h btree.c #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) @brief: 创建搜索二叉树 @argument: root: 根指针地址 @argument: data: 存储的数据 @ret : 0 成功 -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: 添加的数据 @ret : 0 成功 -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, *q = NULL; if(!p) { *root = pNew; return 0; } while(p) { 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: 待删除的节点数据 @ret : 0 成功 -1 失败 */ int btree_delete(NODE** root,data_t data) { /* 原则:将待删除的节点尽量转换为删除叶子节点,因为删除叶子节点对BST树影响是最小的; 思路: 1. 从根节点开始遍历BST找到待删除的节点; 2. 对待删除的节点状态进行判断,如果节点有左子树,找到左子树中最大的节点,然后利用左子树中最大的 节点数据替换待删除 的节点数据,删除左子树中最大的节点;左子树中最大的节点大概率是叶子节点 3. 如果节点只有右子树,找到右子树中最小的节点,然后 利用右子树中最小的节点数据替换待删除的节点 数据,删除右子树中 最小的节点;右子树中最小的节点大概率是叶子节点 4. 如果待删除节点是叶子节点,直接删除。 */ NODE* del = *root; //指向待删除的节点 NODE* parent = NULL; //指向实际删除节点的双亲节点 NODE* replace = NULL; //指向实际删除的节点 while(del) { if(memcmp(&(del -> data),&data,sizeof(data_t)) < 0) //待删除的数据大于当前节点数据,向右子 树继续查找 { parent = del; del = del -> right; } else if(memcmp(&(del->data),&data,sizeof(data_t)) > 0) //待删除的数据小于当前节点数据,向 左子树继续查找 { parent = del; del = del -> left; } else // 找到了待删除的节点 { if(del -> left) //待删除的节点有左子树 { parent = del ; replace = del -> left; while(replace -> right) //找左子树中最大的节点 { parent = replace; replace = replace -> right; } del -> data = replace -> data; if(parent -> right == replace) //实际删除的节点在双亲节点的右子树上 parent -> right = replace -> left; else parent -> left = replace -> left; free(replace); } else if(del -> right) //待删除的节点只有右子树 { parent = del; replace = del -> right; while(replace -> left) { parent = replace; replace = replace -> left; } del -> data = replace -> data; if(parent -> right == replace) parent -> right = replace -> right; else parent -> left = replace -> right; free(replace); } else //待删除的节点是叶子 { if(!parent) { free(del); *root = NULL; return 0; } if(parent -> left == del) parent -> left = NULL; else parent -> right = NULL; free(del); } return 0; } } return -1; } /* @function: void Preorder(const NODE* root) @brief: 二叉树前序遍历 @argument: root: 根指针 @ret : 无 */ void Preorder(const NODE* root) { if(!root) return ; printf("%4d",root -> data); Preorder(root->left); Preorder(root->right); } /* @function: void Midorder(const NODE* root) @brief: 二叉树中序遍历 @argument: root: 根指针 @ret : 无 */ void Midorder(const NODE* root) { if(!root) return ; Midorder(root->left); printf("%4d",root -> data); Midorder(root->right); } /* @function: void Postorder(const NODE* root) @brief: 二叉树后序遍历 @argument: root: 根指针 @ret : 无 */ void Postorder(const NODE* root) { if(!root) return ; Postorder(root->left); Postorder(root->right); printf("%4d",root -> data); } /* @function: void Postorder(const NODE* root) @brief: 二叉树层序遍历 @argument: root: 根指针 @ret : 无 */ void Levelorder(const NODE* root) { SQueue q; SQ_init(&q,100); SQ_push(&q,(type_t)root); while(!SQ_isempty(&q)) { type_t dt; if(0 == SQ_pop(&q,&dt)) { printf("%4d",dt -> data); if(dt -> left) SQ_push(&q,dt->left); if(dt -> right) SQ_push(&q,dt->right); } } printf("\n"); SQ_free(&q); } /* @function: NODE* btree_find(const NODE* root,data_t data) @brief: 二叉树数据查询 @argument: root: 根指针 @argument: data: 待查询的数据 @ret : 成功:返回查询到的节点地址 失败: NULL */ NODE* btree_find(const NODE* root,data_t data) { const NODE *p = root; while(p) { if(memcmp(&data,&(p -> data),sizeof(data_t)) < 0) p = p -> left; else if(memcmp(&data,&(p -> data),sizeof(data_t)) > 0) p = p -> right; else return (NODE*)p; } return NULL; } /* @function: int btree_update(const NODE* root,data_t old,data_t newdata) @brief: 更新二叉树数据old 为 newdata @argument: root: 根指针 @argument: old: 待更新的数据 @argument: newdata: 更新后的数据 @ret : 成功:0 失败: -1 */ int btree_update(const NODE* root,data_t old,data_t newdata) { NODE* pfind = btree_find(root,old); if(!pfind) return -1; pfind -> data = newdata; return 0; } /* @function: void btree_destroy(NODE** root) @brief: 二叉树回收 @argument: root: 根指针地址 @ret : 无 */ static void btree_free(NODE* root) { if(!root) return ; btree_free(root->left); btree_free(root->right); free(root); } void btree_destroy(NODE** root) { btree_free(*root); *root = NULL; }
- btree_main.c
#include "btree.h" #include <stdio.h> #include <time.h> #include <stdlib.h> #define N 10 int main(void) { NODE *root = NULL; register int i = 0; data_t a[] = {20,15,25,10,19,28,6,13,26,30,11,27}; int n = sizeof a / sizeof a[0]; srand(time(NULL)); for(; i < n ; i++) { // int data = rand() % 99 + 1; // printf("%4d",data); // btree_add(&root,data); btree_add(&root,a[i]); } printf("\n"); puts("====先序遍历===="); Preorder(root); printf("\n"); puts("====中序遍历===="); Midorder(root); printf("\n"); puts("====后序遍历===="); Postorder(root); printf("\n"); puts("====层序遍历===="); Levelorder(root); printf("\n"); data_t deldata = 0; while(1) { printf("请输入要删除的数据(-1退出):"); scanf("%d",&deldata); if(deldata == -1) break; if(btree_delete(&root,deldata) == -1) { puts("删除失败请重试..."); continue; } puts("====先序遍历===="); Preorder(root); printf("\n"); } btree_destroy(&root); return 0; }
puts(“中序遍历”);
Midorder(root);
printf(“\n”);
puts(“后序遍历”);
Postorder(root);
printf(“\n”);
puts(“层序遍历”);
Levelorder(root);
printf(“\n”);
data_t deldata = 0;
while(1)
{
printf(“请输入要删除的数据(-1退出):”);
scanf(“%d”,&deldata);
if(deldata == -1)
break;
if(btree_delete(&root,deldata) == -1)
{
puts(“删除失败请重试…”);
continue;
}
puts(“先序遍历”);
Preorder(root);
printf(“\n”);
}
btree_destroy(&root);
return 0;
}
``` -