定义

树是n个节点的有限集合,当n=0时,为空树;n>0时,为非空树。任何一个非空树,满足一下两点:

1)有且仅有一个根节点

2)除根节点以外,其余节点可分为m(m>0)个互不相交的有限集,其中每一个集合本身又是一棵树,并且称为根的子树(subtree)

该定义是从集合论的角度给出的树的递归定义,即把树的节点看作一个集合。除了树根以外,其余节点分为m个互不相交的集合,每一个集合又是一棵树

例如,一棵树如图。该树除了树根之后,又分成了3个互不相交的集合T1,T2,T3,这3个集合本身又各是一棵树,称为根的子树。

相关术语

  • 节点——节点包含数据元素及若干指向子树的分支信息
  • 节点的度——节点拥有的子树个数
  • 树的度——树中节点的最大度数
  • 终端节点——度为0的节点,又称为叶子
  • 分支节点——度大于0的节点。除了叶子都是分支节点
  • 内部节点——除了树根和叶子都是内部节点

  • 节点的层次——从根到该节点的层数(根节点为第1层)
  • 树的深度——指所有节点中最大的层数。例如,一棵树如图,根为第1层,根的子节点为第2层……该树的最大层次为4,因此树的深度为4

  • 路径——树中两个节点之间所经过的节点序列
  • 路径长度——两节点之间路径上经过的边数。例如,一棵树如图所示,D到A的路径为D—B—A,D到A的路径长度为2。由于树中没有环,因此树中任意两个节点之间的路径都是唯一的

  • 有序树——节点的各子树从左至右有序,不能互换位置
  • 无序树——节点各子树可互换位置
  • 森林——由m棵不相交的树组成的集合。例如,删除树根A后,余下的3个子树构成一个森林。如图。

二叉树

定义

二叉树是一种特殊的树,它最多有两个子树,分别为左子树和右子树,二者是有序的,不可以互换。也就是说,二叉树中不存在度大于2的节点

五种形态

二叉树性质

证明:

  • 总结点数为n,度为0的有n0,度为1的有n1,度为2的有n2,则n=n0+n1+n2
  • 总结点数又等于分支数+1,只有根没有分支
  • 分支=n1+2n2

例题

例题1:一棵完全二叉树有1 001个节点,其中叶子节点的个数是多少?

解题思路:首先找到最后一个节点1 001的双亲节点,其双亲节点编号为1 001/2=500,该节点是最后一个拥有孩子的节点,其后面全是叶子,即1 001−500=501个叶子

例题2:一棵完全二叉树第6层有8个叶子,则该完全二叉树最少有多少节点,最多有多少个节点?

解题思路:完全二叉树的叶子分布在最后一层或倒数第二层,因此该树有可能为6层或7层。

节点最少的情况(6层):8个叶子在最后一层,即第6层,前5层是满的。最少有2的5次方-1+8=39

节点最多的情况(7层):8个叶子在倒数第二层,即第6层,前6层是满的,第7层最少缺失了8×2个节点,因为第6层的8个叶子如果生成孩子的话,会有16个节点。如图

 

二叉树的存储结构

顺序存储

二叉树也可以采用顺序存储,按完全二叉树的节点层次编号,依次存放二叉树中的数据元素。完全二叉树很适合顺序存储方式

而普通二叉树在顺序存储时需要补充为完全二叉树,在对应完全二叉树没有孩子的位置补0。显然,普通二叉树不适合顺序存储方式,因为有可能在补充为完全二叉树过程中,补充太多的0,而浪费大量空间,因此普通二叉树可以使用链式存储。

链式存储

二叉树采用链式存储方式:每个节点包含一个数据域,存储节点信息;还包含两个指针域,指向左右两个孩子。这种存储方式称为二叉链表

结构体定义

二叉链表的节点结构体定义

三叉链表的节点结构体定义

二叉树创建

递归创建

询问法

#include <iostream>
using namespace std;
typedef struct Bnode{
	char data;
	struct Bnode *lchild,*rchild;
}Bnode,*Btree; 


void createtree(Btree &T){
	char check;//判断是否创建左右节点,输入Y/N
	T=new Bnode;
	cout <<"请输入节点信息:"<<endl;
	cin >>T->data;
	cout <<"是否添加"<<T->data<<"的左孩子?(Y/N)"<<endl;
	cin >>check;
	if(check=='Y'){
		createtree(T->lchild);
	}else{
		T->lchild=NULL;
	}
	cout <<"是否添加"<<T->data<<"的右孩子?(Y/N)"<<endl;
	cin >>check;
	if(check=='Y'){
		createtree(T->rchild);
	}else{
		T->rchild=NULL;
	}
	return ;
}

int main()
{
    Btree mytree;
    createtree(mytree);/*创建二叉树*/
    return 0;
}

补空法

补空法是指如果左子树或右子树为空时,则用特殊字符补空,如“#”,然后按照根、左子树、右子树的顺序,得到先序遍历序列,根据该序列递归创建二叉树

算法步骤:

1)输入补空后的二叉树先序遍历序列。

2)如果ch=='#',T=NULL;否则创建一个新节点T,令T->data=ch;递归创建T的左子树;递归创建T的右子树

void Createtree(Btree &T)//创建二叉树函数(补空法)
{
    //二叉树补空后,按先序遍历序列输入字符,创建二叉树
    char ch;
    cin>>ch;
    if(ch=='#')
        T=NULL;    //建空树
    else{
        T=new Bnode;
        T->data=ch;               //生成根节点
        Createtree(T->lchild);    //递归创建左子树
        Createtree(T->rchild);    //递归创建右子树
    }
}

二叉树的遍历

按照根的访问顺序不同,根在前面称为先序遍历(DLR),根在中间称为中序遍历(LDR),根在最后称为后序遍历(LRD)

先序遍历

void preorder(Btree T)//先序遍历
{
    if(T)
    {
       cout<<T->data<<"  ";
       preorder(T->lchild);
       preorder(T->rchild);
    }
}

中序遍历

void inorder(Btree T){
	if(T){
		preorder(T->lchild);
		cout <<T->data<<" ";
		preorder(T->rchild);
	}
}

后序遍历

void posorder(Btree T)//后序遍历
{
    if(T)
    {
       posorder(T->lchild);
       posorder(T->rchild);
       cout<<T->data<<"  ";
    }
}

线索二叉树

定义

二叉树采用二叉链表存储时,每个节点有两个指针域。如果二叉链表有n个节点,则一共有2n个指针域,而只有n−1个是实指针,其余n+1是空指针,可以充分利用空指针记录节点的前驱或后继信息,从而加快查找节点前驱和后继的速度

这种带有标志域的二叉链表称为线索链表,指向前驱和后继的指针称为线索,带有线索的二叉树称为线索二叉树,以某种遍历方式将二叉树转化为线索二叉树的过程称为线索化

对于频繁查找前驱和后继的运算,线索二叉树优于普通二叉树。但是对于插入和删除操作,线索二叉树比普通二叉树开销大,因为除插入和删除操作外,还要修改相应的线索。

结构体定义

构造线索化二叉树

二叉树线索化的过程,实际上是在遍历过程中修改空指针的过程(空指针记录节点的前驱或后继线索)。可以设置两个指针,一个指针pre指向刚刚访问的节点,另一个指针p指向当前节点。也就是说,pre指向的节点为p指向的节点的前驱,反之,p指向的节点为pre指向的节点的后继。在遍历的过程中,如果当前节点p的左孩子为空,则该节点的lchild指向其前驱,即p->lchild=pre;如果pre节点的右孩子为空,则该节点的rchild指向其后继,即pre->rchild=p

每种遍历顺序不同,节点的前驱和后继也不同,因此二叉树线索化必须指明是什么遍历顺序的线索化。线索二叉树分为前序线索二叉树、中序线索二叉树和后序线索二叉树

void InThread(BTtree &p) //中序线索化
{
    //pre是全局变量,指向刚刚访问过的节点,p指向当前节点,pre为p的前驱
    if(p)
    {
        InThread(p->lchild);  //中序线索化p的左子树
        if(!p->lchild)        //p的左子树为空
        {
            p->ltag=1;        //标志域为1,表示线索(前驱)
            p->lchild=pre;    //p的左指针指向pre(前驱)
        }
        else
            p->ltag=0;        //标志域为0,表示非线索
        if(pre)
        {
            if(!pre->rchild)  //pre的右子树为空
            {
                pre->rtag=1;  //标志域为1,表示线索(后继)
                pre->rchild=p; //pre的右指针指向p(后继)
            }
            else
                pre->rtag=0;  //标志域为0,表示非线索
        }
        pre=p;  //更新pre,p将要移向右子树,始终保持pre指向p的前驱
        InThread(p->rchild);   //中序线索化p的右子树
    }
}

void CreateInThread(BTtree &T) //创建中序线索二叉树
{
    pre=NULL;//初始化为空
    if(T)
    {
        InThread(T); //中序线索化
        pre->rchild=NULL;// 处理遍历的最后一个节点,其后继为空
        pre->rtag=1;
    }
}

遍历

线索二叉树的线索记录了前驱和后继信息,因此可以利用这些信息进行遍历。下面以中序线索二叉树遍历为例,讲述遍历过程。

步骤

1)指针p指向根节点

2)若p非空,则重复以下操作:

p指针沿左孩子向下,找到最左节点,它是中序遍历的第一个节点;

访问p节点;

沿着右线索查找当前节点p的后继节点并访问,直到右线索为0或遍历结束。

3)遍历p的右子树

void InorderThread(BTtree T)//遍历中序线索二叉树
{
    BTtree p;
    p=T;
    while(p)
    {
       while(p->ltag==0) p=p->lchild;  //找最左节点
       cout<<p->data<<"  ";            //输出节点信息
       while(p->rtag==1&&p->rchild)    //右孩子为线索化,指向后继
       {
         p=p->rchild;   //访问后继节点
         cout<<p->data<<"  ";  //输出节点信息
       }
       p=p->rchild;  //转向p的右子树
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值