目录
一、树的基本概念:
树(Tree)是n(n>=0)个结点的有限集,或为空树,或为非空树。
对于非空树:
- 有且只有一个称为根的结点
- 除根节点以外的其他结点可分为m(m>=0)个互不相交的有限集,T1 、T2、T3、、、Tm,其中每个结合本身的也是一颗树,并且称之为数的子根(SubTree)
-
树的其他表示方法: -
二、树的相关语句:
1、结点的度
一个结点含有的子树的个数成为该结点的度(下图表示为每个结点的度的个数)
2、叶子结点
度为0的结点称为叶子结点,也可以叫做终端结点,叶子结点没有直接后继
3、分支结点
度不为零的结点叫做分支结点,也可以叫做非终端结点
4、结点的层次
从根节点开始,结点的层次为1,根的直接后继层次为2依次类推
5、树的度
树中所有结点的度的最大值(以上图为例树的度=3)。
6、树的高度
树中结点的最大层次
7、森林
m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根结点,森林就变成一棵树。
8、孩子结点
一个结点的直接后继结点成为该结点的孩子结点。
9、双亲结点
一个结点的直接前驱结点称为该结点的双亲结点。
10、兄弟结点
同一个双亲结点的孩子结点之间互相称为兄弟结点
三、二叉树:
1、二叉树的概念:
二叉树就是度不超过2的树(每个结点最多有两个子节点)
2、二叉树的基本性质
- 在二叉树的第i层最多有2^(i-1)个结点
- 深度为K的二叉树最多有2^k - 1个结点(2^0+2^1+2^2……2^(k-1)),最少有k个结点
3、特殊形态的二叉树:
3.1:满二叉树: 一个二叉树,每层的结点树都达到了最大值,则这个二叉树就是满二叉树。深度 为K 的 满二叉树有2^k-1个结点
3.2:完全二叉树:叶结点只能出现在最下层和次下层,并且最下面一层的结点都集中在最左层的若干位置的二叉树。
特点:1、叶子结点只能出现在最下边两层。
2、最下层的叶子一定集中在左部连续位置。
3、倒数第二层如果有叶子一定都在右部连续位置。
4、同样结点树的二叉树,完全二叉树的深度最小。
区分:“完全”和“满”的差异,满二叉树一定会是一颗完全二叉树,但是完全二叉树不一定是满二叉树
4、二叉树的遍历
在对于二叉树元素进行访问、插入、删除等操作的时候、我们需要对二叉树进行遍历,所谓遍历就是指按照某条搜索路径遍访每个结点,并且不重复。(又称周游)。
对二叉树的遍历可以有两种思路:
- 广度遍历:按层次遍历
- 深度遍历:
- 前序(先序)遍历:根结点--->左子树--->右子树
- 中序遍历:左子树--->根结点--->右子树(升序排列)
- 后序遍历:左子树 ---> 右子树 ---> 根结点
5、二分插找树
1、概念:
二分查找树BST(也叫二分查找树,二叉排序树)的提出是为了提供查找效率,之所以称之为二分查找树,是因为该二叉树对应着二分查找算法,查找平均的时间复杂度为O(logn),所以该数据结构的提出是为了提高查找效率。
2、二分查找树的性质
二分查找树具有的性质:
- 若他的左子树不为空,则左子树上所有结点的值小于根结点的值
- 若他的右子树不为空,则右子树上所有结点的值小于根结点的值
- 它的左右子树均为二分查找树
3、二分查找树的插入
- 遍历规则:
- 1、从根结点开始遍历
- 2、如果比遍历到的结点大,遍历该结点的右子树如果右子树为空则将新的结点作为该结点的右子树
- 3、如果比遍历到的结点小,则遍历该结点的左子树,如果左子树为空则将新的结点作为该结点的左子树
- 例子:将4插入到二分查找树中
思路:先从根结点开始遍历,因为4<9所以进入左子树(5结点)开始遍历,又因为4<5所以进入左子树(2结点)开始遍历,因为4>2所以进入右子树(3结点)开始遍历,因为4>3所以进入左子树开始遍历,然后因为3结点为叶子结点所以4就位于3结点的右子树上
4、二分查找树结点的删除
- 如果删除的结点是叶子结点,直接删除。
- 有一个分支的,删除结点,子节点上提。
先找到需要删除的结点,将该结点删除。
不论需要删除的结点有左子树还是右子树,该结点上提后都应该删除删除的结点的父节点的左子树
- 两个分支,结点删除,找到右子树的最小子树替换删除的结点
5、实现二叉树的结点描述
- 二叉树的结点至少有三个域:一个数据域,两个指针域分别指向该结点的左孩子结点和右孩子结点
5.1、定义二叉树的一个结点:
typedef int ELemType ;
//描述二分查找树的一个结构
typedef struct Node
{
ELemType data;//数据域
struct Node *LChild;//左指针
struct Node *RChild;//右指针1
}Node;
5.2、定义一个二叉树
//描述一颗二分查找树
typedef struct Tree
{
Node *root;//定义一个指向二分查找树根节点的指针
}Tree;
5.3、二分查找树的创建
/* @brief 初始化一颗二叉树
* @return 返回描述一颗二叉树的结构体指针
* */
Tree *tree_Init()
{
Tree *t; //定义一个树
t = (Tree *)malloc(sizeof(Tree));//在堆上申请树的空间
t->root = NULL;//树的根指针先指向NULL
return t;
}
5.4、创建一个结点
/* @brief 创建一个结点
* @param data 结点上数据域的值
* @return 返回指向创建好的结点的指针
* */
Node *node_create(ELemType data)
{
Node *node;
node = (Node *)malloc(sizeof(Node));
node->data=data;
node->LChild=NULL;
node->RChild=NULL;
return node;
}
5.5、二分查找树的初始化
/* @brief 向树中插入一个结点,使这棵树成为一颗二分查找树,(前提插入该结点之前就已经使一颗二分查找树)
* @param root 二叉树根节点指针(root为指向根地址的指针,此处这样写的目的是为了插入根节点的时候(*root指向的位置发生了变化不在使NULL))
* @param data 插入到结点上数据域的值
* @return 成功返回TRUE,失败返回FALSE
* */
int bst_Tree(Node **root,ELemType data)
{
if(NULL==root)
return FALSE;
//先判断树是否为一颗空树
if(NULL==*root)
{
//新插入的结点就是根节点
Node *t;
t=node_create(data);
//然后要把该结点传给二叉树
*root=t;
return TRUE;//也是下边递归退出的条件
}
#if REC //递归的方式实现二分查找树结点的插入。在实际中并不推荐使用该方法,效率低
//从根节点开始遍历,如果插入的结点比根节点小,插入左子树
if(data<(*root)->data)
{
//运用递归连续传入
bst_Tree(&((*root)->LChild),data);//因为该函数的第一个入口参数为一个二级指针
}
//如果插入的结点比根节点大,插入右子树
else if (data>(*root)->data)
{
bst_Tree(&((*root)->RChild),data);//因为该函数的第一个入口参数为一个二级指针
}
//如果与根节点相等,不插入
else
return FALSE;
#endif
#if NO_REC //非递归的方式实现二分查找树结点的插入
Node *tmp = *root;//定义一个指向根节点的指针
//遍历到叶子结点
while (tmp->LChild!=NULL && tmp->RChild!=NULL)
{
//如果插入的结点比根节点大,遍历右子树
if(data>tmp->data)
{
tmp = tmp->RChild;//通过tmp临时指针遍历
}
//如果插入的结点比根节点小,遍历左子树
else if(data<tmp->data)
{
tmp = tmp->LChild;
} else{
return FALSE;
}
}
Node *node;
node = node_create(data);
//将新的结点作为叶子结点的子树
if(data>tmp->data)
{
//作为右结点
tmp->RChild=node;
} else if(data<tmp->data){
tmp->LChild=node;
} else{//相等不插入
return FALSE;
}
#endif
return TRUE;
}
此处一共使用了俩个中方式,1、递归法,2非递归法。
5.6、二叉分找树的遍历:
先序遍历:
/* @brief 二分查找树的遍历(先序遍历)根左右 * @param root 结点上数据域的值 * @return 返回指向创建好的结点的指针 * */ int pre_traversal(Node *root) { #if REC //递归法 if(root == NULL) { return FALSE; } printf("%d ",root->data); pre_traversal(root->LChild);//左子树 pre_traversal(root->RChild);//右子树 return TRUE; #endif #if NO_REC //非递归法 按照深度优先的遍历二叉树上所有的点 思想:使用栈经行存储 if(root == NULL) { return FALSE; } SqStack *s=Stastack_Init(size_init_len);//初始化一个栈 Node *tmp = root;//定义临时指针遍历二叉树 while (1) { //如果栈为空,并且出栈的结点和左子树和右子树都全部为空,结束遍历 if(tmp->RChild == NULL && tmp->LChild==NULL && is_empty(s)){ printf("%d ",tmp->data); break; } //先将跟结点的数据域打印出来 printf("%d ",tmp->data); //将结点的右子树入栈 if(tmp->RChild != NULL) push(s,tmp->RChild); //将结点的左子树入栈 if(tmp->LChild !=NULL) push(s,tmp->LChild); //出栈 pop(s,&tmp); } #endif }
这里的非递归法用到了栈的知识,可以参考前边的文章
中序遍历:
/* @brief 二分查找树的遍历(中序遍历)左根右 * @param root 结点上数据域的值 * @return 返回指向创建好的结点的指针 * */ int mid_traversal(Node *root) { #if REC//递归法 if(root == NULL) return FALSE; mid_traversal(root->LChild); printf("%d ",root->data); mid_traversal(root->RChild); return TRUE; #endif #if NO_REC //非递归法 if(root == NULL) { return FALSE; } SqStack *s=Stastack_Init(size_init_len);//初始化一个栈 Node *tmp = root;//定义临时指针遍历二叉树 while (1) { //一直往左边遍历,直到遍历到的结点的左子树为不空 while (tmp) { push(s,tmp);//入栈 tmp=tmp->LChild; } if(is_empty(s)!=TRUE) { //出栈 pop(s,&tmp); printf("%d ",tmp->data); tmp = tmp->RChild; } else{ break; } } // printf("\n"); #endif }
后续遍历:
/* @brief 二分查找树的遍历(后序遍历)左右根 * @param root 结点上数据域的值 * @return 返回指向创建好的结点的指针 * */ int tail_traversal(Node *root) { #if 1 //递归法 if(root == NULL) return FALSE; tail_traversal(root->LChild); tail_traversal(root->RChild); printf("%d ",root->data); return TRUE; #endif }
5.7查找二分叉找树的最大值和最小值
/* @brief 查找二分查找树的最小值 * @param root 指向左右子树的指针 * @return 返回指向创建好的结点的指针 * */ int min_search(Node *root) { if(root->LChild == NULL) { printf("\nmin=%d",root->data); return FALSE; } min_search(root->LChild); } /* @brief 查找二分查找树的最大值 * @param root 树的根节点的指针 * @return 返回指向创建好的结点的指针 * */ int max_search(Node *root) { if(NULL == root) { return FALSE; } if(root->RChild == NULL) { printf("\nmax=%d",root->data); return TRUE; } max_search(root->RChild); }
根据二分查找树的性质,最左子树最小,最右子树最大
5.8查找指定直接点
/* @brief 返回二分查找树上指定值所在的结点 * @param root 树的根节点的指针 * @param data 需要查找的结点数值 * @return 返回指向创建好的结点的指针 * */ Node *find_elem(Node *root,ELemType data) { #if REC //递归法 //如果找不到 if(NULL == root) { printf("\nThere is no node!!!"); return NULL; } if(root->data == data) { printf("\nThere is node!!!==%d",root->data); return root; } if(data>root->data) { find_elem(root->RChild,data); } else if(data<root->data) { find_elem(root->LChild,data); } #endif #if NO_REC //非递归方法 if(NULL==root) return NULL; Node *tmp = root; while (1) { //从结点开始遍历,如果比遍历到的结点小 if(data<tmp->data) { if(tmp->LChild == NULL)//if该结点没有左子树,认为结点不在树上 { printf("\n[%s %d] can not find element:%d",__FUNCTION__ ,__LINE__,data); return NULL; } else{//如果比遍历到的结点并且该结点有左子树,继续遍历左子树 tmp = tmp->LChild; continue; } } else if(data>tmp->data){ if(tmp->RChild == NULL){ printf("\n[%s %d] can not find element:%d",__FUNCTION__ ,__LINE__,data); return NULL; } else{//如果比遍历到的结点并且该结点有右子树,继续遍历右子树 tmp = tmp->RChild; continue; } } else{//如果根遍历到的结点相同则找到 return tmp; } } #endif }
思路:通过比大小,比节点大的进入右子树,比节点小的进入左子树
整体代码:
#include <stdio.h> #include <stdlib.h> #define FALSE 0 #define TRUE 1 #define REC 0 #define NO_REC 1 typedef int ELemType ; //描述二分查找树的一个结构 typedef struct Node { ELemType data;//数据域 struct Node *LChild;//左指针 struct Node *RChild;//右指针1 }Node; //描述一颗二分查找树 typedef struct Tree { Node *root;//指向二分查找树根节点的指针 }Tree; typedef Node *ElemType ; typedef unsigned int uint; /*******************顺序栈************************/ #define size_init_len 10 #define expand_num 20 //用于对栈的扩增 //顺序栈描述结构体 typedef struct SqStack{ ElemType *top;//栈顶指针 ElemType *base;//栈底指针,指向栈空间的首地址 uint stacklen;//栈能够存储的最大元素个数 uint len;//实际存储的元素的个数 }SqStack; //打印栈内元素 void printf_Sqs(SqStack *s) { if (NULL == s) { printf("[%s %d] stack pointer is NULL ...\n", __FUNCTION__ , __LINE__); } ElemType *num=s->top; while(num!=s->base) { num--; printf("%d ",*num); } printf("%d ",*num); //判断栈是否为空 if(s->top==s->base) { printf("[%s %d] stack pointer is NULL ...\n", __FUNCTION__ , __LINE__); } } //初始化 /* * @brief 初始化一个顺序链表 * @param 初始顺序栈长度 * @return 返回初始化后的栈的指针 * */ SqStack * Stastack_Init(uint size) { //创建一个栈 SqStack *s=(SqStack *)malloc(sizeof(SqStack)); //栈分配存储空间 s->base =(ElemType *)malloc(size * sizeof(ElemType)); //指向栈空间的首地址 s->top = s->base; s->stacklen = size; s->len=0; return s; } /* * @brief 对栈进行扩容 * @param s 需要扩容的栈的指针 * @return 成功返回TRUE,失败返回FALSE * */ int expand(SqStack *s) { printf("[%s %d] SqStack expand....\n",__FUNCTION__ ,__LINE__); //先判断这个栈是否为空 if (NULL == s) { printf("[%s %d] stack pointer is NULL ...\n", __FUNCTION__ , __LINE__); return FALSE; } //为栈的存储空间重新分配空间 s->base=(ElemType*)realloc(s->base,(s->stacklen + expand_num)*sizeof(ElemType)); s->top = s->base + s->stacklen; s->stacklen += expand_num; return TRUE; } /* * @brief 入栈 * * @param s 栈指针 * * @param data 需要入栈的元素 * * @return 成功返回TRUE, 失败返回FALSE * */ int push(SqStack *s,ElemType data) { if (NULL == s) { printf("[%s %d] stack pointer is NULL ...\n", __FUNCTION__ , __LINE__); return FALSE; } //如果栈满了 if (s->top - s->base >= s->stacklen) expand(s); *(s->top) = data; s->top++; return TRUE; } /* * @brief 出栈 * * @param s 栈指针 * * @param data 存放栈顶元素的指针 * * @return 成功返回TRUE, 失败返回FALSE * */ int pop(SqStack *s,ElemType * data) { if (NULL == s|| NULL==data) { printf("[%s %d] stack pointer is NULL ...\n", __FUNCTION__ , __LINE__); return FALSE; } //先让栈顶指针--,指向栈顶的元素 s->top--; *data = *(s->top); } int is_empty(SqStack *s) { if(s==NULL) return FALSE; if(s->base==s->top) { return TRUE; } else{ return FALSE; } } /**********************顺序栈*******************************/ /* @brief 初始化一颗二叉树 * @return 返回描述一颗二叉树的结构体指针 * */ Tree *tree_Init() { Tree *t; t = (Tree *)malloc(sizeof(Tree)); t->root = NULL;//指向二分查找树根节点的指针赋值为NULL return t; } /* @brief 创建一个结点 * @param data 结点上数据域的值 * @return 返回指向创建好的结点的指针 * */ Node *node_create(ELemType data) { Node *node; node = (Node *)malloc(sizeof(Node)); node->data=data; node->LChild=NULL; node->RChild=NULL; return node; } /* @brief 向树中插入一个结点,使这棵树成为一颗二分查找树,(前提插入该结点之前就已经使一颗二分查找树) * @param root 二叉树根节点指针 * @param data 插入到结点上数据域的值 * @return 成功返回TRUE,失败返回FALSE * *///递归的方式实现二分查找树结点的插入 int bst_Tree(Node **root,ELemType data) { if(NULL==root) return FALSE; //先判断树是否为一颗空树 if(NULL==*root) { //新插入的结点就是根节点 Node *t; t=node_create(data); //然后要把该结点传给二叉树 *root=t; return TRUE;//也是下边递归退出的条件 } #if REC //递归的方式实现二分查找树结点的插入。在实际中并不推荐使用该方法,效率低 //从根节点开始遍历,如果插入的结点比根节点小,插入左子树 if(data<(*root)->data) { //运用递归连续传入 bst_Tree(&((*root)->LChild),data);//因为该函数的第一个入口参数为一个二级指针 } //如果插入的结点比根节点大,插入右子树 else if (data>(*root)->data) { bst_Tree(&((*root)->RChild),data);//因为该函数的第一个入口参数为一个二级指针 } //如果与根节点相等,不插入 else return FALSE; #endif #if NO_REC //非递归的方式实现二分查找树结点的插入 Node *tmp = *root;//定义一个指向根节点的指针 //遍历到叶子结点 while (tmp->LChild!=NULL && tmp->RChild!=NULL) { //如果插入的结点比根节点大,遍历右子树 if(data>tmp->data) { tmp = tmp->RChild;//通过tmp临时指针遍历 } //如果插入的结点比根节点小,遍历左子树 else if(data<tmp->data) { tmp = tmp->LChild; } else{ return FALSE; } } Node *node; node = node_create(data); //将新的结点作为叶子结点的子树 if(data>tmp->data) { //作为右结点 tmp->RChild=node; } else if(data<tmp->data){ tmp->LChild=node; } else{//相等不插入 return FALSE; } #endif return TRUE; } /* @brief 二分查找树的遍历(先序遍历)根左右 * @param root 结点上数据域的值 * @return 返回指向创建好的结点的指针 * */ int pre_traversal(Node *root) { #if REC //递归法 if(root == NULL) { return FALSE; } printf("%d ",root->data); pre_traversal(root->LChild);//左子树 pre_traversal(root->RChild);//右子树 return TRUE; #endif #if NO_REC //非递归法 按照深度优先的遍历二叉树上所有的点 思想:使用栈经行存储 if(root == NULL) { return FALSE; } SqStack *s=Stastack_Init(size_init_len);//初始化一个栈 Node *tmp = root;//定义临时指针遍历二叉树 while (1) { //如果栈为空,并且出栈的结点和左子树和右子树都全部为空,结束遍历 if(tmp->RChild == NULL && tmp->LChild==NULL && is_empty(s)){ printf("%d ",tmp->data); break; } //先将跟结点的数据域打印出来 printf("%d ",tmp->data); //将结点的右子树入栈 if(tmp->RChild != NULL) push(s,tmp->RChild); //将结点的左子树入栈 if(tmp->LChild !=NULL) push(s,tmp->LChild); //出栈 pop(s,&tmp); } #endif } /* @brief 二分查找树的遍历(中序遍历)左根右 * @param root 结点上数据域的值 * @return 返回指向创建好的结点的指针 * */ int mid_traversal(Node *root) { #if REC//递归法 if(root == NULL) return FALSE; mid_traversal(root->LChild); printf("%d ",root->data); mid_traversal(root->RChild); return TRUE; #endif #if NO_REC //非递归法 if(root == NULL) { return FALSE; } SqStack *s=Stastack_Init(size_init_len);//初始化一个栈 Node *tmp = root;//定义临时指针遍历二叉树 while (1) { //一直往左边遍历,直到遍历到的结点的左子树为不空 while (tmp) { push(s,tmp);//入栈 tmp=tmp->LChild; } if(is_empty(s)!=TRUE) { //出栈 pop(s,&tmp); printf("%d ",tmp->data); tmp = tmp->RChild; } else{ break; } } // printf("\n"); #endif } /* @brief 二分查找树的遍历(后序遍历)左右根 * @param root 结点上数据域的值 * @return 返回指向创建好的结点的指针 * */ int tail_traversal(Node *root) { #if 1 //递归法 if(root == NULL) return FALSE; tail_traversal(root->LChild); tail_traversal(root->RChild); printf("%d ",root->data); return TRUE; #endif #if 0//非递归法 if(root == NULL) { return FALSE; } SqStack *s=Stastack_Init(size_init_len);//初始化一个栈 Node *tmp = root;//定义临时指针遍历二叉树 while (1) { //一直往左边遍历,直到遍历到的结点的左子树为不空 while (tmp) { push(s,tmp);//入栈 tmp=tmp->LChild; } if(is_empty(s)!=TRUE) { pop(s,&tmp);//出栈 // printf("%d ",tmp->data); tmp = tmp->RChild; push(s,tmp);//入栈 } //一直往左边遍历,直到遍历到的结点的左子树为不空 while (tmp) { push(s,tmp);//入栈 tmp=tmp->LChild; } if(is_empty(s)!=TRUE) { pop(s,&tmp);//出栈 printf("%d ",tmp->data); tmp = tmp->RChild; } else{ break; } } printf("\n"); #endif } /* @brief 查找二分查找树的最小值 * @param root 指向左右子树的指针 * @return 返回指向创建好的结点的指针 * */ int min_search(Node *root) { if(root->LChild == NULL) { printf("\nmin=%d",root->data); return FALSE; } min_search(root->LChild); } /* @brief 查找二分查找树的最大值 * @param root 树的根节点的指针 * @return 返回指向创建好的结点的指针 * */ int max_search(Node *root) { if(NULL == root) { return FALSE; } if(root->RChild == NULL) { printf("\nmax=%d",root->data); return TRUE; } max_search(root->RChild); } /* @brief 返回二分查找树上指定值所在的结点 * @param root 树的根节点的指针 * @param data 需要查找的结点数值 * @return 返回指向创建好的结点的指针 * */ Node *find_elem(Node *root,ELemType data) { #if REC //递归法 //如果找不到 if(NULL == root) { printf("\nThere is no node!!!"); return NULL; } if(root->data == data) { printf("\nThere is node!!!==%d",root->data); return root; } if(data>root->data) { find_elem(root->RChild,data); } else if(data<root->data) { find_elem(root->LChild,data); } #endif #if NO_REC //非递归方法 if(NULL==root) return NULL; Node *tmp = root; while (1) { //从结点开始遍历,如果比遍历到的结点小 if(data<tmp->data) { if(tmp->LChild == NULL)//if该结点没有左子树,认为结点不在树上 { printf("\n[%s %d] can not find element:%d",__FUNCTION__ ,__LINE__,data); return NULL; } else{//如果比遍历到的结点并且该结点有左子树,继续遍历左子树 tmp = tmp->LChild; continue; } } else if(data>tmp->data){ if(tmp->RChild == NULL){ printf("\n[%s %d] can not find element:%d",__FUNCTION__ ,__LINE__,data); return NULL; } else{//如果比遍历到的结点并且该结点有右子树,继续遍历右子树 tmp = tmp->RChild; continue; } } else{//如果根遍历到的结点相同则找到 return tmp; } } #endif } int main() { //初始化一颗二分查找树 Tree *t; t=tree_Init(); bst_Tree(&(t->root),9); bst_Tree(&(t->root),5); bst_Tree(&(t->root),13); bst_Tree(&(t->root),2); bst_Tree(&(t->root),7); bst_Tree(&(t->root),12); bst_Tree(&(t->root),15); bst_Tree(&(t->root),14); bst_Tree(&(t->root),4); pre_traversal(t->root);//先序遍历 printf("\n"); mid_traversal(t->root);//中序遍历 printf("\n"); tail_traversal(t->root);//后续遍历 printf("\n"); min_search(t->root);//查找最小值 max_search(t->root);//查找最大值 Node *n; n=find_elem(t->root,14); printf("\nfind=%d",n->data); return 0; }
二叉树图:
运行结果: