写在前面
- 本以为这二叉树有什么难的,无非就是几个定义,然后把前中后序遍历搞清楚就行了,然后还有几个满二叉树,完全二叉树的定义。在老师持续输出了几天后发现脑子根本跟不上,二叉树难的是用非递归实现,难的是用学过的栈、队列,与二叉树联合起来,用不同的方法实现算法。老师把我按在地上摩擦,不写点笔记复习一下真的懂不了。
二叉树的定义
二叉树由左子树,右子树以及数据构成,每一层节点数达到最大值称为满二叉树,最后一层从右向左依次缺省节点的树称为完全二叉树。
二叉树的定义:
typedef char ElemType;
typedef struct BTNode{
struct BTNode* leftchild;
struct BTNode* rightchild;
ElemType data;
}BTNode, *BinaryTree;
购买节点:
BTNode* BuyNode(ElemType val){
BTNode* s = (BTNode*)malloc(sizeof(BTNode));
if(s == NULL) exit(EXIT_FAILURE);
s->data = val;
s->leftchild = NULL;
s->rightchild = NULL;
return s;
}
释放函数:
void FreeNode(BTNode* p){
free(p);
}
二叉树节点个数:
可以用递归找左子树与右子树个数,再加上根节点
int GetSize(BTNode* p){
if(p == NULL) return 0;
return GetSize(p->leftchild) +
GetSize(p->rightchild) + 1;
}
二叉树高度:
与查询节点个数相似,递归查找左子树右子树的高度最大值
需要自己写一个找最大值的函数
int Max(int a,int b){
return a>b?a:b;
}
int GetDepth(BTNode* p){
if(p == NULL) return 0;
return Max(GetDepth(p->leftchild),GetDepth(p->rightchild)) + 1;
}
创建二叉树:
手动输入字符串创建二叉树,#表示NULL
ch不等于’#'就赋值给s,递归赋值左子树右子树
BTNode* CreateBinatyTree(){
ElemType ch;
BTNode* s=NULL;
scanf("%c",&ch);
if(ch!='#'){
s = BuyNode(ch);
s->leftchild = CreateBinaryTree();
s->rightchild = CreateBinaryTree();
}
return s;
}
这里使用了前序遍历测试
创建二叉树2:
不输入,在主函数中定义p(这里是老师第一次摩擦我,指针花活绕死我了,用的应该是c++中的知识,clion不让俩语言一块用)
BTNode* CreateBinaryTree(const char** p){
BTNode* s=NULL;
if(p!=NULL && *p!=NULL && **p!='#'){
s= BuyNode(**p);
s->leftchild = CreateBinaryTree(&++*p);
s->rightchild = CreateBinaryTree(&++*p);
}
return s;
}
以上两种方法都是告诉了NULL,下面为只有data创建二叉树
利用前序中序排序字符串创建二叉树(PI):
前序的第一个字符为根节点,在中序中,根节点左边的全是左子树,右边的全为右子树。利用这一逻辑创建二叉树。
首先要找到根节点的下标位置
int FindIs(const char* is,char ps,int n){
assert(is!=NULL);
for(int i=0;i<n;++i){
if(ps = is[i]) return i;
}
return -1;
}//is为中序排序,ps为根节点字符,n为字符串长度,
//用i返回根节点在is中的下标
BTNode* CreatePI(const char* ps,const char* is,int n){
BTNode* s = NULL;
if(n>0){
s = BuyNode(ps[0]);//前序第一位为根节点
int pos = FindIs(is,ps[0],n);//找is中根节点的下标
if(-1 == pos) exit(EXIT_FAILURE);
//递归写出左子树与右子树
//左子树前序排序从第二个节点开始pos个,中序从0开始pos个
s->leftchild = CreatePI(ps+1,is,pos);
//右子树前序排序从pos+1节点开始n-pos-1个
// 中序排序从pos+1节点开始n-pos-1个
s->rightchild = CreatPI(ps+pos+1,is+pos+1,n-pos-1);
}
return s;
}
BTNode* CreateBinaryTreePI(const char* ps,const char* is){
assert(ps!=NULL && is!=null);
int n = strlen(ps);
return CreatePI(ps,is,n);
}
利用后序遍历检验,运行成功。
利用中序后序排序字符串创建二叉树(IL)
后序排序最后一个字符为根节点,在中序排序中找到根节点的下标pos,左子树的中序排序为0~pos,后序排序为0~pos
右子树的中序排序为pos+1 ~ n-pos-1,后序排序为pos ~n-pos-1
BTNode* CreateIL(const char* is,const char* ls,int n){
BTNode* s=NULL;
if(n>0){
s = BuyNode(ls[n-1]);
int pos = FindIs(is,ls[n-1],n);
if(-1 == NULL) exit(EXIT_FAILURE);
s->leftchild = CreateIL(is,ps,pos);
s->rightchild = CreateIL(is+pos+1,ls+pos,n-pos-1);
}
return s;
}
BTNode* CreateBinaryTreeIL(const char* is,const char* ls){
assert(is!=NULL&&ls!=NULL);
int n=strlen(is);
return CreateIL(is,ls,n);
}
利用前序遍历检验
前中后序,层次遍历
递归遍历比较简单,根据逻辑写代码就行。
前序遍历:
使用递归实现,逻辑即为“根左右”,访问到根的时候就打印,然后递归遍历左子树,右子树
void PreOrder(BTNode* p){
if(p != NULL){
printf("%c",p->data);
PreOrder(p->leftchild);
PreOrder(p->rightchild);
}
}
中序遍历:
逻辑为“左根右”,到根的时候打印,否则递归
void InOrder(BTNode* p){
if(p != NULL){
InOrder(p->leftchild);
printf("%c",p->data);
InOrder(p->rightchild);
}
}
后序遍历:
逻辑为“左右根”,到根的时候打印,否则递归
void PastOrder(BTNode* p){
if(p!=NULL){
PastOrder(p->leftchild);
PastOrder(p->rightchild);
printf("%c",p->data);
}
}
层次遍历较为复杂,老师用到了队列
逻辑顺序为:
1.将根节点先入队。
2.队头出队打印,并且访问左右节点,非空便依次入队
void NiceLevelOrder(BTNode* ptr){
if(ptr == NULL) return;
std::queue<BTNode*> qu;
qu.push(ptr);
while (!qu.empty())//队列不为空就循环
{
ptr = qu.front(); qu.pop();//队头出列并打印
printf("%c ", ptr->data);
if (ptr->leftchild != nullptr)
{//左子树不为空就入队
qu.push(ptr->leftchild);
}
if (ptr->rightchild != nullptr)
{//右子树不为空就入队
qu.push(ptr->rightchild);
}
}
printf("\n");
}
非递归遍历
这里用到了栈
非递归中序遍历
当树不为空时,向栈中传入数据,树指向左子树。当树空时,即为没有了左子树,出栈并且打印,此时打印的就是最下面的左子树。然后将树ptr赋值为右子树。逻辑便是左根右。
void NiceInOrder(BTNode* ptr){//非递归中序遍历
if(ptr == NULL) return;
LinkStack mys;
InitStack(&mys);
while(ptr!=NULL || !StackEmpty(&mys)) {
while (ptr != NULL) {
Push(&mys, ptr->data);//左子树不为空就入栈
ptr = ptr->leftchild;//往左子树走
}
Pop(&mys, &ptr);//左子树为空时出栈
printf("%c", ptr->data);//出栈并打印
ptr = ptr->rightchild;//根有右子树就赋值,没有右子树就下一个循环
}
DestroyStack(&mys);
}
非递归后序遍历
与中序遍历不同,中序遍历一直往左走就可以了,但是后序遍历需要考虑右子树,这里用到了tag标记,还不是很明白,再去看看回放。
void StkNicePastOrder(BTNode* ptr)
{
if (ptr == NULL) return;
BTNode* tag = NULL;
LinkStack mys;
InitStack(&mys);
SElemType e, x = { ptr,0 };
Push(&mys, x);
while (!StackEmpty(&mys))
{
Pop(&mys, &e);
if (++e.popnum == 3)
{
printf("%c ", e.pnode->data);
}
else
{
Push(&mys, e);
if (e.popnum == 1 && e.pnode->leftchild != nullptr)
{
SElemType left = { e.pnode->leftchild,0 };
Push(&mys, left);
}
else if (e.popnum == 2 && e.pnode->rightchild != nullptr)
{
SElemType right = { e.pnode->rightchild , 0 };
Push(&mys, right);
}
}
}
printf("\n");
DestroyStack(&mys);
}
查询
二叉树查找某一个值
这个较为简单,利用递归,根节点不对就去找左子树,左子树为空就找右子树
BTNode* FindValue(BTNode* ptr,elemType val){
if(ptr == NULL || ptr->data == val) return ptr;
else {
BTNode* p = FindValue(ptr->leftchild,val);
if(p == NULL){
p=FindValue(ptr->rightchild,val);
}else{
return p;
}
}
}
二叉树找双亲
与查询值相似,递归即可
BTNode* FindParent(BTNode* ptr,elemType child){
if(ptr == NULL || ptr->leftchild == child || ptr->rightchild == child){
return ptr;
}else{
BTNode* p = FindParent(ptr->leftchild,child);
if(p == NULL){
p = FindParent(ptr->rightchild,child);
}else return p;
}
}
二叉树查找最近公共祖先
二叉搜索树(BST树)
二叉搜索树的定义:
左子树都小于根节点的值
右子树都大于根节点的值
这一性质使BST树的查询速度要由于链表查询,要么比val大要么比val小
但当数组极端情况下,也就是数组有序时,二叉搜索树就退化成一个仅有右子树或左子树的树,类似于链表。后文将讲述优化方法。
BST树的定义
与二叉树定义相同,这里的数据类型改为int,加入双亲节点parent
typedef int ElemType;
typedef struct BstNode //BinarySortTreeNode
{
struct BstNode* leftchild;
struct BstNode* parent;
struct BstNode* rightchild;
ElemType data;
}BstNode, * PBstNode;
typedef struct
{
BstNode* root;
int cursize;
}BinarySortTree;
BstNode* Buynode(ElemType val, BstNode* Parg = nullptr,
BstNode* Larg = nullptr, BstNode* Rarg = nullptr);//这里不再赘述
void Freenode(BstNode* p);
void InitBSTree(BinarySortTree * ptree);
void DestroyBSTree(BinarySortTree* ptree);;
BST树查询
利用递归,val的值大于当前节点或小于当前节点,或者就等于当前节点的值。类似于中序遍历。
BstNode* SearchValue(BstNode* ptr, ElemType val)
{
BstNode* p = nullptr;
if (nullptr == ptr || val == ptr->data)
{
p = ptr;
}
else if (val < ptr->data)
{
p = SearchValue(ptr->leftchild, val);
}
else
{
p = SearchValue(ptr->rightchild, val);
}
return p;//用指针p返回
}
插入节点
1.如果树为空,就直接插入,cursize+1.
2.找到应该插入的位置,左子树或者右子树。
3.找到应插入的节点后,判断应该插在左边或者右边
bool Insert(BinarySortTree * proot, ElemType val)
{
if (nullptr == *proot)
{
*proot = Buynode(val);
return true;
}//二叉树为空直接插入
BstNode* pa = nullptr;//定义一个节点标记应该插入的节点
BstNode* p = *proot;
while (p != nullptr && p->data != val)
{
pa = p;
p = val < p->data ? p->leftchild : p->rightchild;
}//val小就往左走,大就往右走。
//当p为空时,也就是查询到了应插入的位置,并且pa标记了
if (p != nullptr) return false;//p不为空表示没有地方可以插入。
p = Buynode(val, pa);//在pa处购买节点,值为val
if (p->data < pa->data)
{
pa->leftchild = p;
}//val小于pa->data就插在左边
else
{
pa->rightchild = p;
}//val大于pa->data就插在右边
return true;
}
删除节点
1.如果树为空或者找不到要删除的值,就返回false
2.
在这里插入代码片
AVL树
前面讲过,如果二叉搜索树遇到了极端情况,也就是数组本身就有序,做成BST树就会退化成一个链表,查询时间大大增加。因此需要优化,让我们制作BST树时,能让数值均匀的分布在二叉树的左右。
数据结构|平衡二叉搜索树(AVL树)
红黑树(RBTree)
未完待续,持续更新