数据结构与算法学习笔记5:二叉树

  • 树:一对多

  • img

    A为根root,FJCHI为叶子,BCDE为中间结点,AC连接的“—”称作

+img

从A第一层往下到GHIJ为第四层,该树的**高度(深度)**为4,但对于树上的某个结点来说,深度和高度不是一个概念,比如B的高度从下往上看是3,B的深度从上往下看是2

  • img

    **度:**当前结点有几个子节点,比如D的度是3,B的度是1,C的度是2

    **路径长度:**看路径上有几条边,如A-B-D-G的路径长度是3

二叉树

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

  • img

    上图中BDGHI就是A的左子树,CEFJ就是A的右子树


二叉树的特性:
  • i i i 层上最多 2 i − 1 2^{i-1} 2i1 个节点 。(i>=1)

  • 二叉树中如果深度为 k k k,那么最多 2 k − 1 2^k-1 2k1 个节点。(k>=1)

    • 最多也就是二叉树上的结点都是满的(满二叉树)
  • 总结点个数: S = n 0 + n 1 + n 2 = 0 × n 0 + 1 × n 1 + 2 × n 2 S=n_0+n_1+n_2=0×n_0+1×n_1+2×n_2 S=n0+n1+n2=0×n0+1×n1+2×n2

    得到: n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1 n 0 n_0 n0表示度数为0的节点数, n 2 n_2 n2表示度数为2的节点数。

  • 在完全二叉树中,具有n个节点的完全二叉树的深度为 [ l o g 2 n ] + 1 [log_2n]+1 [log2n]+1,其中 [ l o g 2 n ] [log_2n] [log2n]是向下取整。


满二叉树 vs 完全二叉树

(二者都可以采用顺序存储的底层结构出现)

  • 满二叉树:结点必须全满(例如A BC DEFG)
  • 完全二叉树:只有最后一层可以有空缺且必须从右往左连续空缺 (例如A BC DE)

完全二叉树的特性:
  • 对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性:

(1) 若$ i=1 $,则该结点是二叉树的根,无双亲
(2) 若 2 i > n 2i>n 2i>n,则编号为 i i i 的结点无左孩子;否则,编号为 $ 2i $ 的结点为编号左孩子结点;
(3) 若 2 i + 1 > n 2i+1>n 2i+1>n,则编号为 i i i 的结点无右孩子;否则,编号为 2 i + 1 2i+1 2i+1 的结点为其右孩子结点。
(4)父亲结点的范围是 1 到 n 2 1到\frac{n}{2} 12n

  • 对含 n 个结点的完全二叉树从上到下且从左至右进行 0 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性:

(1) 若 i = 0 i=0 i=0,则该结点是二叉树的根,无双亲
(2) 若 2 i + 1 ≥ n 2i+1≥n 2i+1n,则该结点无左孩子;否则,编号为 2 i + 1 2i+1 2i+1 的结点为编号左孩子结点;
(3) 若 2 i + 2 ≥ n 2i+2≥n 2i+2n,则该结点无右孩子结点;否则,编号为 2 i + 2 2i+2 2i+2 的结点为其右孩子结点。
(4)父亲结点的范围是 0 到 n 2 − 1 0到\frac{n}{2}-1 02n1


一个有124个叶子结点的完全二叉树,总结点最多是多少个?

  • n 0 n_0 n0=124, n 2 n_2 n2=123

  • 完全二叉树中,度为1的结点只有两种情况,一个是1一个是0

  • 所以,总结点最多 = 124 + 123 + 1 = 248 =124+123+1=248 =124+123+1=248

二叉搜索树 / 排序二叉树 (Binary Search Tree)

每个结点左子树上所有结点的值都小于该结点的值,右子树上所有结点的值都大于该结点的值。如:

img

  • 极端情况下,当BST退化为链表的情况,其搜索的复杂度就不再具备优势了,树的平衡化也就出现啦
  • 常见的平衡二叉搜索树有:
    • AVL树(平衡二叉树)
      • 平衡二叉树也称平衡二叉搜索树,在二分搜索树的基础上,平衡二叉树需要满足两个条件:

        • 1、它的左右两个子树的高度差的绝对值不超过1
        • 2、左右两个子树都是一棵平衡二叉树
          img
          img
    • 红黑树(运用最多,后续再做详细讨论)
    • Treap

二叉树的创建(粗暴版)
//最简单粗暴基础的二叉树创建~(傻瓜式一个个结点加)

#include <stdio.h>
#include <stdlib.h>

typedef struct Node{
    int nValue;
    struct Node *pLeft;
    struct Node *pRight;
}BinaryTree;

BinaryTree* CreateBinaryTree(){
    //根
    BinaryTree* Root = NULL;
    Root = (BinaryTree*)malloc(sizeof(BinaryTree));
    Root->nValue = 1;
    //根的左
    Root->pLeft = (BinaryTree*)malloc(sizeof(BinaryTree));
    Root->pLeft->nValue = 2;
    //根的右
    Root->pRight = (BinaryTree*)malloc(sizeof(BinaryTree));
    Root->pRight->nValue = 3;
    //根的左的左
    Root->pLeft->pLeft = (BinaryTree*)malloc(sizeof(BinaryTree));
    Root->pLeft->pLeft->nValue = 4;
    Root->pLeft->pLeft->pLeft = NULL;
    Root->pLeft->pLeft->pRight = NULL;
    //根的左的右
    Root->pLeft->pRight = (BinaryTree*)malloc(sizeof(BinaryTree));
    Root->pLeft->pRight->nValue = 5;
    Root->pLeft->pRight->pLeft = NULL;
    Root->pLeft->pRight->pRight = NULL;

    return Root;
}

int main()
{
    BinaryTree*BinaryTree1 = CreateBinaryTree();
    return 0;
}
遍历
image-20220426184919150
深度
前序遍历:
  • 根/左子树/右子树 (如上图应为 1 2 4 5 7 8 3 6)

  • 代码:

    void PreorderTraversal(BinaryTree *pTree){
        if (pTree == NULL)  return;
        //打印
        printf("%d",pTree->nValue);
        //左子树
        PreorderTraversal(pTree->pLeft);
        //右子树
        PreorderTraversal(pTree->pRight);
    }//递归写法
    
  • ① 辅助栈

  • ② 遍历

    • 1)结点非空,打印,保存,找左侧
    • 2)弹出
    • 3)处理右侧
  • 代码:

    void PreorderTraversal(BinaryTree *pTree){
        if (pTree == NULL)  return;
        //栈
        stack<BinaryTree*> s;
    
        while (1) {
            while (pTree) {
                //输出
                cout<<pTree->nValue<<"  ";
                //保存
                s.push(pTree);
                //左
                pTree = pTree->pLeft;
            }
            if (s.empty()) break;
            //弹出
            pTree = s.top();
            s.pop();
            //右侧
            pTree = pTree->pRight;
        }
    }//非递归实现的前序遍历
    
中序遍历:
  • 左子树/根/右子树(如上图应为4 2 7 5 8 1 3 6)

  • 代码:

    void InorderTraversal(BinaryTree *pTree){
        if (pTree == NULL)  return;
        //左子树
        InorderTraversal(pTree->pLeft);
        //打印
        printf("%d",pTree->nValue);
        //右子树
        InorderTraversal(pTree->pRight);
    }//递归写法
    
  • ① 辅助栈

  • ② 遍历

    • 1)结点非空,保存,找左侧
    • 2)弹出,打印
    • 3)处理右侧
  • 代码:

    void InorderTraversal(BinaryTree *pTree){
        if (pTree == NULL)  return;
        //栈
        stack<BinaryTree*> s;
    
        while (1) {
            while (pTree) {
                //保存
                s.push(pTree);
                //左
                pTree = pTree->pLeft;
            }
            if (s.empty()) break;
            //弹出
            pTree = s.top();
            //输出
            cout<<pTree->nValue<<"  ";
            s.pop();
            //右侧
            pTree = pTree->pRight;
        }
    }//非递归实现的中序遍历
    
后序遍历:
  • 左子树/右子树/根(如上图应为4 7 8 5 2 6 3 1)

  • 代码:

    void PostorderTraversal(BinaryTree *pTree){
          if (pTree == NULL)  return;
          //左子树
          PostorderTraversal(pTree->pLeft);
          //右子树
          PostorderTraversal(pTree->pRight);
          //打印
          printf("%d",pTree->nValue);
      }//递归写法
    
    • ① 辅助栈

    • ② 遍历

      • 1)结点非空,保存,找左侧
      • 2)栈顶元素有无右侧
        • 有未处理,则处理右侧
        • 有已处理 / 或无,则弹出打印
      • 如何判断右侧是否被处理?(可以标记~)
        • 左右根的顺序下,如果右被处理了,那下一个要处理的就是该节点的根,也就是说,去判断栈顶元素结点的右子节点是否为上次打印的结点,既可以判断出右子节点是否被处理。
    • 代码:

      void LevelTaversal(BinaryTree* pTree){
          if (pTree == NULL)  return;
          //栈
          stack<BinaryTree*> s;
      
          BinaryTree* pMark = NULL;
          while(1){
              while (pTree) {
                  //保存
                  s.push(pTree);
                  //找左
                  pTree = pTree->pLeft;
              }
              if (s.empty()) break;
              //栈顶结点的右侧
              if (s.top()->pRight == NULL || s.top()->pRight == pMark) {
                  //弹出 打印 标记
                  pMark = s.top();
                  s.pop();
                  printf("%d  ",pMark->nValue);
              }
              else {
                  pTree = s.top()->pRight;
              }
          }
      }//非递归实现的后序遍历
      

  • 前中后序遍历练习

  • image-20220427143413155
  • 答案:

    BAGONFRKCDM 前

    GNOABRFDMCK 中

    NOGARMDCKFB 后

  • 给前中or后中可以构造出唯一的二叉树,但只给前后就无法构造(除非比较特殊 比如红黑树啥的 目前还没学红黑树 具体后面再讨论~)

广度
层序遍历:
  • 按照从上到下,从左到右的顺序依次遍历(如下图为 B A F K C O M N G)

  • image-20220427205553813
  • ① 申请辅助队列,放待处理的结点

  • ② 遍历二叉树,根节点入队

    • 1)弹出,打印
    • 2)将其非空子节点入队排队等待处理
  • 代码:

    void LevelTaversal(BinaryTree* pTree){
        if (pTree == NULL) return;
        
        //队列
        queue<BinaryTree*> q;
        //根节点入队
        q.push(pTree);
        //遍历
        while (!q.empty()) {
            pTree = q.front();
            q.pop();
            //输出
            cout<<pTree->nValue<<"  ";
            //非空子节点入队
            if (pTree->pLeft != NULL) {
                q.push(pTree->pLeft);
            }
            if (pTree->pRight != NULL) {
                q.push(pTree->pRight);
            }
        }
    }
    

二叉树的创建(前序递归版)
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>

typedef struct Node{
    int nValue;
    struct Node *pLeft;
    struct Node *pRight;
}BinaryTree; 

//通过形参修改实参 创建数要修改根结点 所以地址传递
void CreateBinaryTree(BinaryTree **pTree){
    int nNum;
    scanf("%d",&nNum);
    if (nNum == -1) return; //设置特殊标志
    //节点添加
    *pTree = (BinaryTree*)malloc(sizeof(BinaryTree));
    (*pTree)->nValue = nNum;
    (*pTree)->pLeft = NULL;
    (*pTree)->pRight = NULL;
    //左子树
    CreateBinaryTree(&( (*pTree)->pLeft) );
    //右子树
    CreateBinaryTree(&( (*pTree)->pRight) );
}

void PreorderTraversal(BinaryTree *pTree){
    if (pTree == NULL)  return;
    //打印
    printf("%d",pTree->nValue);
    //左子树
    PreorderTraversal(pTree->pLeft);
    //右子树
    PreorderTraversal(pTree->pRight);
}//递归写法

int main()
{
    BinaryTree *pTree = NULL;
    CreateBinaryTree(&pTree);
    PreorderTraversal(pTree);
    return 0;
}
如何创建完全二叉树
  • ① 数据放入结构体数组; ② 然后进行左右关系关联(基于完全二叉树的特性)
  • 示意图如下:
image-20220427163559234
  • 代码:

    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct Node{
        int nValue;
        struct Node *pLeft;
        struct Node *pRight;
    }BinaryTree; 
    
    BinaryTree *CreateCompleteBT(int arr[], int nLength){
        if (arr == NULL || nLength <= 0) return NULL;
    
        //空间申请
        BinaryTree *pCCompleteBT = NULL;
        pCCompleteBT = (BinaryTree*)malloc(sizeof(BinaryTree)*nLength);
    
        //赋值
        int i;
        for (i = 0; i < nLength; i++) {
            pCCompleteBT[i].nValue = arr[i];
            pCCompleteBT[i].pLeft = NULL;
            pCCompleteBT[i].pRight = NULL;
        }
    
        //父子关系关联
        for (i = 0; i <= (nLength / 2) - 1; i++) {
            if (2 * i + 1 < nLength) {
                pCCompleteBT[i].pLeft = &pCCompleteBT[2 * i + 1];
            }
            if (2 * i + 2 < nLength) {
                pCCompleteBT[i].pRight = &pCCompleteBT[2 * i + 2];
            }
        }
        return pCCompleteBT;
    }
    
    void PreorderTraversal(BinaryTree *pTree){
        if (pTree == NULL)  return;
        printf("%d",pTree->nValue);
        PreorderTraversal(pTree->pLeft);
        PreorderTraversal(pTree->pRight);
    }
    
    int main()
    {
        BinaryTree *pTree = NULL;
        int arr[] = {1,2,3,4,5};
        pTree = CreateCompleteBT(arr, 5);
        PreorderTraversal(pTree);
        return 0;
    }
    

图源网络,侵权联系删除。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

97Marcus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值