数据结构中的树:从基础概念到C++实现

数据结构中的树:从基础概念到C++实现

在这里插入图片描述

文章目录

引言

在计算机科学的广阔领域中,数据结构是构建高效算法和程序的基石。而在众多数据结构中,树(Tree)以其独特的层次结构和灵活的组织方式,成为了解决各类复杂问题的强大工具。无论是操作系统的文件管理、数据库的索引设计,还是编译器的语法分析,树结构都扮演着不可替代的角色。

本教程将深入探讨树这一重要的数据结构,从基本概念出发,逐步介绍各种类型的树及其实现方法,并通过丰富的C++代码示例,帮助读者全面理解树结构的原理和应用。无论你是计算机科学的初学者,还是希望巩固知识的专业人士,这篇教程都将为你提供系统而详尽的指导。

在接下来的内容中,我们将首先介绍树的基本概念和术语,然后探讨不同类型的树结构(如二叉树、二叉搜索树、AVL树、红黑树、B树等),最后通过C++代码实现这些树结构的常见操作(如创建、插入、删除、遍历等)。每个部分都会配以详细的解释和丰富的示例,确保读者能够透彻理解每个概念和操作。

让我们开始这段探索树结构奥秘的旅程吧!

树的基本概念

树的定义

树(Tree)是一种非线性的数据结构,它是由n(n≥0)个有限节点组成的一个具有层次关系的集合。当n=0时,称为空树。当n>0时,树具有以下特点:

  1. 有且仅有一个特定的称为根(Root)的节点
  2. 其余节点可分为m(m≥0)个互不相交的有限集T1、T2、…、Tm,其中每个集合本身又是一棵树,称为原树的子树(SubTree)

树的结构之所以称为"树",是因为它看起来像一棵倒置的树,根在上,叶在下。下图展示了一个典型的树结构:

在这里插入图片描述

树的基本术语

为了更好地理解和讨论树结构,我们需要熟悉以下基本术语:

  1. 节点(Node):树中的每个元素称为节点,它包含数据元素和指向子树的链接。

  2. 根节点(Root Node):树中最顶层的节点,它没有父节点。在上图中,A是根节点。

  3. 子节点(Child Node):如果节点A是节点B的父节点,那么节点B就是节点A的子节点。在上图中,B、C、D是A的子节点。

  4. 父节点(Parent Node):如果节点A是节点B的子节点,那么节点B就是节点A的父节点。在上图中,A是B、C、D的父节点。

  5. 兄弟节点(Sibling Node):具有相同父节点的节点互称为兄弟节点。在上图中,B、C、D互为兄弟节点。

  6. 叶节点(Leaf Node):没有子节点的节点称为叶节点或终端节点。在上图中,E、F、G、H、I、J、K、L、M、N、O、P是叶节点。

  7. 非叶节点(Non-leaf Node):有子节点的节点称为非叶节点或分支节点。在上图中,A、B、C、D是非叶节点。

  8. 节点的度(Degree):一个节点拥有的子树数量称为该节点的度。在上图中,A的度为3,B的度为2。

  9. 树的度(Degree of Tree):树中所有节点的度的最大值称为树的度。在上图中,树的度为3。

  10. 节点的层次(Level):从根开始定义,根为第1层,根的子节点为第2层,以此类推。

  11. 树的深度(Depth):树中所有节点的层次的最大值称为树的深度或高度。在上图中,树的深度为4。

  12. 森林(Forest):m(m≥0)棵互不相交的树的集合称为森林。

树与其他数据结构的比较

树结构与线性结构(如数组、链表)有着本质的区别:

  1. 线性结构

    • 第一个元素:无前驱
    • 最后一个元素:无后继
    • 中间元素:一个前驱一个后继
    • 一对一的关系
  2. 树结构

    • 根节点:无父节点,唯一
    • 叶节点:无子节点,可以有多个
    • 中间节点:一个父节点多个子节点
    • 一对多的关系

这种一对多的关系使得树结构特别适合表示具有层次关系的数据,如文件系统、组织结构等。

树的分类

根据节点的子树数量和排列方式,树可以分为多种类型:

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

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

  3. 二叉树:每个节点最多有两个子树的树称为二叉树。二叉树是最常见和最重要的树类型之一,我们将在后续章节中详细讨论。

  4. 满二叉树:所有叶节点都在同一层,且每个非叶节点都有两个子节点的二叉树称为满二叉树。

  5. 完全二叉树:除了最后一层外,其他层的节点数都达到最大值,且最后一层的节点都集中在左侧的二叉树称为完全二叉树。

  6. 平衡二叉树:任意节点的左右子树的高度差不超过1的二叉树称为平衡二叉树,如AVL树。

  7. 二叉搜索树:左子树上所有节点的值均小于根节点的值,右子树上所有节点的值均大于根节点的值的二叉树称为二叉搜索树。

  8. B树和B+树:多路搜索树,常用于数据库和文件系统中。

在接下来的章节中,我们将详细介绍这些不同类型的树结构及其实现方法。

树的存储结构

树的存储结构主要有两种:

  1. 双亲表示法:每个节点除了存储数据外,还存储其父节点的位置。

  2. 孩子表示法:每个节点除了存储数据外,还存储其所有子节点的位置。

在C++中,我们通常使用指针来实现树的节点,例如:

// 二叉树节点的基本结构
struct TreeNode {
   
    int data;           // 节点存储的数据
    TreeNode* left;     // 指向左子节点的指针
    TreeNode* right;    // 指向右子节点的指针
    
    // 构造函数
    TreeNode(int val) : data(val), left(nullptr), right(nullptr) {
   }
};

对于一般的树(非二叉树),我们可以使用动态数组或链表来存储子节点:

// 普通树节点的基本结构
struct TreeNode {
   
    int data;                       // 节点存储的数据
    vector<TreeNode*> children;     // 存储所有子节点的指针
    
    // 构造函数
    TreeNode(int val) : data(val) {
   }
};

不同类型的树结构及其C++实现

二叉树(Binary Tree)

二叉树的定义与特性

二叉树是最常见和最重要的树类型之一,它的每个节点最多有两个子节点,通常称为左子节点和右子节点。二叉树具有以下特性:

  1. 每个节点最多有两个子节点
  2. 左子节点和右子节点是有序的,不能互换
  3. 第i层的节点数最多为2^(i-1)
  4. 深度为k的二叉树最多有2^k-1个节点
  5. 对于任何一棵二叉树,如果叶节点数为n0,度为2的节点数为n2,则n0 = n2 + 1
二叉树的C++实现

在C++中,我们通常使用指针来实现二叉树的节点:

// 二叉树节点的基本结构
struct TreeNode {
   
    int data;           // 节点存储的数据
    TreeNode* left;     // 指向左子节点的指针
    TreeNode* right;    // 指向右子节点的指针
    
    // 构造函数
    TreeNode(int val) : data(val), left(nullptr), right(nullptr) {
   }
};
二叉树的类型
1. 满二叉树(Full Binary Tree)

满二叉树是指所有非叶节点都有两个子节点,所有叶节点都在同一层的二叉树。

在这里插入图片描述

2. 完全二叉树(Complete Binary Tree)

完全二叉树是指除了最后一层外,其他层的节点数都达到最大值,且最后一层的节点都集中在左侧的二叉树。

完全二叉树的特点使其非常适合用数组来存储,因为对于数组中的任意位置i,其左子节点的位置是2i+1,右子节点的位置是2i+2,父节点的位置是(i-1)/2(整数除法)。

3. 平衡二叉树(Balanced Binary Tree)

平衡二叉树是指任意节点的左右子树的高度差不超过1的二叉树。平衡二叉树的目的是确保树的深度尽可能小,从而提高查找、插入和删除操作的效率。

4. 二叉搜索树(Binary Search Tree)

二叉搜索树是一种特殊的二叉树,它具有以下性质:

  1. 左子树上所有节点的值均小于根节点的值
  2. 右子树上所有节点的值均大于根节点的值
  3. 左子树和右子树也分别为二叉搜索树

这种特性使得二叉搜索树非常适合进行查找、插入和删除操作,这些操作的平均时间复杂度都是O(log n)。

下面是一个二叉搜索树的示例:

在这里插入图片描述

二叉搜索树(Binary Search Tree)

二叉搜索树的定义与特性

二叉搜索树(BST)是一种特殊的二叉树,它的每个节点都满足以下条件:

  1. 节点的左子树中的所有节点的值都小于该节点的值
  2. 节点的右子树中的所有节点的值都大于该节点的值
  3. 左子树和右子树也都是二叉搜索树

这种特性使得二叉搜索树非常适合进行查找、插入和删除操作,这些操作的平均时间复杂度都是O(log n)。

二叉搜索树的C++实现
// 二叉搜索树的节点结构
struct BSTNode {
   
    int data;           // 节点存储的数据
    BSTNode* left;      // 指向左子节点的指针
    BSTNode* right;     // 指向右子节点的指针
    
    // 构造函数
    BSTNode(int val) : data(val), left(nullptr), right(nullptr) {
   }
};

// 二叉搜索树类
class BST {
   
private:
    BSTNode* root;  // 根节点指针
    
    // 递归插入节点
    BSTNode* insertRecursive(BSTNode* node, int val) {
   
        // 如果当前节点为空,创建新节点
        if (node == nullptr) {
   
            return new BSTNode(val);
        }
        
        // 如果值小于当前节点的值,插入到左子树
        if (val < node->data) {
   
            node->left = insertRecursive(node->left, val);
        }
        // 如果值大于当前节点的值,插入到右子树
        else if (val > node->data) {
   
            node->right = insertRecursive(node->right, val);
        }
        
        // 返回更新后的节点指针
        return node;
    }
    
    // 递归查找节点
    BSTNode* searchRecursive(BSTNode* node, int val) {
   
        // 如果当前节点为空或者找到了目标值,返回当前节点
        if (node == nullptr || node->data == val) {
   
            return node;
        }
        
        // 如果值小于当前节点的值,在左子树中查找
        if (val < node->data) {
   
            return searchRecursive(node->left, val);
        }
        // 如果值大于当前节点的值,在右子树中查找
        return searchRecursive(node->right, val);
    }
    
    // 查找最小值节点
    BSTNode* findMin(BSTNode* node) {
   
        // 最小值节点是最左边的节点
        while (node->left != nullptr) {
   
            node = node->left;
        }
        return node;
    }
    
    // 递归删除节点
    BSTNode* deleteRecursive(BSTNode* node, int val) {
   
        // 基本情况:如果树为空
        if (node == nullptr) {
   
            return nullptr;
        }
        
        // 如果值小于当前节点的值,在左子树中删除
        if (val < node->data) {
   
            node->left = deleteRecursive(node->left, val);
        }
        // 如果值大于当前节点的值,在右子树中删除
        else if (val > node->data) {
   
            node->right = deleteRecursive(node->right, val);
        }
        // 找到了要删除的节点
        else {
   
            // 情况1:叶节点(没有子节点)
            if (node->left == nullptr && node->right == nullptr) {
   
                delete node;
                return nullptr;
            }
            // 情况2:只有一个子节点
            else if (node->left == nullptr) {
   
                BSTNode* temp = node->right;
                delete node;
                return temp;
            }
            else if (node->right == nullptr) {
   
                BSTNode* temp = node->left;
                delete node;
                return temp;
            }
            // 情况3:有两个子节点
            // 找到右子树中的最小值节点(中序后继)
            BSTNode* temp = findMin(node->right);
            // 将当前节点的值替换为后继节点的值
            node->data = temp->data;
            // 删除后继节点
            node->right = deleteRecursive(node->right, temp->data);
        }
        
        return node;
    }
    
    // 递归中序遍历
    void inorderRecursive(BSTNode* node) {
   
        if (node != nullptr) {
   
            inorderRecursive(node->left);
            std::cout << node->data << " ";
            inorderRecursive(node->right);
        }
    }
    
    // 递归销毁树
    void destroyRecursive(BSTNode* node) {
   
        if (node != nullptr) {
   
            destroyRecursive(node->left);
            destroyRecursive(node->right);
            delete node;
        }
    }
    
public:
    // 构造函数
    BST() : root(nullptr) {
   }
    
    // 析构函数
    ~BST() {
   
        destroyRecursive(root);
    }
    
    // 插入节点
    void insert(int val) {
   
        root = insertRecursive(root, val);
    }
    
    // 查找节点
    bool search(int val) {
   
        return searchRecursive(root, val) != nullptr;
    }
    
    // 删除节点
    void remove(int val) {
   
        root = deleteRecursive(root, val);
    }
    
    // 中序遍历
    void inorder() {
   
        inorderRecursive(root);
        std::cout << std::endl;
    }
};
二叉搜索树的局限性

虽然二叉搜索树在平均情况下具有良好的性能,但在最坏情况下(例如,当输入数据已经排序时),二叉搜索树会退化成一个链表,此时查找、插入和删除操作的时间复杂度会退化为O(n)。为了解决这个问题,人们发明了各种平衡二叉搜索树,如AVL树和红黑树。

AVL树

AVL树的定义与特性

AVL树是一种自平衡的二叉搜索树,它在插入和删除操作后会自动调整树的结构,以保持树的平衡。在AVL树中,任意节点的左右子树的高度差不超过1。这种平衡性保证了AVL树的操作(查找、插入、删除)的时间复杂度始终保持在O(log n)。

AVL树的平衡因子

AVL树的每个节点都有一个平衡因子(Balance Factor),定义为左子树的高度减去右子树的高度。在AVL树中,每个节点的平衡因子只能是-1、0或1。

AVL树的旋转操作

当插入或删除节点导致AVL树失去平衡时,需要通过旋转操作来恢复平衡。旋转操作分为四种:

  1. 左旋(Left Rotation):当右子树的高度比左子树的高度大2时,进行左旋。
  2. 右旋(Right Rotation):当左子树的高度比右子树的高度大2时,进行右旋。
  3. 左右旋(Left-Right Rotation):先对左子节点进行左旋,再对当前节点进行右旋。
  4. 右左旋(Right-Left Rotation):先对右子节点进行右旋,再对当前节点进行左旋。
AVL树的C++实现
// AVL树节点结构
struct AVLNode {
   
    int data;           // 节点存储的数据
    AVLNode* left;      // 指向左子节点的指针
    AVLNode* right;     // 指向右子节点的指针
    int height;         // 节点的高度
    
    // 构造函数
    AVLNode(int val) : data(val), left(nullptr), right(nullptr), height(1) {
   }
};

// AVL树类
class AVLTree {
   
private:
    AVLNode* root;  // 根节点指针
    
    // 获取节点的高度
    int getHeight(AVLNode* node) {
   
        if (node == nullptr) {
   
            return 0;
        }
        return node->height;
    }
    
    // 获取节点的平衡因子
    int getBalanceFactor(AVLNode* node) {
   
        if (node == nullptr) {
   
            return 0;
        }
        return getHeight(node->left) - getHeight(node->right);
    }
    
    // 更新节点的高度
    void updateHeight(AVLNode* node) {
   
        if (node == nullptr) {
   
            return;
        }
        node->height = 1 + std::max(getHeight(node->left), getHeight(node->right));
    }
    
    // 右旋操作
    AVLNode* rightRotate(AVLNode* y) {
   
        AVLNode* x = y->left;
        AVLNode* T2 = x->right;
        
        // 执行旋转
        x->right = y;
        y->left = T2;
        
        // 更新高度
        updateHeight(y);
        updateHeight(x);
        
        // 返回新的根节点
        return x;
    }
    
    // 左旋操作
    AVLNode* leftRotate(AVLNode* x) {
   
        AVLNode* y = x->right;
        AVLNode* T2 = y->left;
        
        // 执行旋转
        y->left = x;
        x->right = T2;
        
        // 更新高度
        updateHeight(x);
        updateHeight(y);
        
        // 返回新的根节点
        return y;
    }
    
    // 插入节点
    AVLNode
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

扫地的小何尚

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

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

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

打赏作者

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

抵扣说明:

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

余额充值