数据结构之二叉树

目录

前言

一、二叉树的逻辑结构

1.二叉树的定义

 二叉树的特点

 二叉树的五种基本形态

 几种特殊的二叉树

 2.二叉树的基本性质

3.二叉树的抽象数据类型定义

4.二叉树的遍历操作

二、二叉树的存储结构及实现

1.顺序存储结构

 2.二叉链表

1.前序遍历

2.后序遍历

3.中序遍历

4.层序遍历

5.构造函数

6.析构函数

3.三叉链表

三、树、森林与二叉树的转换

1.树转换为二叉树

2.森林转换成二叉树

3.二叉树转换成树或森林

总结


前言

本篇可以助你开始了解二叉树。

一、二叉树的逻辑结构

1.二叉树的定义

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵五互不相交的、分别称为根节点的左子树和右子树的二叉树组成。

 二叉树的特点:

  • 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点
  • 二叉树是有序的,其次序不能任意颠倒,即使树中某个结点只有一棵树,也要区分它是左子树还是右子树。所以,二叉树和树是两种树结构。

 

 二叉树的五种基本形态:

  • 空二叉树
  • 只有一个根结点
  • 根结点只有左子树
  • 根结点只有右子树
  • 根结点既有左子树又有右子树

几种特殊的二叉树:

  • 斜树

所有结点都只有左子树或都只有右子树

  • 满二叉树:

所有分支节点都存在左子树和右子树,并且所有叶子都在同一层上

特点:

  1. 叶子只能出现在最下一层
  2. 只有度为0和度为2的结点

  • 完全二叉树

 对一棵具有n个结点的二叉树按层序编号,编号为i(1<=i<=n)的结点与同深度的满二叉树中编号为i的结点在二叉树中的位置相同

特点:

  1. 叶子结点只能出现在最下两层,且最下层的叶子结点都集中在二叉树左侧连续的位置
  2. 如果有度为1的结点,只可能有一个,且该结点只有左孩子

 2.二叉树的基本性质

  • 二叉树的第i层上最多有2^{i-1}个结点(i>=1)
  • 一棵深度为k的二叉树中,最多有2^{k}-1个结点(满二叉树),最少有k个结点(可以是斜树)
  • 在一棵二叉树中,如果叶子结点的个数为n_{0},度为2的结点个数为n_{2},则n_{0}=n_{2}+1
  • 具有n个结点的完全二叉树的深度为\left \lfloor {log_{2}}^{n} \right \rfloor+1
  • 对一棵具有n个结点的完全二叉树中的结点从1开始按层序编号,则对于任意的编号为i(i<=i<=n)的结点(简称为结点i),有:

    (1)如果i>1,则结点i的双亲的编号为\left \lfloor i/2 \right \rfloor;否则结点i为根结点,无双亲。

    (2)如果2i<=n,则结点i的右孩子的编号为2i;否则结点i无左孩子。

    (3)如果2i+1<=n,则结点i的有孩子的编号为2i+1;否则结点i无右孩子。

3.二叉树的抽象数据类型定义

 与树的定义基本一致,具体见后续代码

4.二叉树的遍历操作

  • 前序遍历
  • 中序遍历
  • 后序遍历
  • 层序遍历

前序序列、后序序列和中序序列可以唯一确定一棵二叉树,但是前序序列和后序序列不能唯一确定一棵二叉树。

具体的遍历在二叉树的存储结构及实现里以代码的形式展现

二、二叉树的存储结构及实现

1.顺序存储结构

用一维数组存储二叉树中的结点,并且用节点的存储位置(下标)表示结点之间的逻辑关系——父子关系。具体步骤如下:

              (1)将二叉树按完全二叉树顺序编号。根节点的编号为1,若某结点i有左孩子,则其右孩子的编号为2i+1;

              (2)将二叉树中的结点以编号顺序存储到一维数组中。注意,c++语言的数组下标从0开始,因此,编号为i的结点存储到下标为i-1的位置。

 显然这种方法会造成存储空间的浪费,最坏的情况是右斜树。

 2.二叉链表

二叉树一般采用二叉链表存储,其基本思想是:令二叉树的每个结点对应一个链表结点,链表结点除了存放与二叉树结点有关的数据信息外,还要设置左右孩子的指针。

结构:

struct BiNode
{
    char data;
    BiNode *lchild,*rchild;
};

data为数据域,存放该结点的数据信息;Ichild为左指针域,左孩子不存在即为空;Rchild为右指针域,右孩子不存在即为空;

 

 1.前序遍历

void PreOrder(BiNode *bt)
{
    if(bt==nullptr)return;
    else
    {
        cout<<bt->data<<" ";
        PreOrder(bt->lchild);
        PreOrder(bt->rchild);
    }
}

 2.后序遍历

void PostOrder(BiNode *bt)
{
    if(bt==nullptr)return;
    else
    {
        PostOrder(bt->lchild);
        PostOrder(bt->rchild);
        cout<<bt->data<<" ";
    }
}

3.中序遍历

void InOrder(BiNode *bt)
{
    if(bt==nullptr)return;
    else
    {
        InOrder(bt->lchild);
        cout<<bt->data<<" ";
        InOrder(bt->rchild);
    }
}

4.层序遍历

#ifndef BiTree_H
#define BiTree_H
struct BiNode
{
    char data;
    BiNode *lchild,*rchild;
};
class BiTree
{
public:
    BiTree(){root=Creat(root);}
    ~BiTree(){Release(root);}
    void PreOrder(){PreOrder(root);}
    void InOrder(){InOrder(root);}
    void PostOrder(){PostOrder(root);}
    int DepthOfTree(){DepthOfTree(root);}
    int GetNodenum(){GetNodenum(root);}
    void GetLeafNode(){GetLeafNode(root);}
    void LeverOrder();
private:
    BiNode *root;
    BiNode *Creat(BiNode *bt);
    void Release(BiNode *bt);
    void PreOrder(BiNode *bt);
    void InOrder(BiNode *bt);
    void PostOrder(BiNode *bt);
    int DepthOfTree(BiNode *bt);
    int GetNodenum(BiNode *bt);
    void GetLeafNode(BiNode *bt);
};
#endif // BiTree_H
#include <iostream>
using namespace std;
BiNode *BiTree::Creat(BiNode *bt)
{
    char ch;
    cin>>ch;
    if(ch=='#') return NULL;
    else
    {
        bt=new BiNode;
        bt->data=ch;
        bt->lchild=Creat(bt->lchild);
        bt->rchild=Creat(bt->rchild);
    }
    return bt;
}
void BiTree::Release(BiNode *bt)
{
    if(bt!=nullptr);
    Release(bt->lchild);
    Release(bt->rchild);
    delete bt;
}
void BiTree::PreOrder(BiNode *bt)
{
    if(bt==nullptr)return;
    else
    {
        cout<<bt->data<<" ";
        PreOrder(bt->lchild);
        PreOrder(bt->rchild);
    }
}
void BiTree::InOrder(BiNode *bt)
{
    if(bt==nullptr)return;
    else
    {
        InOrder(bt->lchild);
        cout<<bt->data<<" ";
        InOrder(bt->rchild);
    }
}
void BiTree::PostOrder(BiNode *bt)
{
    if(bt==nullptr)return;
    else
    {
        PostOrder(bt->lchild);
        PostOrder(bt->rchild);
        cout<<bt->data<<" ";
    }
}
void BiTree::LeverOrder()
{
    int front,rear;
    BiNode *Q[10000];
    front=rear=-1;
    if(root==NULL) return;
    Q[++rear]=root;
    BiNode *q;
    while(front!=rear)
    {
        q=Q[++front];
        cout<<q->data<<" ";
        if(q->lchild!=NULL) Q[++rear]=q->lchild;
        if(q->rchild!=NULL) Q[++rear]=q->rchild;
    }
}
int BiTree::DepthOfTree(BiNode *bt)
{
    if(bt==nullptr)return 0;
    else return max(DepthOfTree(bt->lchild),DepthOfTree(bt->rchild))+1;
}
int BiTree::GetNodenum(BiNode *bt)
{
    if(bt==nullptr)return 0;
    else return GetNodenum(bt->lchild)+GetNodenum(bt->rchild)+1;
}
void BiTree::GetLeafNode(BiNode *bt)
{
    if(bt==nullptr)return;
    else
    {
        if(bt->lchild==nullptr&&bt->rchild==nullptr)
        {
            cout<<bt->data<<" ";
        }
        GetLeafNode(bt->lchild);
        GetLeafNode(bt->rchild);
    }
}
int main()
{
    BiTree T;
    cout<<"-----Preorder-----"<<endl;
    T.PreOrder();
    cout<<endl;
    cout<<"-----Inorder-----"<<endl;
    T.InOrder();
    cout<<endl;
    cout<<"-----Postorder-----"<<endl;
    T.PostOrder();
    cout<<endl;
    cout<<"-----LeverOrder-----"<<endl;
    T.LeverOrder();
    cout<<endl;
    cout<<"-----The number of leafnode-----"<<endl;
    T.GetLeafNode();
    cout<<endl;
    cout<<"-----The depth of tree-----"<<endl;
    cout<<T.DepthOfTree()<<endl;
    cout<<"-----The number of tree's nodes-----"<<endl;
    cout<<T.GetNodenum()<<endl;


}

5.构造函数

BiNode *Creat(BiNode *bt)
{
    char ch;
    cin>>ch;
    if(ch=='#') return NULL;
    else
    {
        bt=new BiNode;
        bt->data=ch;
        bt->lchild=Creat(bt->lchild);
        bt->rchild=Creat(bt->rchild);
    }
    return bt;
}

6.析构函数

void Release(BiNode *bt)
{
    if(bt!=nullptr);
    Release(bt->lchild);
    Release(bt->rchild);
    delete bt;
}

完整代码:

3.三叉链表

 能够借助二叉链表访问某结点的孩子结点,但是要找到它的双亲结点,则需要从根结点开始,最坏情况下,需要遍历整个二叉链表。此时,用三叉链表存储显得十分必要。

结构

data、Ichild、rchild三个域的含义与二叉链表相同;parent域为指向结点的双亲结点的指针。

 示例

三叉链表既便于查找孩子,又便于查找双亲;但是增加了空间开销。

三、树、森林与二叉树的转换

1.树转换为二叉树

(1)加线——树中所有相邻兄弟之间加一条连线;

(2)去线——对树中的每个结点,只保留它与第一个孩子结点之间的连线,删去它与其他孩子节点之间的连线;

(3)层次调整——以树结点为轴心,将树顺时针转动一定角度,使之层次分明。

示例:

 2.森林转换成二叉树

(1)将森林中每棵树转换成二叉树;

(2)从第二棵二叉树开始,依次把后一颗二叉树的根结点作为前一棵二叉树根结点的有孩子,当所有二叉树连起来后,此时所得到的二叉树就是森林转换的得到的二叉树。

示例:

  3.二叉树转换成树或森林

(1)加线——若结点x是其双亲y的左孩子,则把结点x的有孩子、右孩子的右孩子、......,都与系结点y用线连起来;

(2)去线——删去原二叉树中所有的双亲结点与右孩子结点的连线;

(3)层次调整——整理(1)、(2)两步所得到的树或森林,使之层次分明。

示例:

总结

本篇仅仅记录了树的基本操作,有关线索链表以及树的遍历非递归算法之后会完善。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值