二叉树操作总结

storage.h

#ifndef STORAGE_H_INCLUDED
#define STORAGE_H_INCLUDED


//typedef  oldName  newName;

//二叉树的顺序存储结构
#define MaxSize1 50
typedef int SqBitree[MaxSize1];  //SqBitree bt; 从数组下表1开始存储结点,否则不匹配二叉树数学性质

//二叉树的链式存储结构(二叉链表)-介个用的多
typedef struct BitNode
{
    int data;
    struct BitNode *lchild, *rchild;
}BitNode, *Bitree;  //BitNode *p;-通常表明一个结点, Bitree T;-通常表明一个树

//*Bitree 是结构体指针!
//https://blog.csdn.net/zhaojie911272507/article/details/106261997/



//栈的顺序存储结构
typedef struct
{
    BitNode *data[MaxSize1];  //创建一个内存单位为BitNode大小的数组,共计有(MaxSize1)个
    int top;
}SqStack;  //SqStack S; 定义一个结构体变量


//链队列的存储结构-这才好用
typedef struct LinkNode
{
    BitNode *data;
    struct LinkNode *next;
}LinkNode;//定义一个结点
typedef struct
{
    LinkNode *lifront, *lirear;
}LinkQuene;//定义队头队尾的指针变量





// testTree.cpp
void createBTree(BitNode *&p);


#endif // STORAGE_H_INCLUDED

order.h

#ifndef ORDER_H_INCLUDED
#define ORDER_H_INCLUDED


//  order.cpp
void InitStack(SqStack &S);  //创
bool StackEmpty(SqStack S);  //判空
void PushStack(SqStack &S, Bitree e);  //入栈(增)
void PopStack(SqStack &s, Bitree &x );  //出栈(删)
void GetTop(SqStack S, Bitree &y);  //读取栈顶元素

void InitLiQuene(LinkQuene &Q);  //创
void EnLiQuene(LinkQuene &Q, Bitree y);
int DeLiQuene(LinkQuene &Q, Bitree &z);
int IsLiQuEmpty(LinkQuene Q);

void visit(Bitree T);  //访问函数--实际上定义为打印结点信息
void Preorder(Bitree T);  //先序遍历
void Preorder1(Bitree T);  //非递归的先序遍历
void Inorder(Bitree T);
void Inorder1(Bitree T);
void Postorder(Bitree T);
void Postorder1(Bitree T);
void Levelorder(Bitree T);   //层序遍历+

//  order.cpp 遍历的应用
void CreateBitree1(Bitree &T);  //利用先序遍历创建二叉树
void CopyBitree(Bitree T, Bitree &newT);  //复制一个二叉树
int DepthBitree1(Bitree T);  //计算二叉树的深度
int NodeCount1(Bitree T);  //统计二叉树的结点
int NodeCount_0(Bitree T);  //统计度为0的结点
int NodeCount_1(Bitree T);  //统计度为1的结点
int NodeCount_2(Bitree T);  //统计度为2的结点
#endif // ORDER_H_INCLUDED

threadbitree.h


#ifndef THREADBITREE_H_INCLUDED
#define THREADBITREE_H_INCLUDED

/*
二叉链表-含n个结点的二叉树其有n+1个空指针。[叶结点空指针2个,度为1的结点空指针1个。n=n0+n1+n2=分支总数(2n2+n1)+1,所以n0=n2+1,然后换算即可)
n+1个空指针可以被利用,在指向其左右孩子的基础上,指向其线性序列(通过遍历而得)前驱或后继
同时需引入对应标志来标识是前驱后继还是左右孩子。
线索二叉树由此而来
通过遍历将二叉链表改为二叉线索树的方式成为二叉树的线索化
*/


//线索二叉树存储结构
typedef struct ThreadNode
{
    int data;
    struct ThreadNode *lchild, *rchild;  //指向左右孩子(线索=0)或前驱后继(线索=1)的指针变量
    int ltag, rtag;  //左右线索标志
}ThreadNode, *Threadtree;

void Inthread(Threadtree &p, Threadtree &pre);
void createInthread(Threadtree &T);

ThreadNode *FirstNode(ThreadNode *p);
ThreadNode *LastNode(ThreadNode *p);
ThreadNode *NextNode(ThreadNode *p);
ThreadNode *PriorNode(ThreadNode *p);


#endif //THREADBITREE_H_INCLUDED

testTree.cpp

//原文链接:https://blog.csdn.net/weixin_45482422/article/details/108163699
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "storage.h"

/*
	二叉树的先序遍历非递归算法
	目标遍历的二叉树:
		 1
		/ \
	   2   4
	  / \
	 3   5
	待输出结果为1,2,3,5,4
	1.首先得用上面定义的结构体把这颗树表示出来
	2.表示出这颗树后在调用二叉树的先序遍历非递归算法
*/

void createBTree(Bitree &p)
{
	/*
		生成这颗树,这里就是直接按照二叉树的样子来逐一生成
		p做为根结点(root)
	*/
	p = (Bitree)malloc(sizeof(Bitree));
	p->data = 1;
	BitNode *n2, *n3, *n4, *n5 ;//分别对应存储元素2,4,3,5的4个结点
	n2 = (BitNode*)malloc(sizeof(BitNode));
	n3 = (BitNode*)malloc(sizeof(BitNode));
	n4 = (BitNode*)malloc(sizeof(BitNode));
	n5 = (BitNode*)malloc(sizeof(BitNode));
	n2->data = 2;
	n3->data = 4;
	n4->data = 3;
	n5->data = 5;
	p->lchild = n2;
	p->rchild = n3;
	n2->lchild = n4;
	n2->rchild = n5;
	n3->lchild = n3->rchild = NULL;
	n4->lchild = n4->rchild = NULL;
	n5->lchild = n5->rchild = NULL;
}

order.cpp

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "storage.h"

//实际上 访问一个结点p时,栈中结点恰好是p结点所有祖先。从栈底到栈顶结点再加上p结点,刚好构成从根节点到p结点的一条路径


//栈的操作
void InitStack(SqStack &S)
{
    S.top = -1;  //top指针始终指向栈顶(且已存放数据)位置。top=0则表示指向栈顶下一个(且未存放数据)位置
}

bool StackEmpty(SqStack S)  //栈判空
{
    if(S.top == -1)  //关注栈顶指针
        return true;
    else
        return false;
}

void PushStack(SqStack &S, Bitree e)  //入栈
{
    if(S.top >=-1 && S.top < MaxSize1 -1 )  //栈顶指针合法值=[-1,MaxSize-1].所以如这样的存储结构申请下来,一个栈占用(MaxSize+1)个int空间。入栈前若栈满,只会溢出
        S.data[++S.top] = e;  //先自增栈顶指针后入栈,确保指针指向栈顶元素
}

void PopStack(SqStack &S, Bitree &x)  //出栈
{
    if(S.top >=-1 && S.top <= MaxSize1 -1 )
        x = S.data[S.top--];  //先出栈再指针-1
}

void GetTop(SqStack S, Bitree &y)
{
    if(S.top >=-1 && S.top <= MaxSize1 -1 )
        y = S.data[S.top];

}



//队列的操作
void InitLiQuene(LinkQuene &Q)
{
    Q.lifront=Q.lirear=(LinkNode*)malloc(sizeof(LinkNode));  //生成一个结点,front 和 rear均指向该结点
    Q.lifront->next = NULL;  //此时front 和 rear 指向同一个结点
}

void EnLiQuene(LinkQuene &Q, Bitree y)
{
    LinkNode *x;
    x = (LinkNode*)malloc(sizeof(LinkNode));
    x->data = y;

    //x->next = NULL;  //王道书上有这么一句,可以写
    Q.lirear->next = x;
    Q.lirear = x;
}

int DeLiQuene(LinkQuene &Q, Bitree &z)
{
    if(Q.lifront == Q.lirear)
        return 0;  //说明队列为空
    LinkNode *p;

    p = Q.lifront->next;  //该队列有头结点!!!

    z = p->data;
    Q.lifront->next = p->next;

    if(Q.lirear == p)
        Q.lirear = Q.lifront;  //原队列中只有一个结点,删除后都指向头结点,就是空列

    free(p);
}


int IsLiQuEmpty(LinkQuene Q)
{
    if(Q.lifront == Q.lirear)
        return 1;  //队头队尾均指向一个结点头结点,说明队列空
    else
        return 0;
}



void visit(Bitree T)
{

    printf("%2d", T->data);
}


//先序遍历-根左右-递归
void Preorder(Bitree T)
{
    if(T!=NULL)
    {
        visit(T);
        Preorder(T->lchild);
        Preorder(T->rchild);
    }

}

//先序遍历-根左右-非递归(用栈的方式体现)
void Preorder1(Bitree T)
{
    SqStack S;
    InitStack(S);

    Bitree p = T;  //书上是这么写的,相对严谨一点

//注意此处的循环判断。犯错在于栈判空未取非,以至于左孩子为空的时候就会退出循环
    while(p || !StackEmpty(S))
    {
        if(p)
        {
            visit(p);
            PushStack(S, p);
            p=p->lchild;
        }
        else
        {
            PopStack(S, p);
            p=p->rchild;
        }
    }

}

void Inorder(Bitree T)
{
    if(T!=NULL)
    {

        Inorder(T->lchild);
        visit(T);
        Inorder(T->rchild);
    }
}

void Inorder1(Bitree T)
{
    SqStack S;  //局部变量
    InitStack(S);

    Bitree p = T;  //为后续函数调用少写1个*,此处声明tree指针变量。同时也是从根节点开始遍历的,所以也能够理解。

    while( p || !StackEmpty(S))
    {
        if(p)
        {
            PushStack(S, p);
            p = p->lchild;
        }
        else
        {
            PopStack(S, p);
            visit(p);
            p = p->rchild;
        }
    }
}

void Postorder(Bitree T)
{
    if(T!=NULL)
    {
        Postorder(T->lchild);
        Postorder(T->rchild);
        visit(T);
    }
}

void Postorder1(Bitree T)
{
    SqStack S;
    InitStack(S);

    Bitree p = T;
    Bitree r = NULL;

    while( p || !StackEmpty(S))
    {
        if(p)
        {
            PushStack(S, p);
            p = p->lchild;
        }
        else
        {
            GetTop(S, p);  //不能直接出栈,否则回退时,找不到该结点。因为该结点已经出栈了
            if( p->rchild && p->rchild != r )
                //访问会回退,因此只有设置另一指针变量r才能确保在进行下一次访问时,所访问结点不是上一次的。
            {
                p = p->rchild;

            }
            else
            {
                PopStack(S,p);
                visit(p);
                r = p;

                p = NULL;  //没有这个 输出将会一直是左下角第一个节点。为什么?
                /*
                左下角结点入栈后因为没有右孩子出栈,访问。然后重新开始while循环,
                此时左下角结点再一次入栈。然后循环往复
                */
            }



        }
    }
}


void Levelorder(Bitree T)
{
    //队列先进先出,一个结点最多有左右两个孩子
    LinkQuene Q;
    InitLiQuene(Q);

    EnLiQuene(Q, T);
    Bitree p;
    while(!IsLiQuEmpty(Q))
    {
        DeLiQuene(Q, p);
        visit(p);


        if(p->lchild != NULL)  //判断条件不能写!p->lchild。!IsLiQuEmpty(Q)是因为函数会根据结果返回0或1的状态
            EnLiQuene(Q, p->lchild);
        if(p->rchild != NULL)
            EnLiQuene(Q, p->rchild);

    }
}



//

orderuse.cpp

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "storage.h"


//“遍历”是二叉树各种操作的基础
/* 遍历的应用 */

/*
1.表达式    先、中、后序遍分别对应前缀、中缀、后缀表达式
2.先序遍历建立二叉链表
3.复制二叉树
4.计算二叉树的深度
5.统计二叉树中结点数量
    5.1统计二叉树中度为0的结点数
    5.2统计二叉树中度为1的结点数
    5.3统计二叉树中度为2的结点数

*/


/*
2.先序遍历建立二叉链表

算法思想:
肯定得是传递一个指向根节点的指针变量T来!
1)输入数字
2)指针变量T获取空间
3)数据域赋值
4)递归:递归生成左子树==传递当前结点的左指针进入函数(意思是使用存储当前结点的左指针变量)
5)递归:递归生成右子树

如何结束?肯定是输入特定字符就结束
1.1)加一个判断条件,当输入数字不为9999时,即可视作可输入数字,否则视为空。


*/

void CreateBitree1(Bitree &T)
{
    int da;
    printf("输入字符,9999建立空结点");

    scanf("%d", &da);

    if(da != 9999)
    {
        T = (Bitree)malloc(sizeof(BitNode));  //创建一个空间大小为BitNode型的空间,赋予地址给Bitree型结构体指针
        T->data = da;

        CreateBitree1(T->lchild);
        CreateBitree1(T->rchild);
    }
    else
        T = NULL;  //请注意这个是必须的
}


/*
3.复制二叉树

算法思想:几乎和之前一致。
生成新结点、赋值、递归

如果节点是空的怎么办!空的必须赋予NULL!

*/

void CopyBitree(Bitree T, Bitree &newT)
{

    if(T != NULL)
    {
        newT = (Bitree)malloc(sizeof(BitNode));
        newT->data = T->data;

        CopyBitree(T->lchild, newT->lchild);
        CopyBitree(T->rchild, newT->rchild);
    }
    else
        newT = NULL;  //书上还写了一个 return;

}


/*
4.计算二叉树的深度

算法思想:
递归。
个人理解:再复杂的二叉树 最后都视作一个根结点,一个左孩子和一个右孩子的三角组合。
指向他们的方式是利用递归进行

这是用后序遍历的基础上的运算,是否可以用前序遍历的方式计算呢?
*/

int DepthBitree1(Bitree T)
{
    int m = 0;
    int n = 0;
    if(T == NULL)
        return 0;

    else
    {
        m = DepthBitree1(T->lchild);
        n = DepthBitree1(T->rchild);
    }

    if(m>n)
        return (m+1);
    else
        return (n+1);
}


/*
5.统计二叉树中结点的个数
*/


int NodeCount1(Bitree T)
{
    if(T == NULL)
        return 0;
    else
        return NodeCount1(T->lchild)+NodeCount1(T->rchild)+1;
}

//https://blog.csdn.net/qq_36645322/article/details/102655196/
//这是王道书中认为比较重要的部分
//5.1.统计度为0的结点数
int NodeCount_0(Bitree T)
{
    int i = 0;

    if(T == NULL)
        return 0;

    if(T->lchild == NULL && T->rchild == NULL)
    {
        i = 1;  //因为是叶结点,在此之后不会再有结点了。在此处就只累计1个
    }
    else
        i = NodeCount_0(T->lchild) + NodeCount_0(T->rchild);  //非叶结点,则使用递归查询左右子树

    return i;
}


//5.2.统计度为1的结点数
int NodeCount_1(Bitree T)
{
    int i = 0;

    if(T == NULL)
        return 0;

    if((T->lchild == NULL && T->rchild != NULL) && (T->rchild == NULL && T->lchild != NULL))
        i = 1 + NodeCount_1(T->lchild) +NodeCount_1(T->rchild);
    else
        i = NodeCount_1(T->lchild) +NodeCount_1(T->rchild);

    return i;
}


//5.3.统计度为2的结点数
int NodeCount_2(Bitree T)
{
    int i = 0;

    if(T == NULL)
        return 0;

    if(T->lchild != NULL && T->rchild != NULL)
        i = 1 + NodeCount_2(T->lchild) +NodeCount_2(T->rchild);
    else
        i = NodeCount_2(T->lchild) +NodeCount_2(T->rchild);

    return i;
}

threadbitree.cpp

#include <iostream>
#include <stdio.h>
#include <stdlib.h>

#include "storage.h"
#include "order.h"
#include "threadbitree.h"




/*
中序线索二叉树构造
目的:利用二叉链表空余位置寻找前驱及后继
1.必有2指针,一前一后
2.每一次左右访问必有 是否有左右孩子的判断条件
3.类比于中序遍历


*/
void Inthread(Threadtree &p, Threadtree &pre)
{

    if(p!=NULL)
    {

        Inthread(p->lchild, pre);

        if(p->lchild == NULL)
        {
            p->lchild = pre;  //后序指针(pre)是前序指针(p)的前驱
            p->ltag = 1;
        }

        if(p->rchild == NULL && pre != NULL)  //没有右孩子,就是有后继
        {
            pre->rchild = p;  //先序指针是后序指针的后继
            pre->ltag = 1;

        }

        pre = p;

        Inthread(p->rchild, pre);
    }

}



//不带头结点的中序线索二叉树的构造
void createInthread(Threadtree &T)
{
    Threadtree pre = NULL;
    if(T!=NULL)
    {
        Inthread(T,pre);

        pre->rchild = NULL;
        pre->ltag = 1;
        //若额外附设头结点,其头结点被第一个结点的lchild指针(定义pre时,就指向头结点)和最后一个结点的rchild指针所指向。
    }
}

//带头结点的中序线索二叉树的构造
void createInthread1(Threadtree &T)
{
    Threadtree head;
    head = (Threadtree)malloc(sizeof(ThreadNode*));

    //头结点左指针,树空时指向自己,树非空指向根结点(视为左孩子)
    //头结点右指针,初始化时指向自己,是线索
    //后序指针pre指向头结点
    head->ltag = 0;
    head->rtag = 1;

    head->rchild = head;
    if(T == NULL)
        head->lchild = head;
    else
    {
        head->lchild = T;
        Threadtree pre = head;

        Inthread(T,pre);

        pre->rchild = head;
        pre->ltag = 1;
        head->rchild = pre;


    }


}


//求中序线索二叉树中 中序遍历序列的第一个结点
//第一个结点,左孩子一定为空
ThreadNode *FirstNode(ThreadNode *p)
{
    while(p->ltag == 0)
        p = p->lchild;  //遍历已生成的线索二叉树,一直找左孩子直到其没有左孩子位置。那么这个就是中序遍历序列的第一个节点
    return p;
}

//求中序遍历序列的最后一个结点
//最后一个结点,右孩子一定为空
ThreadNode *LastNode(ThreadNode *p)
{
    while(p->rtag == 1)
        p = p->rchild;
    return p;
}

//求结点p在中序序列下的后继
ThreadNode *NextNode(ThreadNode *p)
{
    if(p->rtag == 1)
        return p->rchild;  //如果其有后继标志,则直接返回后继结点
    else
        return FirstNode(p->rchild);  //如果其有右孩子标志,则直接返回其右子树的第一个结点
}

//求结点p在中序序列下的前驱
ThreadNode *PriorNode(ThreadNode *p)
{
    if(p->ltag == 1)
        return p->lchild;  //如果其有前驱标志,则直接返回前驱结点
    else
        return FirstNode(p->lchild);  //如果其有左孩子标志,则直接返回其左子树的第一个结点
}

main.cpp

#include <iostream>
#include <stdio.h>
#include <stdlib.h>

#include "storage.h"
#include "order.h"  //声明与遍历二叉树相关的函数
#include "threadbitree.h"



using namespace std;

int main()
{
    Bitree T;
    createBTree(T);
    int i;
    i = NodeCount1(T);
    printf("---%d",i);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值