【数据结构】之二叉树

二叉树是我们在数据结构中学到的第一个非线性结构,是后续学习更为复杂的树、图结构的基础。本文整理了二叉树的概念定义、基本操作、遍历算法、伪代码与代码实现以及实例说明,方便大家随时查找对应。

一、定义与基本术语

二叉树是一种树形结构 ,每个节点最多有两个子节点 ,分别称为左节点右节点

基本术语:

  • 根节点:树的顶部节点。
  • 叶节点:没有子节点的节点。
  • 父节点:某个节点的直接上级节点。
  • 子节点:某个节点的直接下级节点。
  • 兄弟节点:具有相同父节点的节点。
  • 深度:从根节点到某个节点的边的数量。
  • 高度:从某个节点到最远叶节点的边的数量。
    A (Depth: 0, Root)

/

B (Depth: 1)

/ \

C1 C2(Depth: 2)

\

D (Depth: 3, Right)

\

E (Depth: 4, Right)

/ \

F1 F2(Depth: 4)

二、主要特点

  1. 递归性质:二叉树的许多操作可以通过递归实现。
  2. 动态集合:二叉树可以用来实现动态集合,支持插入、删除和查找操作。
  3. 层次结构:二叉树具有明确的层次结构,适合表示具有层次关系的数据。

三、基本操作

1. 构建二叉树:

① 构建节点:

cpp 复制代码
struct TreeNode{
    int key;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int k): key(k),left(nullptr),right(nullptr){}
};

② 插入节点:

插入操作会根据节点的值来决定插入的位置。例如,在二叉搜索树(BST)中,插入操作遵循以下规则:

  • 如果当前节点的值大于插入值,则递归地插入到左子树。
  • 如果当前节点的值小于插入值,则递归地插入到右子树。
  • 如果当前节点的值等于插入值,则可以选择不插入或插入到左/右子树。
cpp 复制代码
TreeNode* insert(TreeNode* node, int key){
    if(node == nullptr){
        return new TreeNode(key);
    }
    if(key < node->key){
        node->left = insert(node->left, key);
    }else if(key > node->key){
        node->right = insert(node->right,key);
    }
    return node;
}

③ 构建树:

从一个空树开始,通过多次插入节点,可以逐步构建二叉树。

cpp 复制代码
TreeNode* root = nullptr;
// 如果是基于一个给定数组构建二叉树
for(int i = 0;i<length;i++){
    root = insert(root,nums[i]);
}
//手动输入构建
root = insert(root,10);
root = insert(root,4);
root = insert(root, 7);
root = insert(root, 20);
root = insert(root, 34);
root = insert(root, 16);
root = insert(root, 8);

2. 删除节点:

cpp 复制代码
TreeNode* deleteNode(TreeNode* node, int key){
    if(node == nullptr) return node;
    if(key < node->key){
        node->left = deleteNode(node->left, key);
    } else if (key > node->key) {
        node->right = deleteNode(node->right, key);
    }else{
        if(node->left == nullptr){
            TreeNode* tmp = node->right;
            delete node;
            return tmp;
        }
        else if(node->right == nullptr){
            TreeNode* tmp = node->left;
            delete node;
            return tmp;
        }
        TreeNode* temp = findMin(node->right);
        node->key = temp->key;
        node->right = deleteNode(node->right, temp->key);
    }
    return node;
}

TreeNode* findMin(TreeNode* node){
    while(node->left != nullptr){
        node = node->left;
    }
    return node;
}

3. 查找节点:

cpp 复制代码
TreeNode* searchNode(TreeNode* node, int key){
    if(node == nullptr||node->key = key) return node;
    if(key < node->key){
        return searchNode(node->left,key);
    }
    if(key > node->key){
        return searchNode(node->right, key);
    }
}

四、遍历算法

需要了解:前序遍历、中序遍历、后序遍历、层序遍历

1. 前序遍历:

A

/ \

B C

/ \ \

D E F

遍历顺序:A->B->D->E->C->F
1

/ \

2 3

/ \ \

4 5 6

/ \

7 8

遍历顺序:1->2->4->7->8->5->3->6

前序遍历的访问顺序:根节点-->左子树-->右子树

cpp 复制代码
// 以int整数型为例
vector<int> result
void preorder(TreeNode* node){
    if(node == nullptr){
        return;
    }
    // 如果题目要求的是前序遍历打印节点值,那么可以直接:
    // cout<<node->key<<" ";
    // 如果题目要求前序遍历将节点值存储到数组中,需要先定义一个vector数组(如↑),再进行存储
    result.push_back(node_key);
    preorder(node->left);
    preorder(nofe->right);
}

2. 中序遍历

A

/ \

B C

/ \ \

D E F

遍历顺序:D->B->E->A->C->F
1

/ \

2 3

/ \ \

4 5 6

/ \

7 8

遍历顺序:7->4->8->2->5->1->3->6
10

/ \

6 14

/ \ / \

4 8 12 16

遍历顺序:4->6->8->10->12->14->16

中序遍历的访问顺序:左子树-->根节点-->右子树

cpp 复制代码
vector<int> result;
void inorder(TreeNode* node) {
    if (node == nullptr) return;
    inorder(node->left);
    visit(node);
    inorder(node->right);
}

void visit(TreeNode* node){
    // cout<< node->key <<" ";
    result.push_back(node->key);
}

3. 后序遍历

A

/ \

B C

/ \ \

D E F

遍历顺序:D->E->B->F->C->A
1

/ \

2 3

/ \ \

4 5 6

/ \

7 8

遍历顺序:7->8->4->5->2->6->3->1

后序遍历的访问顺序:左子树->右子树->根节点。

cpp 复制代码
void postorder(TreeNode* node){
    if(node==nullptr) return;
    postorder(node->left);
    postorder(node->right);
    visit(node);
}
//visit函数要根据题目要求来定义,示例可参考中序遍历的定义

4. 层序遍历

A

/ \

B C

/ \ \

D E F

遍历顺序:A->B->C->D->E->F
1

/ \

2 3

/ / \

4 5 6

/

7

遍历顺序:1->2->3->4->5->6->7

层序遍历的顺序是:从上到下,从左到右

层序遍历的结果特别好写,从上往下盯齐就没问题,但是代码是最长的。。

先来看看伪代码:

LevelOrder(root)

queue = new Queue()

queue.enqueue(root)

while not queue.isEmpty()

node = queue.dequeue()

visit(node)

if node.left != null

queue.enqueue(node.left)

if node.right != null

queue.enqueue(node.right)

cpp 复制代码
void levelorder(TreeNode* root){
    if(root == nullptr) return;
    queue<TreeNode* > q; // 创建一个队列,用于存储待访问的节点
    q.push(root); // 把现在的根节点存进去
    while(!q.empty()){
        TreeNode* node = q.front(); // 取出队列的第一个节点
        q.pop(); // 将该节点从队列中移除
        visit(node);
        if (node->left != nullptr) q.push(node->left);
        if (node->right != nullptr) q.push(node->right); 
        // 这里加入队列的顺序是先左再右,队列又是FIFO结构,遍历得到的顺序也就满足了先左再右
    }
}

五、适用场景举例

  1. 表达式树:用于表示和计算算术表达式。
  2. 二叉搜索树(BST):用于实现动态集合,支持高效的插入、删除和查找操作。
  3. 哈夫曼树:用于数据压缩。
  4. :用于实现优先队列。
  5. 线索二叉树:用于高效地遍历二叉树。

六、Leetcode 二叉树 练习题汇总

1. 初级题目

题号 题目名称 知识点
94 二叉树的中序遍历 中序遍历,递归与迭代
144 二叉树的前序遍历 前序遍历,递归与迭代
145 二叉树的后序遍历 后序遍历,递归与迭代
102 二叉树的层序遍历 层序遍历,队列的应用
226 翻转二叉树 递归,树的翻转
617 合并二叉树 递归,树的合并
589 N叉树的前序遍历 N叉树,前序遍历
897 递增顺序搜索树 二叉搜索树,中序遍历的应用

2. 中级题目

题号 题目名称 知识点
98 验证二叉搜索树 二叉搜索树的性质,递归
113 路径总和 II 深度优先搜索,路径求和
257 二叉树的所有路径 深度优先搜索,路径记录
114 二叉树展开为链表 递归,树的展开
116 填充每个节点的下一个右侧节点指针 层序遍历,队列的应用
104 二叉树的最大深度 深度优先搜索或层序遍历
543 二叉树的直径 深度优先搜索,树的直径
236 二叉树的最近公共祖先 递归,最近公共祖先

希望对你有帮助!

相关推荐
与己斗其乐无穷5 小时前
数据结构(2)线性表-顺序表
数据结构
周Echo周5 小时前
20、map和set、unordered_map、un_ordered_set的复现
c语言·开发语言·数据结构·c++·算法·leetcode·list
小青龙emmm5 小时前
数据结构(一) 绪论
数据结构
矿渣渣5 小时前
AFFS2 的 `yaffs_ext_tags` 数据结构详解
数据结构·算法·文件系统·yaffs2
chenyuhao20247 小时前
链表的面试题4之合并有序链表
数据结构·链表·面试·c#
水水沝淼㵘8 小时前
嵌入式开发学习日志(数据结构--顺序结构单链表)Day19
linux·服务器·c语言·数据结构·学习·算法·排序算法
莹莹学编程—成长记9 小时前
list基础用法
数据结构·list
清幽竹客9 小时前
redis数据结构-09 (ZADD、ZRANGE、ZRANK)
数据结构·数据库·redis
葵花日记9 小时前
数据结构——二叉树
c语言·数据结构
越城10 小时前
数据结构中的栈与队列:原理、实现与应用
c语言·数据结构·算法