数据结构-二叉树

本文详细介绍了二叉树的概念,包括其特点、特殊类型的二叉树如斜树、满二叉树和完全二叉树。此外,还阐述了二叉树的存储结构,通常采用链式存储,并提供了前序、中序、后序遍历以及层次遍历的实现方法。最后,提到了二叉树的节点插入、删除以及计算高度、节点数和叶子节点数的方法。
摘要由CSDN通过智能技术生成
        简而言之,二叉树(Binary Tree)是一种每个结点最多有两个孩子的树,分别称为左孩子和右孩子。这样的树我们称为二叉树。

二叉树的特点

1.每个结点最多有两颗子树,所以二叉树中不存在度大于 2 的结点。
2.左子树和右子树是有顺序的,次序不能任意颠倒。
3.即使树中某一结点只有一颗子树,也要区分它是左子树还是右子树,例如下图,图一
是左子树,图二是右子树,他们是不同的。

 .特殊二叉树特点

1.斜二叉树
        顾名思义,斜树一定是要斜的,所有结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树,这两者都称为斜树,如下图,斜树属于一种阶级操作,因为当一颗二叉树表现为斜树时就演变成一个链表了,这样就失去二叉树优良的查找特性了。

 

2.满二叉树
        在一颗二叉树中,如果所有分支结点都存在左子树和右子树,并且所有的叶子结点都在同一层上,这样的二叉树称为满二叉树。如下图

 

 

3.完全二叉树
        对一颗具有 n 个结点的二叉树按层次编号,如果编码为 i(1<i<n)的结点与同样深度的满二叉树编号为 i 的结点在二叉树的位置完全相同,则这棵树称为完全二叉树,所以满二叉树一定是完全二叉树,但是完全二叉树不一定是满二叉树。

 二叉树的性质

1.性质一:在二叉树的第 i 层上至多有 2 i-1 个结点
2. .性质二:深度为 k 的二叉树最多有 2 k -1 个结点
3. .性质三:对任意一颗二叉树 T,如果其叶子结点树为 n,那么度为 2 的结点树为 n- 1
4. .性质四:具有 n 个结点的完全二叉树的深度为{log 2 n}+1,其中{X}表示不大于 X 的 最大整数
5.性质五:如果对一颗有 n 个结点的完全二叉树的结点按层序编号,对任意结点 i 都有
        a.如果 i= 1,则结点 i 是二叉树的根,如果 i>1,其双亲是结点 i/2,
        b.如果 2i>n,则结点 i 无左孩子,否则其左孩子是结点 2i,
        c.如果 2i+1>n,则结点 i 无右孩子,否则其右孩子是结点 2i+1

二叉树的存储结构

        本文档讲述二叉树采用链式存储,因为一般顺序存储不常见,下面是二叉树的程序存储设计示例:
typedef char TYPE;
typedef struct tnode
{
    TYPE date; //元素
    struct tnode *lchild; //左孩子
    struct tnode *rchild; //右孩子
}TNode;
使用的时候只需要记住根结点的位置就行了。

 二叉树的遍历

        二叉树的遍历方法根据根结点的位置分为前序遍历,中序遍历,后序遍历。

前序遍历

        对于一颗树或子树,先根结点,再左结点,再右节点的遍历方式为前序遍历

 程序实现方法:

采用递归

void pre_order(TNode *t) //t 为根结点
{
    if(t==NULL)
        return;
    printf("%c ",t->date); //先根
    pre_order(t->lchild); //左子树
    pre_order(t->rchild); //右子树
}

采用栈:

void for_pre_order(TNode *t) //按照先序遍历的方法访问 t 表示的二叉树
{
    Stack *s=NULL;
    TYPE v;
    s=linkstack(); //建立栈
    push(s,t); //根节点入栈
    TNode *q=NULL;
    while (stackisair(s))
    {
        q=s->top->tree; //记录栈头节点
        GetTop(s,&v); //获取栈顶元素
        printf("%c ",v);
        pop(s); //出栈
        if(q->rchild!=NULL) //左节点入栈
            push(s,q->rchild);
        if(q->lchild!=NULL) //右节点入栈
            push(s,q->lchild);
    }
}

中序遍历

        对于一颗树或子树,先左结点,再根结点,再右节点的遍历方式为中序遍历

 程序实现方法:

采用递归:
void mid_order(TNode *t) //中序遍历
{
    if(t==NULL)
        return;
    mid_order(t->lchild); //先左子树
    printf("%c ",t->date); //根结点
    mid_order(t->rchild); //右子树
}

采用栈:

void for_mid_order(TNode *t) //按照中序遍历的方法访问 t 表示的二叉树
{
    Stack *s=NULL;
    TYPE v;
    s=linkstack();
    TNode *q=t;
    while (stackisair(s)||q)
    {
        while (q) //一直将节点右孩子入栈
        {
            push(s,q);
            q=q->lchild;
        }
        q=s->top->tree;
        GetTop(s,&v);
        printf("%c ",v);
        pop(s);
        q=q->rchild;
    } 
}

后序遍历

对于一颗树或子树,先左结点,再右结点,再根节点的遍历方式为后序遍历

 程序实现方法:

采用递归:
void last_order(TNode *t) //后序遍历
{
    if(t==NULL)
        return;
    last_order(t->lchild);
    last_order(t->rchild);
    printf("%c ",t->date);
}

采用栈:

void for_last_order(TNode *t) //后序遍历
{
    Stack *s=NULL;
    TYPE v;
    int loop = 1;
    s=linkstack();
    push(s,t);
    TNode *q=t;
    while (stackisair(s))
    {
        //long 是用来判断栈顶元素和出栈元素是否有父子关系,有的话 long为 0 栈顶元素直接出栈
        while (q&&loop)
        {
            if(q->rchild)
                push(s,q->rchild);
            if(q->lchild)
                push(s,q->lchild);
            if(q->lchild)
                q=q->lchild;
            else
                q=q->rchild;
        }
        q=s->top->tree;
        GetTop(s,&v);
        printf("%c ",v);
        pop(s);
        if(!stackisair(s)) //栈为空退出
            break;
        if(q==s->top->tree->lchild||q==s->top->tree->rchild)
            loop=0;
        else
            loop=1;
        q=s->top->tree;
    }
}

顺序遍历

从上到下,从左到右遍历
采用队列实现:         
//按照层次遍历的方法访问 r 表示的二叉树
void Level_order(TNode *t)
{
    if(t==NULL)
        return;
    TNode *queue[1000]; //建立队列
    int i=0,j=0; //i 是队头,j 是队尾
    queue[j++]=t; //入队
    while (i!=j)
    {
        printf("%c ",queue[i]->date); //访问队头
        TNode *v=queue[i]; //记录队头
        i++; //出队
        if(v->lchild) //出队元素左孩子入队列
            queue[j++]=v->lchild;
        if(v->rchild) //出队元素左孩子入队列
            queue[j++]=v->rchild;
    }
}

二叉树的建立

        二叉树之所以有着优秀的查找特性,就是因为它的特殊存储结构,一般来说存储一堆数据,从根结点开始存储,比该结点小的存放在该结点的左结点处,比该结点大的存放在该结点的右结点处,这样就把一堆无序的数据在每一个结点处都分为了两股数据,这样在查找的时候就可以自动二分查找,降低查找的复杂度。

 一般方法:

TNode* establishtree(char *str)
{
    TNode *t=NULL; //定义一个指针保存根结点
    printf("请输入二叉树中的字符串:\n");
    scanf("%s",str);
    while (*str)
    {
        TNode *p=(TNode*)malloc(sizeof(TNode));
        p->date=*str; //存放结点数据
        p->lchild=p->rchild=NULL;
        if(t==NULL) //如果二叉树没有节点
        {
            t=p;
        }
        else
        {
            TNode *q=t;
            while (1) //寻找插入位置
            {
                if(q->date>p->date) //比这个节点小
                {
                    if(q->lchild==NULL)
                        break;
                    q=q->lchild;
                }
                else if(q->date<p->date) //比这个节点大
                {
                    if(q->rchild==NULL)
                        break;
                    q=q->rchild;
                }
                else
                {
                    break;
                }
            }
            if(q->date==p->date) //输入元素等于二叉树中的元素则跳过
            {
                str++;
                continue;
            }
            //插入
            if(q->date>p->date) 
            {
                q->lchild=p;
            }
            else
            {
                q->rchild=p;
            } 
        }
        str++;
    }
    return t;
}
递归法:
TNode* establishtree_dg(TNode *t,TYPE x)//输入一颗排序二叉树,递归法
{
    if(t==NULL)
    {
        TNode *t=(TNode*)malloc(sizeof(TNode));
        t->date=x;
        t->lchild=t->rchild=NULL;
        return t;
    }
    if(x>t->date)
    {
        t->rchild=establishtree_dg(t->rchild,x);
        return t;
    }
    else
    {
        t->lchild=establishtree_dg(t->lchild,x);
        return t;
    }
}

二叉树的结点插入

TNode *insert_node(TNode *t,TYPE x) //往指定的排序二叉树中插入一个元素
{
    TNode *p=(TNode*)malloc(sizeof(TNode));
    p->date=x;
    p->lchild=p->rchild=NULL;
    if(t==NULL)
    {
        t=p;
    }
    TNode *q=t;
    while (1)
    {
        if(q->date>p->date)
        {
            if(q->lchild==NULL) 
                break;
            q=q->lchild;
        }
        else if(q->date<p->date)
        {
            if(q->rchild==NULL)
                break;
            q=q->rchild;
        }
        else
        {
            break;
        }
    }
    if(q->date==p->date)
    return t;
    if(q->date>p->date)
    {
        q->lchild=p;
    }
    else
    {
        q->rchild=p;
    } 
    return t;
}

二叉树的结点删除

TNode *delete_tree(TNode *t,TYPE v) //删除元素值为 V 的结点
{
    if(t==NULL)
        return t;
    TNode *pr=NULL; //待删除结点的双亲结点
    TNode *pk=NULL; //指向待删除结点
    pk=t;
    //找到删除节点
    while (pk)
    {
        if(pk->date==v)
        {
            break;
        }    
        else if(pk->date>v)
        {
            pr=pk;
            pk=pk->lchild;
        }
        else
        {
            pr=pk;
            pk=pk->rchild;
        }
    }
    if(pk==NULL) //没找到
    {
        return t;
    }
    //删除节点操作
    while (1)
    {
        if(pk->lchild==NULL&&pk->rchild==NULL) //如果这个节点是叶子结点
        {
            if(pk==t) //叶子节点是根节点
            {
                t=NULL;
            }
            else if(pr->lchild==pk) //待删除叶子节点在父节点左边
            {
                pr->lchild=NULL;
            }
            else //待删除叶子节点在父节点右边
            {
                pr->rchild=NULL;
            }
            free(pk);
            return t;
        }
        if(pk->rchild==NULL) //待删除节点有左孩子
        {
            if(pk==t) //待删除节点是根节点
            {
                t=pk->lchild;
                pk->lchild=NULL;
            }
            else if(pr->lchild==pk) //待删除叶子节点在父节点左边
            {
                pr->lchild=pk->lchild;
                pk->lchild=NULL;
            }
            else //待删除叶子节点在父节点右边
            {
                pr->rchild=pk->lchild;
                pk->lchild=NULL;
            }
            free(pk);
            return t;
        }
        if(pk->lchild==NULL) //待删除节点有右孩子
        {
            if(pk==t) //待删除节点是根节点
            {
                t=pk->rchild;
                pk->rchild=NULL;
            }
            else if(pr->lchild==pk) //待删除叶子节点在父节点左边
            {
                pr->lchild=pk->rchild;
                pk->rchild=NULL;
            }
            else //待删除叶子节点在父节点右边
            {
                pr->rchild=pk->rchild;
                pk->rchild=NULL;
            }
            free(pk);
            return t;
        }
        //必定是有左右节点
        //找待删除节点的右子树的最大数再和待删除的节点交换一下值,然后把交换后的那个值删除,相当于就去删了叶子结点。
        TNode *var=pk; //值向找待删除节点的右子树的最大数
        TNode *va=pk; //值向找待删除节点的右子树的最大数的父节点
        var=var->lchild; //可以往左找最小
        while (var->rchild)
        {
            va=var;
            var=var->rchild;
        }
        TYPE num;
        num=var->date;
        var->date=pk->date;
        pk->date=num;
        pr=va; 
        pk=var; 
    } 
}
        其中相比大家不能明白的是当删除的结点有左孩子和右孩子时该怎么操作,其实很简单,如下图,当删除的结点是 A 时,我只需要将 A 与 A 的左子树中的最大值 E 进行值交换,然后再去删除 A,此时 A 就是一个叶子结点,删除起来就很简单了。

        

 递归计算二叉树的高度,结点数和叶子结点数

int talltree(TNode *t) //求一颗二叉树的高度
{
    if(t==NULL)
        return 0;
    int q=talltree(t->lchild)+1; //计算左子树高度
    int p=talltree(t->rchild)+1; //计算右子树高度
    return q>p?q:p; //返回最高高度
}
int nodenumtree(TNode *t) //求一颗二叉树的结点个数
{
    if(t==NULL)
        return 0;
    return nodenumtree(t->lchild)+nodenumtree(t->rchild)+1; //左子树结点数 + 右子树结点数 + 1
}
int leaftree(TNode *t) //求一颗二叉树的叶子结点个数
{
if(t==NULL)
    return 0;
if(t->lchild==NULL&&t->rchild==NULL)
    return 1;
return leaftree(t->lchild)+leaftree(t->rchild); //左子树的叶子结点 + 右子树的叶子结点
}

通过上面三个程序,想必大家对于递归又多了一点认识了吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九月丫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值