二叉树基础操作总结

昨天的数据结构讲座,学长讲了二叉树这方面的知识,今天又复习了一下,赶紧记下来。

二叉树的定义

二叉树是n个结点的集合,可以为空,也可以由一个根结点和两棵互不相交的左,右子树组成。

二叉树的特点

1. 每个结点最多有两颗子树(是最多有)。
2. 左右子树有顺序,不能随意颠倒(哪怕其中那个结点只有一颗子树,也要区分是左子树还是右子树)。

现在让我们正式开始讲一下各种操作

先来看一张整体的效果图
在这里插入图片描述
数据结构

typedef struct node
{
	char data;
	struct node *lchild,*rchild;
}Tree;

1. 首先,我们需要一颗二叉树,所以最开始我们先来建立一个二叉树。
在这里插入图片描述我们要先得到原二叉树的扩展二叉树(将原二叉树中每个结点的空指针引出一个虚结点,给其一确定值(如"#")),然后我们就能做到一个遍历序列确定一颗二叉树了。
我们以前序建树为例,所以我们应该输入 ABD#F##E##C##。

void CreatBiTree(Tree **root)
{
	char ch;
	scanf("%c",&ch);
	if(ch == '#')
		*root = NULL;
	else
	{
		*root = (Tree *)malloc(sizeof(Tree));
		(*root)->data = ch;
		CreatBiTree(&(*root)->lchild);
		CreatBiTree(&(*root)->rchild);
	}
}

注:1. 建立二叉树也是利用递归,和递归遍历差距不大,只不过在原来打印结点的地方改成了生成结点,给结点赋值。
2. 中序,后序建树也可以,代码其实差不多,要提前知道其遍历树顺序就好。

2. 建好一颗树后,我们就来看一下遍历(递归 and 非递归)
先来说一下前序,中序,后序遍历。它们3个的递归遍历基本一样,核心就是3行代码,只不过调整一下顺序,递归比较简洁明了,但是效率却比较低(而且别人要是让你写一个二叉树的遍历,你上去直接递归感觉有点不够高级…),所以非递归的就来了,前,中,后序的非递归实现要借助栈(递归遍历也是系统调用栈来实现),可以自己来用c语言来实现一个栈,但c++有封装好的 stack,所以我就直接用了。

前序遍历
递归:
根 -> 左 -> 右
非递归:
从根结点开始,先判断是否为空,不为空就先访问(打印)该结点,然后入栈,接着遍历左子树;为空时让栈顶元素出栈,并以其为父结点访问其右子树,直到当前节点为空且栈为空时结束。

void PreOrderTra(Tree *root)
{
	Tree *p = root;
	if(p == NULL)
		return;
	printf("%c ",p->data);
	PreOrderTra(p->lchild);
	PreOrderTra(p->rchild);
}

//非递归
void PreOrderTra_fdg(Tree *root)
{
    stack<Tree *> s;
    Tree *p = root;
    while(p || !s.empty())
    {
        if(p)
        {
            printf("%c ",p->data);
            s.push(p);      //入栈
            p = p->lchild;  //沿左子树
        }
        else
        {
            p = s.top();    //取出
            s.pop();        //出栈
            p = p->rchild;  //向右
        }
    }
}

中序遍历
递归:
左 -> 根 -> 右
非递归:
和前序遍历差不多,只不过访问节点的顺序不一样,访问节点的时机是从栈中弹出元素时访问,如果从栈中弹出元素,就意味着当前节点的父结点的左子树已经遍历完成,这时候再进行访问。

void InOrderTra(Tree *root)
{
    Tree *p = root;
    if(p == NULL)
        return;
    InOrderTra(p->lchild);
    printf("%c ",p->data);
    InOrderTra(p->rchild);
}

//非递归
void InOrderTra_fdg(Tree *root)
{
    stack<Tree *> s;
    Tree *p = root;
    while(p || !s.empty())
    {
        if(p)
        {
            s.push(p);      //入栈
            p = p->lchild;
        }
        else
        {
            p = s.top();    //取出
            printf("%c ",p->data);
            s.pop();        //出栈
            p = p->rchild;
        }
    }
}

后序遍历
递归:
左 -> 右 -> 根
非递归:
对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,或者都已被访问过了,就可以直接访问该结点。如果有孩子未访问,将P的右孩子和左孩子依次入栈(一定要是先右后左,因为访问的时候是从左孩子开始的)。定义两个指针,分别指向当前结点和之前访问过的结点,先将根结点入栈开始。

void PostOrderTra(Tree *root)
{
    Tree *p = root;
    if(p == NULL)
        return;
    PostOrderTra(p->lchild);
    PostOrderTra(p->rchild);
    printf("%c ",p->data);
}

//非递归
void PostOrderTra_fdg(Tree *root)
{
    stack<Tree *> s;
    Tree *p,*q;
    p = q = NULL;
    s.push(root);
    while(!s.empty())
    {
        p = s.top();
        if((p->lchild == NULL && p->rchild == NULL) || (q != NULL && (q == p->lchild || q == p->rchild)))
        {
            printf("%c ",p->data);  //如果当前结点没有孩子或者孩子结点都已被访问过
            s.pop();    //出栈
            q = p;      //记录上一次访问的结点
        }
        else    //分别放入右左孩子
        {   
            if(p->rchild != NULL)
                s.push(p->rchild);
            if(p->lchild != NULL)
                s.push(p->lchild);
        }
    }
}

ok,了解了前序,中序,后序的遍历之后,再让我们来看一下层序遍历。我写了两种,一种用的数组,一种是 queue(都一样,只不过是借用数组来实现一个队列的功能)。借助队列来实现,还是比较好理解的,先将其父结点入队,然后将其左右孩子依次入队,队列未空就一直按顺序访问即可。

//层序遍历-(数组)
void LevelTra(Tree *root)
{
    Tree *t[20];    //指针数组,存放二叉树结构体指针
    int in,out;
    in = out = 0;
    t[in++] = root; //先保存根结点
    while(in > out)
    {
        if(t[out])
        {
            printf("%c ",t[out]->data);
            t[in++] = t[out]->lchild;
            t[in++] = t[out]->rchild;
        }
        out++;
    }
}

//层序遍历-(队列)
void LevelTra_dl(Tree *root)
{
    queue<Tree *> s;
    Tree *p = NULL;
    if(root)
        s.push(root);           //根结点入队

    while(!s.empty())
    {
        p = s.front();
        printf("%c ",p->data);
        
        if(p->lchild != NULL)
            s.push(p->lchild);
        if(p->rchild != NULL)
            s.push(p->rchild);

        s.pop();
    }
}

3. 求一棵树的结点个数
注:3,4,5,6点都是递归实现的,有些相通的,理解了一个之后,看其它的会比较好理解。

用递归来实现,比较好理解,但是不是很好能想通(我开始是 printf 来的,感受每次递归时的变化)。结点如果为空返回 0,否则返回的是左右子树的结点和再 +1(要加上自己本身)。

int GetNodeNum(Tree *root)
{
    if(root == NULL)
        return 0;
    int lNum = GetNodeNum(root->lchild);
    int rNum = GetNodeNum(root->rchild);
    //printf("%d %d\n",lNum,rNum);
    return lNum + rNum + 1;     
}

4. 求一棵树的深度
因为自己本身也占一层,所以返回时 +1。

int GetTreeDepth(Tree *root)
{
    if(root == NULL)
        return 0;
    int lDepth = GetTreeDepth(root->lchild);
    int rDepth = GetTreeDepth(root->rchild);
    //printf("%d %d\n",lDepth,rDepth);
    return lDepth > rDepth ? (lDepth + 1) : (rDepth + 1);
}

5. 求一棵树的叶子结点数
叶子结点:没有孩子的结点。

int GetLeafNodeNum(Tree *root)
{
    if(root == NULL)
        return 0;
    if(root->lchild == NULL && root->rchild == NULL)
        return 1;
    int lNum = GetLeafNodeNum(root->lchild);
    int rNum = GetLeafNodeNum(root->rchild);
    return (lNum + rNum);
}

6. 求一棵树某一层的结点数
k 为要访问的那一层。

int GetNodeNumLevel(Tree *root, int k)
{
    if(root == NULL || k < 1)
        return 0;
    if(k == 1)
        return 1;
    int lNum = GetNodeNumLevel(root->lchild,k - 1);
    int rNum = GetNodeNumLevel(root->rchild,k - 1);
    return (lNum + rNum);
}

7. 再完成了各种操作之后,一定不能忘记的就是要销毁二叉树了

void DeleteTree(Tree *root)
{
    if(root == NULL)
        return;
    DeleteTree(root->lchild);
    DeleteTree(root->rchild);
    free(root);
}

以上便是我今天所总结的一些有关二叉树的操作,因为自己也比较菜,所以若是有什么地方有错误,请大家指正!

最后贴上我的主函数
需要注意的就是最后要让指针指向 NULL。

int main()
{
	Tree *m_root = NULL;
    int k;
    int NodeNum,TreeDepth,LeafNodeNum,NodeNumLevel;
    
    printf("创建二叉树: ");
	CreatBiTree(&m_root);

    printf("前序遍历(递归): ");
	PreOrderTra(m_root);
	printf("\n");
    printf("前序遍历(非递归): ");
    PreOrderTra_fdg(m_root);
    printf("\n");

    printf("中序遍历(递归): ");
    InOrderTra(m_root);
    printf("\n");
    printf("中序遍历(非递归): ");
    InOrderTra_fdg(m_root);
    printf("\n");

    printf("后序遍历(递归): ");
    PostOrderTra(m_root);
    printf("\n");
    printf("后序遍历(非递归): ");
    PostOrderTra_fdg(m_root);
    printf("\n");

    printf("层序遍历(数组): ");
    LevelTra(m_root);
    printf("\n");
    printf("层序遍历(队列): ");
    LevelTra_dl(m_root);
    printf("\n");

    printf("树的结点数: ");
    NodeNum = GetNodeNum(m_root);
    printf("%d\n",NodeNum);

    printf("树的深度: ");
    TreeDepth = GetTreeDepth(m_root);
    printf("%d\n",TreeDepth);

    printf("树的叶子结点数: ");
    LeafNodeNum = GetLeafNodeNum(m_root);
    printf("%d\n",LeafNodeNum);

    printf("获取第几层的结点数: ");
    scanf("%d",&k);
    NodeNumLevel = GetNodeNumLevel(m_root,k);
    printf("第%d层结点数为: %d\n",k,NodeNumLevel);

	DeleteTree(m_root);
    m_root = NULL;    //防止野指针
	return 0;
}

!@#$%^&*~

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值