初试811-数据结构(C语言)

第五章:树和二叉树🌲🌳


知识点:

  1. 树的概念
    树的性质:
    • 考题:一些概念

      • 树的路径长度是从树根到每个结点的路径长度的总和

      • P131,t6

      • P131,t10

        • ‼️重要性质:n个结点的树具有n-1条边,那么对于每一棵树,其结点数比边数多1,结点树比边数多n,就有n棵树。

  2. 二叉树

    • 概念

      • 满二叉树与完全二叉树的异同

        • 正则二叉树

    • 性质

      • 高度

      • 考题

        • 第i结点的左孩子,不一定为2i,因为未必存在

        • ⚠️注意:树的性质难题,往往是给定一些条件,让你确定某些说法是否正确,这种题一般选项都难以判断,解决办法是从定义下手,回想该树的定义,然后从特殊的情况下手,比方说根节点,既满足原本定义,又符合题目要求,即可选出答案

          • P139,t18

        • 性质的应用,综合题:P140,t4、t5、t6

    • 遍历

      • 与各表达式的异同

        • 应用

          • 注意⚠️:给定了一个遍历序列,无法推出具体的树

            • 缘由

      • 南邮的话需要了解,遍历的递归算法与非递归算法都要学会

      • 遍历的考题:P155(t8、t9、t20、t21)

    • 存储

      • 注意二叉树的顺序存储结构,只适合完全二叉树 如果存储非完全二叉树,需要用isEmpty来判断是否有左右孩子

        • 存储非完全二叉树的缺陷,太浪费空间

  3. 二叉树、树、森林

    • 主要考点(转化为二叉树)

      • “左孩子,右兄弟“

      • 重要性质
        1、分支节点的最右孩子一定是叶子结点
        2、森林转化的二叉树,最右边是叶子结点
        3、左指针为空的没有孩子(叶节点)

  4. 哈夫曼树

    • 主要考点

      • 重要性质

        • 结点为奇数2n-1(合并n-1次)

        • 每个子树,对应左右子树都可以任意看成0/1,即固定一部分,看后续有没有违反
          P198:t18

  5. 线索二叉树

    • 中序线索二叉树

          • 代码:⚠️都别忘了处理最后一个结点

            • 中序

            • 先序线索化,需要注意判断ltag==0

    • 进阶:学会写所有遍历的算法,递归的非递归的,线索化代码,手推前中后序线索化,找前驱后继的过程,包括线索二叉树的遍历过程,可以实现低时间复杂度

      • 并不是每个结点通过线索就能直接求出直接前驱和后继:
        前序:找前驱麻烦 后序:找后继麻烦(都需要知道父节点,三叉链表)

        2.后序线索化,无法通过线索遍历所有结点,需要用到栈的支持(🐔💧)
    • 线索二叉树:为一种物理结构
      二叉树为一种逻辑结构,但线索二叉树是加上线索后的链表线性表结构,即它是二叉树在计算机内部的一种存储结构,所以是一种物理结构

有序树:

树中任意节点的 子结点之间有顺序关系,这种树称为有序树

无序树:

树中任意节点的 子结点之间没有顺序关系,这种树称为无序树,也称为自由树

拓展 :递归

本章递归遍历极其重要‼️,是解决树问题的关键⚠️

  • 递归可视化过程,对写出递归代码帮助大


Q&A:

1、对于三个结点A、B和C,可分别组成多少不同的无序树、有序树和二叉树?

可以组成 9 种无序树,分别是:

  • 1个父节点,2个子结点的情况有 3 种
  • 链式结构有 6 种(因为链式结构相互之间都是父子关系,所以有6种不同的组合)

有 12 种有序树,分别是:

  • 1个父节点,2个子结点的情况有 6 种(子树可以交换位置)
  • 链式结构有 6 种

组成的二叉树,30种,如下图所示每个6种(3!)

    A
   / \
  B   C        //第一种


    A
   /
  B
 /
C             //第二种


    A
     \
      B
       \
        C    //第三种


    A
   /
  B
   \
    C        //第四种


    A
     \
      B
     /
    C        //第五种

2、如果哈夫曼树T有n₀个叶子结点,那么,树T有多少个结点?要求给出求解过程。  

  • 首先哈夫曼树,属于二叉树,即存在性质n₀=n₂+1,且哈夫曼树没有度为1的结点。
  • 故n=2n₂+1=2(n₀-1)+1,即树T有2n₀-1个结点

 3、设一棵完全二叉树采用顺序存储结构,保存在一维数组A中。试设计一个递归算法,复制该完全二叉树,得到一棵新的采用普通二叉链表存储的二叉树。设二叉链表的每个结点有3个域:lChild,rChild和element。算法返回所构造的新二叉树的根结点地址。

typedef struct TreeNode{    //结构体
    int element;
    TreeNode *lChild;
    TreeNode *rChild;

    TreeNode(int val) : element(val), lChild(nullptr), rChild(nullptr) {}
}TreeNode,*Tree;


Tree copyBuild(int A[],int n,int index){
    if(index>=n) //如果下标超出数组范围return NULL
         return null;
    
    //创建树链结点
    TreeNode *TNode = new TreeNode(A[index]);
    
    //递归构造
    Tnode->lChild=copyBuild(A,n,index*2+1);
    Tnode->rChild=copyBuild(A,n,index*2+2);
    
    //返回根结点
    return Tnode;
}


4、已知二叉树以二叉链表存储,是编写算法输出二叉树中值为x的结点的所有祖先结点,假设值为x的结点不超过1个。

bool findAllParents(int x, LinkTree *T) {
    if (T == null)
        return false;  // 空树返回,寻找失败

    if (T->data == x)
        return true;  // 找到目标直接返回true

    // 检查左子树或右子树中是否能找到目标节点
    if ((T->data > x && findAllParents(x, T->lChild)) || 
        (T->data < x && findAllParents(x, T->rChild))) {
        print(T);  // 打印路径祖先节点
        return true;  // 找到后返回true,停止继续递归
    }

    return false;  // 如果在当前节点及其子树中未找到目标节点,返回false
}

5、设计一个算法,对二叉树进行按从上到下、从左到右的层次遍历。

//递归形式
void levelSearch(LinkTree *T, Queue &Q) {
    if (T == null)
        return;  // 是空树,就返回

    print(T);  // 打印当前节点

    // 将左右子树入队,前提是子树非空
    if (T->lChild != null)
        Q.Enqueue(T->lChild);
    if (T->rChild != null)
        Q.Enqueue(T->rChild);

    // 队列非空,一个个出队,递归
    if (!Q.IsEmpty()) {
        LinkTree *nextNode = Q.Dequeue();
        levelSearch(nextNode, Q);
    }
}

//非递归,迭代形式,特别适用于广度优先遍历
void levelSearch(LinkTree *T) {
    if (T == null)
        return;  // 空树直接返回

    Queue Q;
    Q.Enqueue(T);  // 根节点入队

    while (!Q.IsEmpty()) {
        LinkTree *current = Q.Dequeue();  // 出队一个节点
        print(current);  // 打印当前节点

        // 将当前节点的左右子节点入队
        if (current->lChild != null)
            Q.Enqueue(current->lChild);
        if (current->rChild != null)
            Q.Enqueue(current->rChild);
    }
}

6、设一棵二叉树T采用二叉链表表示,试设计一个算法判断二叉树T是否为完全二叉树。

//算法思想:完全二叉树,即每个父节点可以有左孩子
//,或者同时拥有左右孩子,不存在只拥有右孩子
//递归形式
bool judge(LinkTree *T, Queue &Q)
{
    if (T == null)
        return true; // 空树直接返回
    
    if (T->lChild == null && T->rChild != null)
        return false; // 存在只有右孩子的节点,非完全二叉树

    if (T->lChild != null)
        Q.Enqueue(T->lChild);
    if (T->rChild != null)
        Q.Enqueue(T->rChild);

    if (!Q.isEmpty())
    {
        LinkTree *nextTree = Q.Dequeue();
        return judge(nextTree, Q); // 递归调用时返回结果
    }

    return true; // 队列为空,表示所有节点已处理完且符合条件
}


//迭代形式
bool judge(LinkTree *T)
{
    if (T == null)
        return true; // 空树直接返回
    
    Queue Q;
    Q.Enqueue(T);

    while (!Q.isEmpty())
    {
        LinkTree *current = Q.Dequeue();
        if (current->lChild == null && current->rChild != null) // 非完全二叉树
            return false;

        if (current->lChild != null) // 陆续入非空的左右子树
            Q.Enqueue(current->lChild);
        if (current->rChild != null)
            Q.Enqueue(current->rChild);
    }

    return true; // 如果所有节点都符合条件,返回true
}
    

7、分别写出下面的算法
(1)在中序线索二叉树T中查找给定结点*p在中序序列中的前驱和后继。

//找到以p为根的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p){
    //循环找到最左下结点(不一定是叶结点)
    while(p->ltag==0)    p=p->lchild;
    return p;
}

//在中序线索二叉树中找到结点p的后继结点
ThreadNode *Nextnode(ThreadNode *p){
    //右子树中最左下的结点
    if(r->rtag==0) return Firstnode(p->rChild);
    else return p->rChild;     //rtag==1直接返回后继
}

-------------------------------------------------
//找到以p为根的子树中,最后被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p){
    //循环找到最右下结点(不一定是叶结点)
    while(p->rtag==0)    p=p->rchild;
    return p;
}

//在中序线索二叉树中找到结点p的前驱结点(前驱即右下)
ThreadNode *Prenode(ThreadNode *p){
    //左子树中最右下的结点
    if(r->ltag==0) return Firstnode(p->lChild);
    else return p->lChild;     //ltag==1直接返回前驱
}

(2)在先序线索二叉树T中查找给定结点*p在先序序列中的后继。


//在先序线索二叉树中找到结点p的后继结点
//ltag==0,有左子树,后继就是其左子树的根
//ltag==1,即没有左子树,根据先序遍历NLR,即后继就是右子树的根
ThreadNode *Nextnode(ThreadNode *p){
    if(r->ltag==0) return p->lChild;
    else return p->rChild;     //rtag==1直接返回后继
}

(3)在后序线索二叉树T中查找给定结点*p在后序序列中的前驱。


//在后序线索二叉树中找到结点p的前驱结点
//1)ltag==0,有左子树,根据LRN,前驱结点比较复杂需要分析左右子树
//若右子树存在,即内判断rtag==0,寻找右子树最后一个后序遍历的结点,即右子树的根
//若右子树不存在,即rtag==1,寻找左子树最后一个后序遍历的结点,即左子树的根
//2)ltag==1,即没有左子树,根据线索化定义,其前驱就是lChild



ThreadNode *Prenode(ThreadNode *p) {
    if (p->ltag == 0) {  // 如果p有左子树
        if (p->rtag == 0) {  // 如果p有右子树
            return p->rchild;  // 右子树根结点即为前驱
        } else {  // 如果p没有右子树
            return p->lchild;  // 左子树根结点即为前驱
        }
    } else {
        return p->lchild;  // 没有左子树,直接返回lchild作为前驱
    }
}

8、假设二叉树以二叉链表存储,设计一个算法,求其指定的某一层k(k>1)的叶子结点个数

//k>1,传入参数即是根结点时,第一层
int N0 = 0;    //全局变量

void countN0(LinkTree *T, int k) {
    if (T == nullptr) {
        return; // 空节点直接返回
    }

    if (k == 1) {
        // 如果当前层数是目标层,且是叶子节点
        if (T->lChild == null && T->rChild == null) {
            N0++;
        }
        return;
    }

    // 递归处理左子树和右子树,层数减1
    countN0(T->lChild, k - 1);
    countN0(T->rChild, k - 1);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值