陈越《数据结构》第三讲 树(上)

本文深入探讨了树和二叉树的概念、性质及其基本术语,包括树的表示方法、二叉树的定义与特殊类型、二叉树的存储结构与遍历方法。此外,还讨论了二叉树的应用实例及树的同构问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

3.1 树与树的表示


3.1.1 引子:查找


分层次组织在管理上具有更高的效率!


1. 定义
根据某个给定 关键字K ,从 集合R 中找出关键字与K 相同的记录。


2.分类:
- 静态查找:集合中 记录是固定 的;
- 动态查找: 集合中 记录是动态变化的。


3.静态查找的方法

  • 方法1: 顺序查找(时间复杂度为O(n));
    //在数组的头部,建立,可减少判断的分支。
    这里写图片描述

  • 方法2:二分查找(时间复杂度O(logN))。

    这里写图片描述

问:

在二分查找中,我们是取mid等于left和right的中间值,即用等分的方法进行查找。
那为什么一定要等分呐?能不能进行“黄金分割”?也就是mid=left+0.618(right-left),当然mid要取整数。如果这样查找,时间复杂性是多少?也许你还可以编程做个试验,比较一下二分法和“黄金分割”法的执行效率。

答:
二分法每次能有100%的概率能只剩50%的数据,每次剩下的期望为50%,即每次除以2。所以时间复杂度是:log2(N)
而黄金分割的话每次都有0.618的概率剩0.6180.382的概率剩0.382,每次剩下的期望为0.528,即1/0.528=1.894。所以时间复杂度是:log1.894(N)=1.085log2(N)

3.1.2 树


  1. 定义:nn0个结点构成的有限集合
    • n=0 时,称为 空树
    • 树中有一个称为“ (Root ) ”的特殊结点, 用 r 表示;
    • 其余结点可分为m(m>0) 个 互不相交的 有限集,其中每个集合本身又是一棵树,称为原来树的“ 子树 (SubTree )”。

2.

  • 子树是 不相交 的 ;
  • 除了根结点外, 每个结点有且仅有一个父结点
  • 一棵N 个结点的树有N-1 条边

3.
树是保证节点联通的最小的一种联通方式!

  1. 结点的度(Degree ): 结点的 子树个数
  2. :树的所有结点中最大的度数;
  3. 叶结点 (Leaf): 度为0 的结点;
  4. 父结点 (Parent ):有子树的结点是其子树的根结点的父结点;
  5. 子结点 (Child ):若A 结点是B 结点的父结点,则称B 结点是A 结点的子结点;子结点也称 孩子结点
  6. 兄弟结点 (Sibling ):具有同一父结点的各结点彼此是兄弟结点;
  7. :从结点n1nk路径 为一个结点序列n1,n2,,nk,nini+1 的父结点。路径所包含边的个数为 路径的长度 ;
  8. 祖先结点(Ancestor) :沿 树根到某一结点路径 上的所有结点都是这个结点的祖先结点;
  9. 子孙结点(Descendant) :某一结点的 子树中的所有结点 是这个结点的子孙;
  10. (Level):规定 根结点在1 层 ,其它任一结点的层数是其父结点的层数加1;
  11. (Depth) :树中所有结点中的 最大层次 是这棵树的深 度。

4.

儿子-兄弟表示法
所需的总空间为3N(N为节点数)。
这里写图片描述

问:
树的集合称为森林。是否也可以使用“儿子-兄弟”表示法存储森林?如何实现?
答:
可以,不同树的根节点看成是这棵树的入口,各个树之间可以看做是兄弟节点。


3.2 二叉树及存储结构


3.2.1 二叉树

  1. 定义:一个有穷的结点集合。

    • 这个集合 可以为空
    • 若不为空,则它是由 根结点 和称为其 左子树TL右子树TR 的两个不相交的二叉树组成。
  2. 这里写图片描述

    • 一个二叉树第 i层的最大结点数为:2i1i1
    • k的二叉树有最大结点总数为: 2k1k1
    • T,若n0 表示叶结点的个数、n2 是度为2的非叶结点个数,那么两者满足关系n0=n2+1

问:
讨论3.3: m叉树中各类结点数之间的关系:
在二叉树中,我们知道叶结点总数与有两个儿子的结点总数之间的关系是:
那么类似关系是否可以推广到m叉树中?也就是,如果在m叉树中,叶结点总数是,有一个儿子的结点总数是,有2个儿子的结点总数是,有3个儿子的结点总数是,…。那么,之间存在什么关系?
答:
这里写图片描述
4.

  • 引用块内容
  • 遍历又分为:

3.2.2 二叉树的存储结构


  1. 顺序存储(主要针对);

  2. 链式存储


typedef struct PloyNode *Polynomial;//这句有什么用呢?
typedef struct PolyNode{//这两个typedef有什么联系吗?
int coef;
int expon;
Polynomial link;//这里是定义了一个相当于next后继指针吗?
}
如果按这样定义好了之后,如何定义一个这样的链表?

:
1. typedef struct PloyNode *Polynomial;
这句是说:定义了一个结构体变量的指针,以后你可以直接用Polynomial来代表struct PolyNode *这个数据.
2. typedef struct PolyNode{
int coef;
int expon;
Polynomial link; }

这个也是一个定义,定义了一个结构体变量,简化了结构体变量的定义。你可以用PolyNode来代表整个结构体变量。

再来说一下他们之间的联系: 第一个是定义的一个简化的指针变量:struct PolyNode * ; 第二个定义了一个简化的结构体变量PolyNode来代替:typedef struct PolyNode;

综上是否可理解为:指针变量Polynomial具有结构体PolyNode的结构,即L=(Polynomial)malloc(sizeof(PolyNode))这样就生成一个新的PloyNode结点。


3.3 二叉树的遍历


1.运用递归函数进行遍历


  1. 引用块内容


  2. 这里写图片描述


  3. 这里写图片描述

    • 三种遍历算法的遍历路径是一样的,只是访问各节点的时机不同。
      这里写图片描述

2.非递归遍历(使用

这里写图片描述
>

问:
讨论3.4 如何用堆栈实现后序遍历的非递归程序?
我们前面看到,借助堆栈可以实现前序遍历、中序遍历的非递归程序,而且两者的程序结构几乎一样。
答:
不行,不能用前面同样的结构进行对比处理。
可以利用两个堆栈和一个指针进行处理。

3.层序遍历(使用

这里写图片描述

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

就是把数据push到栈A,再把栈A的数据依次poppush到栈B。那么先入栈A的数据就会移到栈B的顶部,对栈B进行pop时就会先出栈B。恰恰符合了队列的本质——First in First out

把数据都enqueue到队列A,如果你想取出最后一个enqueue的数据(即Last in),那么就把队列A的数据依次dequeueenqueue到队列B,但是不能全部移到队列B,要留下一个。这一个就是你需要的数据把它dequeue出来,就会得到Lastin的数据。这个过程,相当于对栈进行了依次pop操作,符合——Last in First out。需要再次pop的时候,按照同样的方法,只需将对A、B的操作交换就可以了。


4.二叉树的应用

例1:输出二叉树中的 叶子结点。
这里写图片描述

例2. 求二叉树的高度
这里写图片描述

例3. 由 任意两种 遍历序列确定二叉树,对么?


3.4 小白专场:树的同构


这里写图片描述

程序:

//author: Paul-Huang
//data: 18/6/2017

#include<stdio.h>
#include <stdlib.h>
#include<process.h>//引入头文件

#define MaxTree 10
#define Null -1 
#define  ElementType char 
#define  Tree int

//建立动态链表
struct TreeNode
{
    ElementType node;
    Tree Left;
    Tree Right;
}Tree1[MaxTree],Tree2[MaxTree];

Tree BuildTree(struct TreeNode T[]);
int Isomprphic(Tree root1, Tree root2);

int main()
{
    Tree R1, R2;
    R1 = BuildTree(Tree1);
    R2 = BuildTree(Tree2);
    if (Isomprphic(R1, R2)){
        printf("Yes\n");
    }
    else{
        printf("No\n");
    }

    system("pause");//暂停往下执行 按下任意键继续
    return 1;
}

Tree BuildTree(struct TreeNode T[])
{
    Tree check[MaxTree], Root = Null; //root = Null 空树则返回Null
    char cl, cr; 
    int i,N;
    scanf("%d\n",&N);
    if (N) {
        for ( i = 0; i < N; i++)
            check[i] = 0;
        //输入数值,并建立链表
        for (i = 0; i < N; i++)
        {
            scanf("%c %c %c\n", &T[i].node, &cl, &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;
    }
    return Root;

}


int Isomprphic(Tree root1, Tree root2)
{
    if ((root1 == Null) && (root2 == Null))/* both empty */
        return 1;
    if (((root1 == Null) && (root2 != Null)) || ((root1 != Null) && (root2 == Null)))
        return 0;/* one of them is empty */

    if (Tree1[root1].node != Tree2[root2].node)
        return 0; /* roots are different */

    if ((Tree1[root1].Left == Null) && (Tree2[root2].Left == Null))
        /* both have no left subtree */
        return Isomprphic(Tree1[root1].Right,Tree2[root2].Right);

    if ((Tree1[root1].Right == Null) && (Tree2[root2].Right == Null))
        /* both have no right subtree */
        return Isomprphic(Tree1[root1].Left, Tree2[root2].Left);

    if ((Tree1[root1].Left != Null) && (Tree2[root2].Left != Null) &&
        ((Tree1[Tree1[root1].Left].node) == (Tree2[Tree2[root2].Left].node)))
        /* no need to swap the left and the right */
        return (Isomprphic(Tree1[root1].Left, Tree2[root2].Left)&&
        Isomprphic(Tree1[root1].Right,Tree2[root2].Right));
    else/* need to swap the left and the right */
        return (Isomprphic(Tree1[root1].Left, Tree2[root2].Right) &&
            Isomprphic(Tree1[root1].Right, Tree2[root2].Left));

}

这里写图片描述

#include<stdio.h>
#include<iostream>

#define MaxTree 10
#define ElementType int
#define Tree int 
#define Null -1

struct TreeNode{
    ElementType Element;
    Tree left;
    Tree right;
}T[MaxTree];

Tree BulidTree(struct TreeNode T[]);
void FindLeave(Tree R);

int main()
{
    Tree R;
    R = BulidTree(T);
    FindLeave(R);
    //system("pause");//暂停往下执行 按下任意键继续
    return 0;
}

Tree BulidTree(struct TreeNode T[])
{
    int i,N;
    char cl,cr;
    Tree check[MaxTree],Root = Null;
    scanf("%d\n",&N);
    for(i=0;i<N;i++)check[i] = 0;
    if(N)
    {
        for(i=0;i<N;i++)
        {
            T[i].Element = i;
            scanf("\n%c %c",&cl,&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;
    }
    return Root;
}

void FindLeave(Tree R)
{
    int leaves = 0;
    int queue[MaxTree];
    int front = 0,rear = 0;

    queue[rear++] = R;
    while(rear - front)
    {

        if((T[queue[front]].left != -1)) 
            queue[rear++] = T[queue[front]].left;       //左边如果不为空,加入队列
        if((T[queue[front]].right != -1)) 
            queue[rear++] = T[queue[front]].right;      //右边如果不为空,加入队列

        if((T[queue[front]].left == -1)&&
            (T[queue[front]].right == -1))
        {   
            if(leaves) printf(" ");
            printf("%d",queue[front]);
            ++leaves;
        }                                   //左右两边为空,则是叶子节点,输出
        front++;

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值