【数据结构】第三讲 树(上)&&树的同构

3.1 树与树的表示

什么是树?

客观事物许多事物之间存在层次关系

  • 人类社会家谱

  • 社会组织结构

  • 图书信息管理

  • 磁盘文件管理

  • 分层次组织管理再管理上有更高的效率
    如何实现有效率的查找?
    查找:根据某个给定的关键字K,从集合R中找出关键字与K相同的记录
    静态查找:
    集合中记录固定

  • 没有插入和删除操作,只有查找。
    动态查找:
    集合中记录是动态变化的。

  • 除查找,还可能发生插入和删除。

3.1.1 引子:顺序查找

静态查找
方法1:顺序查找 O ( n ) O(n) O(n)
typedef struct LNode *List;
struct LNode
{
    int Element[MAXSIZE];
    int Length;
};
int SequentialSearch(List Tbl,int K)
{//在Element[1]~Element[n]中查找关键字为K的数据元素
    int i;
    Tbl->Element[0] = K;//建立哨兵
    for (i = Tbl->Length; Tbl->Element[i] != K;--i);
    return i;//查找成功返回单元下标,不成功返回0
}

3.1.2 引子:二分查找的例子

方法2:二分查找(Binary Search) O ( l o g n ) O(logn) O(logn)

假设n个数据元素的关键字满足有序(比如:小到大
并且是连续存放的数组,那么可以进行二分查找。

3.1.3 二分查找实现

二分查找算法
int BinarySearch(List Tbl,int K)
{//在Tbl中查找关键字为k的数据元素

   int left, right, mid, NoFound = -1;
   left = 1;//初始左边界
   right = Tbl->Length;//初始右边界
   while(left<=right)
   {
       mid = (left + right) / 2;
       if(K<Tbl->Element[mid])
           right = mid - 1;//调整右边界
       else if(K>Tbl->Element[mid])
           left = mid + 1;//调整左边界
       else
           return mid;//查找成功,返回下标
   }
   return NoFound;
}

11个元素的判定树

  • 判定树上每个结点需要查找的次数刚好为该结点所在的层数
  • 查找成功时查找次数不会超过判定树的深度。
  • n个结点的判定树深度为 [ l o g 2 n ] + 1 [log_2^n]+1 [log2n]+1
  • ASL:总查找次数/结点个数=平均查找次数
    启示:
    在这里插入图片描述
    在这里插入图片描述

3.1.4 树的定义和术语

树的定义:
树(Tree)😗*n(n≥0)**个结点

  • 树中有一个称为**根(root)**的特殊结点,用r表示
  • 其余结点可以分为m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每个集合本身又是一棵树
    在这里插入图片描述
    树与非树?
  • 子树是不相交
  • 除了根结点外,每个结点有且只有一个父结点
  • ⭐一棵N个结点的树有N-1条边

在这里插入图片描述
树的一些基本术语:

  • 1.结点的度
  • 2.树的度:树的所有结点中最大的度数。
  • 3.叶结点(Leaf):度为0
  • 4.父节点,子节点,兄弟结点
  • 5.路径和路径长度:路径所包含的边的个数
  • 6.结点的层次(Level):规定根在1层,其他结点+1
  • 7.树的深度(Depth):树中所有结点中的最大层次是这棵树的深度。

⭐3.1.5 树的表示

看似可以用链表表示,但各个结点结构不一样,指针数目不一样。
在这里插入图片描述

设置每个结点相同的结构。按度最大的那个结点的指针域大小配置各个结点,3N,会造成比较大的浪费。

儿子-兄弟表示法
Elemtent
FirstChild

每个结点都是两个指针域,
在这里插入图片描述
旋转45°得到一个图:
在这里插入图片描述

Elemtent
Left

二叉树!
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

讨论3.2 森林及表示

树的集合称为森林。是否也可以使用“儿子-兄弟”表示法存储森林?如何实现?

答:可以设置一个森林节点forest,forest的FirstChild指向第一棵树的根节点root1,root1的NextSibling指向第二课树的根节点root2,依次往后指

3.2 二叉树及存储结构

3.2.1 二叉树的定义及性质

二叉树的定义

二叉树T:一个有穷的结点集合
这个集合可以为空
若不为空,则它是由根结点和称为其左子树 T L T_L TL和右子树 T R T_R TR的两个不相交的二叉树组成。
二叉树的子树有左右顺序之分

特殊二叉树
  • 斜二叉树
    在这里插入图片描述

  • 完美二叉树/满二叉树
    除了叶子结点都是两个子结点。并且叶子结点都在最底层。
    在这里插入图片描述

  • 完全二叉树
    完全二叉树:有n各结点的二叉树,对树中结点按从上至下,从左到右的顺序进行编号,编号为i(1≤i≤n)结点与满二叉树中编号为i的结点在二叉树中位置相同。

完全二叉树不是满二叉树或完美二叉树,允许缺掉最下面一层靠右边的结点。

typedef struct TNode *Position;
typedef Position BinTree; /* 二叉树类型 */
struct TNode{ /* 树结点定义 */
    int Data; /* 结点数据 */
    BinTree Left;     /* 指向左子树 */
    BinTree Right;    /* 指向右子树 */
};
⭐二叉树几个重要性质
  • 1.一个二叉树第i层的最大结点数为: 2 i − 1 , i ≥ 1 2^{i-1},i≥1 2i1,i1
    (层数自1开始)
  • 2.深度为k的二叉树有最大结点总数为: 2 k − 1 , k ≥ 1 2^k-1,k≥1 2k1k1
  • 3.对任何非空二叉树T,若n0表示叶节点的个数,n2是度为2的结点个数,那么两者满足关系 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1

推导方法:总结点数-1得到总边数
在这里插入图片描述

在这里插入图片描述

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

数据对象集:
一个有穷的结点集合
若不为空,则由根节点和其左,右二叉子树组成

操作集:
BT∈BinTree,Item∈ElementType,重要操作有:

1.Boolean IsEmpty(BinTree BT)//判别BT是否为空
2.void Traversal(BinTree BT)//遍历,按某顺序访问每个结点
3.BinTree CreatBinTree()//创建一个二叉树

常用的遍历方法有:

void PreOrderTraversal(BinTree BT)  //先序--根,左子树,右子树
void InOrderTraversal(BinTree BT)   //中序--左子树,根,右子树
void PostOrderTraversal(BinTree BT) //后续--左子树,右子树,根
void LevelOrderTraversal(BinTree BT)//层次遍历。从上到下,从左到右

3.2.2 二叉树的存储结构

1.顺序存储结构

完全二叉树:从上到下,从左到右为n个结点的完全二叉树的结点的父子关系

  • 非根结点(序号i>1)的父结点的序号是**[i/2]**
  • 结点(序号为i)的左孩子结点的序号是2i
    (若2i≤n,否则没有左孩子存在)
  • 结点(序号为i)的右孩子结点的序号是2i+1,(若2i+1≤n,否则没有右孩子)
    在这里插入图片描述

一般二叉树也可以采用这样的结构,但会造成空间浪费…
在这里插入图片描述

⭐2.链表存储
LeftDataRight
typedef struct TreeNode *BitTree;
typedef BitTree Position;
struct TreeNode
{
    int Data;
    BinTree Left;
    BinTree Right;
};

3.3 二叉树的遍历

3.3.1 先序,中序后序遍历

(1)先序遍历
遍历过程为:
①访问根结点
先序遍历其左子树
先序遍历其右子树
在这里插入图片描述

void PreOrderTraversal(BinTree BT)
{
    if(BT)
    {
        cout << BT->Data;
        PreOrderTraversal(BT->Left);
        PreOrderTraversal(BT->Right);
    }
}

(2)中序遍历
遍历过程为:
中序遍历其左子树
②访问根结点
中序遍历其右子树

void InOrderTraversal(BinTree BT)
{
    if(BT)
    {
        PreOrderTraversal(BT->Left);
        cout << BT->Data;
        PreOrderTraversal(BT->Right);
    }
}

(3)后序遍历
遍历过程为:
后序遍历其左子树
后序遍历其右子树
③访问根结点
在这里插入图片描述

void PostOrderTraversal(BinTree BT)
{
    if(BT)
    {
        PreOrderTraversal(BT->Left);
        PreOrderTraversal(BT->Right);
        cout << BT->Data;
    }
}

总结:
先序,中序和后序遍历过程:遍历过程中经过结点的路线一样,只是访问各结点的时机不同

3.3.2中序非递归遍历

中序遍历的非递归实现:
非递归算法实现的基本思路:使用堆栈
中序遍历的非递归遍历算法:

  • 遇到一个结点,就把它压堆,并去遍历它的左子树
  • 左子树遍历结束后,从栈顶弹出这个结点并访问它
void InOrderTraversal(BinTree BT)
{
    BinTree T = BT;
    Stack S = CreatStack(MaxSize);//创建并初始化堆栈S
    while(T||!IsEmpty(S))
    {
        while(T)//一直向左并将沿途结点压入堆栈
        {
            Push(S, T);
            T = T->Left;
        }
        if(!IsEmpty(S))
        {
            T = Pop(S);//结点弹出堆栈
            cout << T->Data;//访问打印结点
            T = T->Right;//转向右子树
        }
    }
}
讨论3.4 如何用堆栈实现后序遍历的非递归程序

我们前面看到,借助堆栈可以实现前序遍历、中序遍历的非递归程序,而且两者的程序结构几乎一样。
那么,是否也可以借助堆栈实现后序遍历的非递归程序?是不是挪动一下printf语句就可以了?
参考:
如何用堆栈实现后序遍历的非递归程序
用堆栈实现后序遍历的非递归程序

3.3.3 层序遍历

二叉树遍历的核心问题:二维结构的线性化

  • 从结点访问其左右儿子结点 。
  • 访问左儿子后,右儿子怎么办?
    • 需要一个存储结构保存暂时不访问的结点、
    • 存储结构:堆栈,队列
队列实现

队列实现:遍历从根结点开始,首先将根结点入队,然后开始执行循环:结点出队,访问该结点,其左,右儿子入队
分享一款做这个GIF用到的软件,非常小巧轻便:GifCam,搭配Quicker很流畅。
密码:2t2u
在这里插入图片描述
层序基本过程:先根结点入队,然后:
①从队列中取出一个元素
访问该元素所指结点
③若该元素所指结点的左,右孩子结点非空。则将其左,右孩子的指针顺序入队。

void LevelOrderTraversal(BinTree BT)
{
    Queue Q;
    BinTree T;
    if(!BT)//若是空树则直接返回
        return;
    Q = CreatQueue(Maxsize);//创建并初始化队列Q
    ADD(Q, BT);//根结点放入队列
    while(!IsEmptyQ(Q))
    {
        T = DeleteQ(Q);//从队列中抛出一个元素赋值为T
        cout << T->Data;//访问取出队列的结点
        if (T->Left)
            AddQ(Q, T->Left);
        if(T->Right)
            AddQ(Q, T->Right);
    }
}
讨论3.5 将层序遍历中的队列改为堆栈老师参与

如果将层序遍历中的队列改为堆栈,是否也是一种树的遍历?可以应用这种方法改造出一种前序、中序、后序的非递归遍历吗?

答:是树的遍历,但不属于中 前 后任意一个。用堆栈可以改造出前序遍历,入栈顺序是 右子树 再左子树。

⭐3.3.4 遍历应用例子

输出二叉树中的叶子结点。
  • 在二叉树的遍历算法中增加检测结点的**“左右子树是否都为空”**
void PreOrderTraversal(BinTree BT)
{
    if(BT)
    {
    	if(!BT->Left&&!BT->Right)
	        cout << BT->Data;
        PreOrderTraversal(BT->Left);
        PreOrderTraversal(BT->Right);
    }
}
求二叉树的高度(递归去做)

利用后序遍历的程序框架实现

int PostOrderGetHeight(BinTree BT)
{
    int HL, HR, MaxH;
    if(BT)
    {
        HL=PostOrderGetHeight(BT->Left);//左子树深度
        HR = PostOrderGetHeight(BT->Right);//右子树深度
        MaxH = (HL > HR) ? HL : HR;//取左右子树较大的深度
    }
    else
        return 0;//空树深度为0
}
二元运算表达式树及其遍历

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

由两种遍历序列确定二叉树

给出的序列必须包含中序遍历才行

因为前序是根,左,右。后序是左,右,根。
没有根,这两个序列的方法都是一样的。

题目:根据先序和中序遍历序列来确定一棵二叉树

  • 1.根据先序遍历序列第一个结点确定根结点
  • 2.根据根结点在中序遍历序列中分割出左右两个子序列。
  • 3.对左子树和右子树分别递归使用相同的办法。

在这里插入图片描述

⭐3.5 树的同构

给定两棵树T1,T2。如果T1可以通过若干次左右孩子互换就变成了T2,则我们称这两棵树是"同构的"。现给定两棵树,判断是不是同构的。
7-3 树的同构 (25 分)

输入格式:
输入给出2棵二叉树树的信息。对于每棵树,首先在一行中给出一个非负整数N (≤10),即该树的结点数(此时假设结点从0到N−1编号);随后N行,第i行对应编号第i个结点,给出该结点中存储的1个英文大写字母、其左孩子结点的编号、右孩子结点的编号。如果孩子结点为空,则在相应位置上给出“-”。给出的数据间用一个空格分隔。注意:题目保证每个结点中存储的字母是不同的。

输出格式:
如果两棵树是同构的,输出“Yes”,否则输出“No”。

求解思路

  • 1.二叉树表示
  • 2.建二叉树
  • 3.同构判别

二叉树表示

结构数组表示二叉树:静态链表
数组中每个分量都是一个结构。
在这里插入图片描述
不能用NULL表示空下标,因为NULL是0,也是一个下标。为了区分,定义NULL为-1。

#define MaxTree 10
#define ElementType char
#define Tree int
#define NULL -1
struct TreeNode
{
    char Element;
    Tree Left;
    Tree Right;
}T1[MaxTree],T2[MaxTree];

判断哪个是根?
根据所有的Left和Right里没有出现过的,就是根结点(没有人指向它)。

程序框架搭建

int main()
{
    //建二叉树1
    //建二叉树2
    //判别是否同构并输出
    return 0;
}

需要设计的函数:

  • 读取该二叉树
  • 二叉树同构异构判断
int main()
{
    Tree R1, R2;
    R1 = BuilidTree(T1);
    R2 = BuildTree(T2);
    if(lsomorphic(R1,R2))
        cout << "Yes!";
    else
        cout << "No!";
    return 0; 
}
怎么建二叉树?
Tree Build(struct TreeNode T[])
{
    int N;
    cin >> N;
    if(N)
    {
        for (i = 0; i < N;++i)
        {
            check[i] = 0;
        }
        for (i = 0; i < N; ++i)
        {
            cin >> T[i].Element;
            cin >> cl;
            cin >> cr;
            if(cl!='-')
            {
                T[i].Left = cl - '0';
                check[T[i].Left] = 1;
            }//对cl的处理
            else
                T[i].Left = NULL;
            //...同样对cr处理
        }//做完之后就只有根没有人check过
        for (i = 0; i < N;++i)
        {
            if(!check[i])
                break;
            Root = i;
        }
        return Root;
    }
}
如何判别两二叉树同构
int lsomorphic(Tree R1,Tree R2)
{
    if((R1==Null)&&(R2==Null))
        return 1;
    if(((R1==Null)&&(R2!=Null))||((R1!=Null)&&(R2==Null)))
        return 0;
    if (T1[R1].Element != T2[R2].Element)
    //roots are different
        return 0;
    if((T1[R1].Left==Null)&&(T2[R2].Left==Null))
    //both have no left subtree
        return lsomorphic(T1[R1].Right, T2[R2].Right);
    if (((T1[R1].Left != Null) && (T2[R2].Left != Null)) && ((T1[T1[T1].Left].Element)==(T2[T2[R2].Left].Element))))
 //左边同时不空,且看左边element是不是同时不一样的
     return (lsomorphic(T1[R1].Left,T2[R2].Left)&&lsomorphic(T1[R1].Right,T2[R2].Right));
 else
 //需要交换
     return (lsomorphic(T1[R1].Left,T2[R2].Right)&&lsomorphic(T1[R1].Right,T2[R2].Left));

}

⭐完整代码

#include <iostream>
#define MaxTree 10
#define ElementType char
#define Tree int
#define Null -1

using namespace std;
struct TreeNode
{
    char Element;
    Tree Left;
    Tree Right;
}T1[MaxTree],T2[MaxTree];
Tree BuildTree(struct TreeNode T[])
{
    int i,N;
    Tree Root;
    cin >> N;
    int check[MaxTree];
    char cl,cr;
    if(N)
    {
        for (i = 0; i < N;++i)
        {
            check[i] = 0;
        }
        for (i = 0; i < N; ++i)
        {
            cin >> T[i].Element;
            cin >> cl;
            cin >> cr;
            if(cl!='-')//判断左节点
            {
                T[i].Left = cl - '0';
                check[T[i].Left] = 1;
            }
            else
                T[i].Left = Null;
            if(cr!='-')//判断右结点
            {
                T[i].Right = cr - '0';
                check[T[i].Right] = 1;
            }
            else
                T[i].Right = Null;
        }
        for (i = 0; i < N;++i)
        {
            if(!check[i])
                break;
        }
        Root = i;
    }
    else return Null;
    return Root;
}

int lsomorphic(Tree R1,Tree R2)
{
    if((R1==Null)&&(R2==Null))
        return 1;
    if(((R1==Null)&&(R2!=Null))||((R1!=Null)&&(R2==Null)))
        return 0;
    if (T1[R1].Element != T2[R2].Element)
    //roots are different
        return 0;
    if((T1[R1].Left==Null)&&(T2[R2].Left==Null))
    //both have no left subtree
        return lsomorphic(T1[R1].Right, T2[R2].Right);
    if (((T1[R1].Left != Null) && (T2[R2].Left != Null)) && ((T1[T1[R1].Left].Element)==(T2[T2[R2].Left].Element)))
    //左边同时不空,且看左边element是不是同时不一样的
        return (lsomorphic(T1[R1].Left,T2[R2].Left)&&lsomorphic(T1[R1].Right,T2[R2].Right));
    else
    //需要交换
        return (lsomorphic(T1[R1].Left,T2[R2].Right)&&lsomorphic(T1[R1].Right,T2[R2].Left));
}
int main()
{
    Tree R1, R2;
    R1 = BuildTree(T1);
    R2 = BuildTree(T2);
    if(lsomorphic(R1,R2))
        cout << "Yes";
    else
        cout << "No";
    return 0; 
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值