数据结构——树

文章目录

二叉树和BST

树与二叉树

基本概念

树是一种非线性结构,其严格的数学定义是:如果一组数据中除了第一个节点(第一个节点称为根节点,没有直接前驱节点)之外,其余任意节点有且仅有一个直接前驱,有零个或多个直接后继,这样的一组数据形成一棵树。这种特性简称为一对多的逻辑关系。即用于描述具有层次关系,类似组织架构关系的一种数据结构。

树的组成:根,分支,叶子

常见例子

日常生活中,很多数据的组织形式本质上是一棵树。比如一个公司中的职员层级关系,一个学校中的

院系层级关系,淘汰赛中的各次比赛队伍,一个家族中的族谱成员关系等,这些都是树状逻辑结构。

由于树状结构表现出来都是具有层次的,因此也被称为层次结构。

相关术语

通常,在逻辑上表达一棵抽象的树状结构的时候,习惯于将树根放在顶部,树枝树杈向下生长,如下图所示。

对于一棵树来说,有如下基本术语:

  1. 结点:

树中的元素 及其子树

  1. 根(root):

树的第一个节点,没有直接前驱。如上图中的A。

  1. 双亲节点**/**父节点(parent):

某节点的直接前驱称为该节点的双亲节点,或成为父节点。例如上图中A是B的父节点。

  1. 孩子节点**/**子节点(child):

某节点的直接后继称为该节点的孩子节点。例如上图中B、C、D均为A的孩子节点。

  1. 节点的层次(level):

根节点所在的层次规定为第1层,其孩子所在的层次为第2层,后代节点以此类推。比如上图中节点E的层次是3。

  1. 节点的度(degree):

一个节点拥有的孩子节点的总数,称为该节点的度。比如上图中节点B的度为2。

  1. 叶子(leaf):

一棵树中度等于0的节点,被称为叶子,又称为终端节点。比如上图中K、L、F、G、M、I、J均为

叶子。

  1. 树的高度(height):

一棵树中所有节点的层次的最大值,称为这棵树的高度,又称为树的深度。比如上图的树的高度为4。

  1. 有序树与无序树:

一棵树中,如果某个节点的孩子节点之间是有次序的,则称这棵树为有序树,反之称为无序树。

二叉树

在各种不同的树状结构中,最常见也最重要的是二叉树(Binary Tree),下面是二叉树的定义:

  • 有序树
  • 任意节点的度小于等于2

比如如下这棵树就是一棵二叉树。其中8是根节点,14是10的右孩子(因为二叉树是有序树,因此严格区分左右),而13则是14的左孩子。

为了方便对二叉树进行操作,通常会对一棵它进行标号:从上到下,从左到右进行标号:

注意: 没有孩子节点的地方也要标出来

对于二叉树而言,有如下特性:

  1. 第𝑖层上,最多有2𝑖−1个节点。
  2. 高度为𝑘的二叉树,最多有2𝑘−1个节点。
  3. 假设叶子数目为𝑛0,度为2的节点数目为𝑛2,则有:

二叉树的一般结构:

  • 满二叉树

一棵深度为k,且有92^(k-1)$个结点的二叉树,称为满二叉树。这种树的特点是每一层上的结点数都是最大结点数。

简单理解: 除了叶子节点之外,其余节点的度都为2;其特点是: 如果深度为 K,则节点数为 2^K-1。

  • 完全二叉树

在一棵二叉树中,除最后一层外,若其余层都是满的,或者最后一层是满的,或者是最后一层在右边缺少连续若干结点,则此二叉树为完全二叉树。

简单理解:除最后一层叶子节点外。是一颗满二叉树,最后一层由右向左有连续缺省的0个,1个或多个节点。

  • 二叉搜索树(BST)

特点:

  1. 如果节点具有左子树,则左子树上所有节点都不大于该节点的值;
  2. 节点具有右子树,则右子树上所有节点都不小于该节点的值;
  3. 子树又是二叉搜索数

二叉搜索树(BST

二叉树(BST)的组成

  • 根指针:指向根节点的指针变量

  • 节点:

    • 数据域 (存储的实际数据)
    • 指针域 (左,右指针)

    结构设计:

    c 复制代码
    typedef int data_t;
    typedef struct _node
    {
        data_t data; // 数据域
        struct _node *left; // 左子树指针
        struct _node *right;// 右子树指针
    }NODE;

二叉树(BST)的算法

  • 创建二叉树

    c 复制代码
    int btree_create(NODE** root,data_t data);
  • 二叉树数据添加

    c 复制代码
    int btree_add(NODE** root,data_t data); 

    示例图:

  • 二叉树数据遍历

    • 前序遍历(先序遍历,即 根左右 ))

      c 复制代码
      void Preorder(const NODE* root); 1

      前序遍历通俗的说就是从二叉树的根结点出发,先输出根结点数据,然后输出左结点,最后输出右结点的数据。

从根结点出发,则第一次到达结点A,故输出A;继续向左访问,第一次访问结点B,故输出B;按照同样规则,输出D,输出H;当到达叶子结点H,返回到D,此时已经是第二次到达D,故不在输出D,进而向D右子树访问,D右子树不为空,则访问至I,第一次到达I,则输出I;I为叶子结点,则返回到D,D左右子树已经访问完毕,则返回到B,进而到B右子树,第一次到达E,故输出E;向E左子树,故输出J;按照同样的访问规则,继续输出C、F、G。

前序遍历输出结果:ABDHIEJCFG

    • 中序遍历(即 左根右 )
    c 复制代码
    void Midorder(const NODE* root);

    中序遍历通俗的说就是从二叉树的根结点出发,先输出左结点数据,然后输出根结点,最后输出右结点的数据。

    从根结点出发,则第一次到达结点A,不输出A,继续向左访问,第一次访问结点B,不输出B;继续到达D,H;到达H,H左子树为空,则返回到H,此时第二次访问H,故输出H;H右子树为空,则返回至D,此时第二次到达D,故输出D;由D返回至B,第二次到达B,故输出B;按照同样规则继续访问,输出J、E、A、F、C、G;

    中序遍历输出结果:HDIBJEAFCG

    • 后序遍历(即 左右根 )

      c 复制代码
      void Postorder(const NODE* root);

      后序遍历通俗的说就是从二叉树的根结点出发,先输出左结点数据,然后输出右结点,最后输出根结点的数据。

      从根结点出发,则第一次到达结点A,不输出A,继续向左访问,第一次访问结点B,不输出

      B;继续到达D,H;到达H,H左子树为空,则返回到H,此时第二次访问H,不输出H;H右子树为空,则返回至H,此时第三次到达H,故输出H;由H返回至D,第二次到达D,不输出D;继续访问至I,I左右子树均为空,故第三次访问I时,输出I;返回至D,此时第三次到达D,故输出D;按照同样规则继续访问,输出J、E、B、F、G、C,A;

      后序遍历输出为: HIDJEBFGCA

  • 二叉树数据查询

    c 复制代码
    NODE* btree_find(const NODE* root,data_t data);
    1. 从根结点出发

    2. 如果比根节点小,那么就去其左子树找

    3. 如果比根节点大就去其右子树找

    4. 找到叶子都没找到, 就代表查找失败

  • 二叉树数据更新

    c 复制代码
    int btree_update(const NODE* root,data_t old,data_t newdata);
  • 二叉树回收

    c 复制代码
    void btree_destroy(NODE** root);
  • 二叉树数据删除

    c 复制代码
    int btree_delete(NODE** root,data_t data);

    原则:将待删除的节点尽量转换为删除叶子节点,因为删除叶子节点对BST树影响是最小的;

    思路:

    ① 从根节点开始遍历BST找到待删除的节点;

    ② 对待删除的节点状态进行判断,如果节点有左子树,找到左子树中最大的节点,然后利用

    左子树中最大的节

    点数据替换待删除的节点数据,删除左子树中最大的节点;左子树中最大的节点大概率

    是叶子节点。

    ③ 如果节点只有右子树,找到右子树中最小的节点,然后 利用右子树中最小的节点数据替

    换待删除的节点数

    据,删除右子树中最小的节点;右子树中最小的节点大概率是叶子节点。

    ④ 如果待删除节点是叶子节点,直接删除。

二叉树(BST)完整实现

  • btree.h

    c 复制代码
    #ifndef __BTREE_H
    #define __BTREE_H
    typedef int data_t;
    typedef struct _node
    {
        data_t data; // 节点上的数据
        struct _node *left; // 该节点左侧子节点的地址
        struct _node *right;// 该节点右侧子节点的地址
    }NODE;
    // 创建搜索二叉树
    int btree_create(NODE** root,data_t data);
    // 二叉树数据添加
    int btree_add(NODE** root,data_t data);
    // 二叉树数据删除
    int btree_delete(NODE** root,data_t data);
    // 二叉树前序遍历
    void Preorder(const NODE* root);
    // 二叉树中序遍历
    void Midorder(const NODE* root);
    // 二叉树后序遍历
    void Postorder(const NODE* root);
    // 二叉树层序遍历
    void Levelorder(const NODE* root);
    // 二叉树数据查询
    NODE* btree_find(const NODE* root,data_t data);
    // 更新二叉树数据old 为 newdata
    int btree_update(const NODE* root,data_t old,data_t newdata);
    // 二叉树回收
    void btree_destroy(NODE** root);
    #endif
  • btree.c

    c 复制代码
    #include "btree.h"
    
    /**
     * @function:   int btree_create(NODE** root,DATA data)
     * @brief:      创建搜索二叉树(BST)
     * @argument:
     *              root:根指针地址
     *              data:存储的数据
     * @return:
     *              成功:0
     *              失败  -1
     */
    int btree_create(NODE * *root, DATA data)
    {
        // 校验根节点是否存在
        if (*root)
        {
            return -1;
        }
    
        // 创建一个节点并申请内存
        NODE *p = (NODE *)malloc(sizeof(NODE));
        if (!p)
        {
            return -1;
        }
    
        // 初始化根节点
        p->data = data;
        p->left = p->right = NULL;
    
        // 将新创建的节点作为根节点
        *root = p;
    
        return 0;
    }
    
    /**
     * @function:   int btree_add(NODE** root,DATA data)
     * @brief:      向搜索二叉树添加数据
     * @argument:
     *              root:根指针地址
     *              data:存储的数据
     * @return:
     *              成功:0
     *              失败  -1
     */
    int btree_add(NODE **root, DATA data)
    {
        // 创建一个新节点并申请内存
        NODE *pNew = (NODE *)malloc(sizeof(NODE));
        if (!pNew)
        {
            return -1;
        }
    
        // 给新节点赋初值
        pNew->data = data;
        pNew->left = NULL;
        pNew->right = NULL;
    
        // 创建一个变量p,用来记录插入位置,变量q,用来记录尾结点(叶子)指针尾随
        NODE *p = *root, *q = NULL;
    
        // 判断传进来的树是否为空
        if (!p)
        {
            // 将刚刚插入的节点作为根节点
            *root = pNew;
            return 0;
        }
    
        // 树不为空,并且可能会有多个子节点
        while (p)
        {
            q = p;
    
            // 校验,如果新节点的值小于当前节点的值,就指向left,否则指向right,关于等于的情况,自行划分
            if (memcmp(&data, &(p->data), sizeof(DATA)) < 0)
            {
                p = p->left;
            }
            else
            {
                p = p->right;
            }
        }
    
        // 执行完上面循环,就找到了插入节点的前一个节点的位置q
    
        if (memcmp(&data, &(q->data), sizeof(DATA)) < 0)
        {
            q->left = pNew;
        }
        else
        {
            q->right = pNew;
        }
    
        return 0;
    }
    
    /**
     * @function:   void Preorder(const NODE* root)
     * @brief:      二叉树数据遍历(前序遍历-根左右)
     * @argument:
     *              root:根指针地址
     * @return:     无
     *
     */
    void Preorder(const NODE *root)
    {
        // 此处判断不仅仅用来校验树是否为空,同时也作为递归的跳出的条件
        if (!root)
        {
            return;
        }
        // 输出我们找到的数据
        printf("%4d", root->data); // 根
        Preorder(root->left);      // 左
        Preorder(root->right);     // 右
    }
    
    /**
     * @function:   void Midorder(const NODE* root)
     * @brief:      二叉树的遍历(中序遍历-左跟右)
     * @argument:
     *              root:根指针地址
     * @return:     无
     *
     */
    void Midorder(const NODE *root)
    {
        // 此处需要控制递归的跳出
        if (!root)
        {
            return;
        }
        Midorder(root->left);      // 左
        printf("%4d", root->data); // 根
        Midorder(root->right);     // 右
    }
    
    /**
     * @function:   void Postorder(const NODE* root)
     * @brief:      二叉树的遍历(后序遍历-左右根)
     * @argument:
     *              root:根指针地址
     * @return:     无
     *
     */
    void Postorder(const NODE *root)
    {
    
        // 此处需要控制递归的跳出
        if (!root)
        {
            return;
        }
    
        Postorder(root->left);     // 左
        Postorder(root->right);    // 右
        printf("%4d", root->data); // 根
    }
    
    /**
     * @function:   NODE* btree_find(const NODE* root,DATA data)
     * @brief:      二叉树数据查询
     * @argument:
     *              root:根指针地址
     * @return:     无
     */
    NODE *btree_find(const NODE *root, DATA data)
    {
        // 创建一个变量,用来接收传进来的节点
        const NODE *p = root;
    
        while (p)
        {
            if (memcmp(&data, &(p->data), sizeof(DATA)) < 0)
            // if(data < p->data)
            {
                p = p->left;
            }
            else if (memcmp(&data, &(p->data), sizeof(DATA)) > 0)
            {
                p = p->right;
            }
            else
            {
                return (NODE *)p;
            }
        }
        return NULL;
    }
    
    /**
     * @function:   int btree_update(const NODE* root,DATA old,DATA newdata)
     * @brief:      更新二叉树的数据old 为 newdata
     * @argument:
     *              root:根指针地址
     *              old:源数据
     *              newdata:目标数据
     * @return:     无
     */
    int btree_update(const NODE *root, DATA old, DATA newdata)
    {
        // 获取old数据对应的节点
        NODE *pFind = btree_find(root, old);
        // 校验
        if (!pFind)
        {
            return -1;
        }
        // 修改节点上的数据
        pFind->data = newdata;
    
        return 0;
    }
    
    /**
     * @function:   static void btree_free(NODE** root)
     * @brief:      二叉树回收
     * @argument:
     *              root:根指针地址
     * @return:     无
     */
    static void btree_free(NODE *root)
    {
    // 递归跳出的条件
        if (!root)
        {
            return;
        }
        btree_free(root->left);  // 左
        btree_free(root->right); // 右
        // 内存释放
        free(root);
    }
    
    /**
     * @function:   void btree_destroy(NODE** root)
     * @brief:      二叉树回收
     * @argument:
     *              root:根指针地址
     * @return:     无
     */
    void btree_destroy(NODE **root)
    {
        // 定义一个回收函数,递归的去回收每一个节点
        btree_free(*root);
        *root = NULL;
    }
    
    /**
     * @function:   int btree_delete(NODE** root,DATA data)
     * @brief:      二叉树数据删除
     * @argument:
     *              root:根指针地址
     *              data:源数据   source,target
     * @return:     无
     */
    int btree_delete(NODE **root, DATA data)
    {
        NODE *del = *root;    // 指向待删除的节点  p
        NODE *parent = NULL;  // 指向实际删除节点的父节点 q
        NODE *replace = NULL; // 指向实际删除的节点
    
        while (del)
        {
            if (memcmp(&(del->data), &data, sizeof(DATA)) < 0)
            {
                parent = del;
                del = del->right;
            }
            else if (memcmp(&(del->data), &data, sizeof(DATA)) > 0)
            {
                parent = del;
                del = del->left;
            }
            else // 找到了待删除的节点
            {
                if (del->left) // 待删除的节点有左子树
                {
                    // 如果有左子树,找左子树中最大的节点
                    parent = del;
                    replace = del->left;
                    // 通过一个循环,找左子树中最大的节点
                    while (replace->right)
                    {
                        parent = replace;
                        // 控制左子树的循环条件
                        replace = replace->right;
                    }
                    // 循环跳出,代表已经找到了最大的子节点
                    // 将我们找到的最大字节点的数据赋值给待删除节点
                    del->data = replace->data;
    
                    if (parent->right = = replace) // 实际删除的节点在父节点的右子树上
                    {
                        // 如果实际删除节点(replace)是叶子节点,就赋值为NULL,如果不是,就改变引用关系
                        parent->right = replace->left;
                    }
                    else
                    {
                        parent->left = replace->left;
                    }
                    free(replace);
                }
                else if (del->right) // 待删除的节点只有右子树
                {
                    // 如果仅有右子树,找右子树中最小的节点
                    parent = del;
                    replace = del->right;
                    // 通过一个循环,找右子树中最小的节点
                    while (replace->left)
                    {
                        parent = replace;
                        replace = replace->left;
                    }
                    // 数据替换
                    del->data = replace->data;
    
                    if (parent->right == replace)
                        parent->right = replace->right;
                    else
                        parent->left = replace->right;
    
                    free(replace);
                }
                else // 待删除的节点是叶子节点
                {
                    if (!parent)
                    {
                        free(del);
                        *root = NULL;
                        return 0;
                    }
                    // 解除父节点和被删节点的引用关系
                    if (parent->left == del)
                    {
                        parent->left = NULL;
                    }
                    else
                    {
                        parent->right = NULL;
                    }
                    free(del);
                }
                return 0;
            }
        }
        return -1;
    }
相关推荐
@小博的博客1 分钟前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
泉崎2 小时前
11.7比赛总结
数据结构·算法
你好helloworld2 小时前
滑动窗口最大值
数据结构·算法·leetcode
JSU_曾是此间年少3 小时前
数据结构——线性表与链表
数据结构·c++·算法
sjsjs113 小时前
【数据结构-合法括号字符串】【hard】【拼多多面试题】力扣32. 最长有效括号
数据结构·leetcode
blammmp4 小时前
Java:数据结构-枚举
java·开发语言·数据结构
昂子的博客5 小时前
基础数据结构——队列(链表实现)
数据结构
lulu_gh_yu5 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
~yY…s<#>7 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
XuanRanDev8 小时前
【每日一题】LeetCode - 三数之和
数据结构·算法·leetcode·1024程序员节