二叉树基础知识
二叉树是计算机科学中一种非常重要的数据结构,它广泛应用于各种算法和应用中。以下是二叉树的一些基本知识:
-
定义:
- 二叉树是每个节点最多有两个子树的树结构,通常称为“左子树”和“右子树”。
-
节点:
- 每个节点包含三个部分:一个数据元素和两个指针(或链接),分别指向左右子树。
-
根节点:
- 二叉树只有一个根节点,它是树的顶部节点,没有父节点。
-
叶子节点:
- 如果一个节点没有左子树或右子树,或者两者都没有,那么它被称为叶子节点。
-
度:
- 一个节点的度是指它拥有的子树数量。在二叉树中,节点的度可以是0(叶子节点)、1或2。
-
高度和深度:
- 二叉树的高度是从根节点到最远叶子节点的最长路径上的节点数。
- 节点的深度是它到根节点的路径上的节点数,包括根节点和该节点本身。
-
满二叉树:
- 所有层都被完全填满的二叉树,除了最后一层,最后一层从左到右填满。
-
完全二叉树:
- 除了最后一层外,每一层都被完全填满,并且最后一层的节点都尽可能地靠左排列。
-
平衡二叉树:
- 任何节点的两个子树的高度差不超过1。
-
二叉搜索树(BST):
- 一种特殊的二叉树,其中每个节点的值都大于其左子树中的任何节点的值,并且都小于其右子树中的任何节点的值。
-
遍历:
- 遍历二叉树是指按照某种顺序访问树中的每个节点的过程。常见的遍历方法包括:
深度优先遍历
- 前序遍历(Pre-order)
- 中序遍历(In-order)
- 后序遍历(Post-order)
这里前中后,其实指的就是中间节点的遍历顺序,只要记住前中后序指的就是中间节点的位置就可以了。
广度优先遍历
- 层序遍历(Level-order)
-
操作:
- 在二叉树上执行的操作包括搜索、插入、删除节点等。
-
应用:
- 二叉树在许多领域都有应用,如文件系统、数据库索引、表达式树、决策树、二叉堆等。
二叉树的这些基本概念是理解和实现更复杂数据结构和算法的基础。
二叉树的种类
二叉树分为两个大类:满二叉树(Full Binary Tree)和完全二叉树(Complete Binary Tree)。
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点,也就是说满二叉树是特殊的完全二叉树。
二叉搜索树
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树
平衡二叉搜索树
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二叉树的链式存储定义
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
int val:存储节点的值。
TreeNode *left:指向该节点左子树的指针。
TreeNode *right:指向该节点右子树的指针。
此外,还定义了一个构造函数TreeNode(int x),它初始化节点的值val为参数x,并将左右子树的指针初始化为NULL,表示新创建的节点最初没有子节点。
这个结构体是二叉查找树节点的标准表示方式,可以用于实现二叉查找树的各种操作,如插入、删除、查找等。
二叉树链式存储基本操作示例
#include <iostream>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
// 函数用于插入新节点
void insert(TreeNode*& root, int val) {
if (root == NULL) {
root = new TreeNode(val);
} else if (val < root->val) {
insert(root->left, val);
} else {
insert(root->right, val);
}
}
// 函数用于打印二叉树(中序遍历)
void inorder(TreeNode* root) {
if (root != NULL) {
inorder(root->left);
std::cout << root->val << " ";
inorder(root->right);
}
}
int main() {
TreeNode* root = NULL;
insert(root, 5);
insert(root, 3);
insert(root, 7);
insert(root, 2);
insert(root, 4);
insert(root, 6);
insert(root, 8);
std::cout << "Inorder traversal of the binary search tree: ";
inorder(root);
std::cout << std::endl;
return 0;
}
二叉树顺序存储
二叉树的顺序存储是指使用数组来存储二叉树的结构。在顺序存储中,二叉树的节点按照一定的顺序存储在数组中,这样可以通过节点在数组中的索引来确定节点之间的关系。
顺序存储的规则
对于一个有 ( n ) 个节点的二叉树,假设节点按照层序遍历(从上到下,从左到右)的方式存储在数组中,那么对于数组中任意位置 ( i ) 的节点,它在数组中的左子节点和右子节点的位置可以通过以下规则确定:
- 左子节点的位置:( 2i + 1 )
- 右子节点的位置:( 2i + 2 )
特点
- 空间利用:顺序存储可以高效地利用计算机内存,因为它避免了指针或链接的使用,从而节省了存储指针的空间。
- 简单性:实现简单,不需要复杂的指针操作。
- 限制:顺序存储要求预先知道树的最大大小,因此在实际应用中可能不够灵活。
示例
假设我们有一个二叉树,其节点按照层序遍历的顺序存储在数组中如下:
A
/ \
B C
/ \ \
D E F
存储在数组中的顺序为:A, B, C, D, E, F
。
- 节点 A 的左子节点是 B(位置 1),右子节点是 C(位置 2)。
- 节点 B 的左子节点是 D(位置 3),右子节点是 E(位置 4)。
应用
顺序存储的二叉树常用于实现二叉堆,特别是在实现优先队列时。在二叉堆中,父节点和子节点之间的关系是固定的,这使得顺序存储成为可能。
注意事项
- 当使用顺序存储时,通常需要一个额外的变量来记录树中节点的数量,因为数组的大小是固定的。
- 在删除操作中,需要移动节点以保持数组的连续性,这可能会增加操作的复杂性。
顺序存储是二叉树存储的一种有效方式,尤其适用于节点数量固定或已知的情况。