文章目录
一、二叉树存储的物理结构
1.1 二叉树基础知识
(1)二叉树的定义: 每个节点最多有两个叶子节点的树结构,二叉树共有5种基本结构:空二叉树、根节点左子树为空、根节点右子树为空、根节点左右子树皆为空、根节点左右子树都不为空。
(2)二叉树的性质:
① 第i层的节点数目最多为
2
i
−
1
(
i
≥
1
)
2^{i-1}(i \geq 1)
2i−1(i≥1) ;
② 深度为k的二叉树,最多有
2
k
−
1
(
k
≥
0
)
2^k-1 (k\geq0)
2k−1(k≥0) 个节点;
③ 包含n个节点的二叉树,该二叉树高度至少为
l
o
g
2
(
n
+
1
)
log_2(n+1)
log2(n+1) ;
(3)完全二叉树: 若设二叉树的深度为h,除第h层外,其他各层的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边。
(3)二叉树每个节点的编号:
对下图二叉树B的每个节点进行编号,首先将二叉树B补成完全二叉树(二叉树A),对二叉树A的每个节点进行层序编号,最后删除多余的节点(还原成二叉树B)。
父节点与孩子节点中间的编号关系:
(1)若父节点的编号为
x
x
x,则左孩子节点的编号为
2
x
2x
2x,右孩子节点的编号为
2
x
+
1
2x + 1
2x+1;
(2)若左孩子节点的编号为
x
x
x,则父节点的编号为
x
/
2
x/2
x/2(向下取整);
(3)若右孩子节点的编号为
x
x
x,则父节点的编号为
x
/
2
x/2
x/2(向下取整)。
1.2 二叉树的存储
二叉树的存储一般有两种方式:顺序存储和链式存储,这里同时采用两种方法进行存储。
1.2.1 单个节点的存储:
struct TreeNode {
char val; //节点的值
TreeNode* left; //指向左子树
TreeNode* right; //指向右子树
};
1.2.2 二叉树的存储
下面将以下图所示的二叉树为例,进行说明
(1)节点池:
将二叉树中每个节点的值存储在一个数组中,数组的下标和二叉树每个节点的编号一一对应。部分编号没有节点,数组中直接存储nullnode(这里就是“-”),该数组就是节点池。只有根据某个节点的下标,就可以计算出该节点的父节点和左右孩子节点。
例如,节点D的下标为4,则其父节点的下标为
4
/
2
=
2
4/2 = 2
4/2=2(B),其左孩子节点为
4
×
2
=
8
4×2 = 8
4×2=8(G),右孩子节点为
4
×
2
+
1
=
9
4×2 + 1 = 9
4×2+1=9(H)
创建节点池的目的是:在后面实现创建二叉树的方法时,可以利用节点池的下标关系递归创建二叉树
(2)二叉树的存储:
存储结构如下图所示,整体存储采用链式结构,每个节点的left指针指向左子树,若左子树为空,则left指针等于NULL,每个节点的right指针指向右子树,若右子树为空,则right指针等于NULL。
二、C++代码实现
2.1 每个二叉树节点结构体:
结构体实现中,包含了一个初始化列表,用于初始化val、left、和right变量。
template <typename T>
struct TreeNode {
T val;
TreeNode* left;
TreeNode* right;
TreeNode(T v = 0) : val(v), left(NULL), right(NULL) {}
};
2.2 二叉树类的定义
成员变量为nodes(节点池首地址)、root(二叉树根节点的地址)和nodesize(二叉树节点数量)
成员函数主要有:
(1)析构、构造函数
(2)树的创建
(3)树节点的访问
(4)树的遍历(前序、中序和后序)
template <typename T>
class BTree {
private:
TreeNode<T>* nodes; //节点池首地址
TreeNode<T>* root; //二叉树的根节点地址
size_t nodesize; //二叉树共有多少个节点
//树的创建
TreeNode<T>* Create(T a[], int size, int nodeId, T nullNode);
void visit(TreeNode<T>* node); //树节点的访问
void preOrder(TreeNode<T>* node); //前序遍历
void inOrder(TreeNode<T>* node); //中序遍历
void postOrder(TreeNode<T>* node); //后续遍历
public:
BTree(); //构造函数
BTree(int maxNodes); //构造函数
~BTree(); //析构函数
TreeNode<T>* GetTreeNode(int id); //根据节点的编号返回节点在节点池中的地址
TreeNode<T>* GetTreeRoot(); //获取二叉树根节点的地址
void CreateTree(T a[], int size, T nullNode); //二叉树的创建
void preOrderTraverse(); //前序遍历
void inOrderTraverse(); //中序遍历
void postOrderTraverse(); //后序遍历
};
注意:
(1)遍历:分为私有方法和公有方法。在实现共有方法时,将会调用到对应的私有方法。
为什么要分开?-对于用户来说,用户只希望从根节点遍历二叉树,用户并不希望自己要传入根节点的地址才能进行遍历。
(2)二叉树的创建:分为私有和共有方法。分开的原因同(1),一些数据不希望暴露给用户。
2.3 方法实现
(1)构造函数和析构函数:
template<typename T>
BTree<T>::BTree() {
nodesize = 10001;
nodes = new TreeNode<T>[nodesize];
}
template<typename T>
BTree<T>::BTree(int maxNodes) {
nodesize = maxNodes;
nodes = new TreeNode<T>[nodesize];
}
template<typename T>
BTree<T>::~BTree() {
delete[] nodes;
}
(2)获取节点地址和根节点地址:
template<typename T>
TreeNode<T>* BTree<T>::GetTreeNode(int id) {
return &nodes[id];
}
template <typename T>
TreeNode<T>* BTree<T>::GetTreeRoot() {
return root;
}
(3)访问某个节点:
template<typename T>
void BTree<T>::visit(TreeNode<T>* node) {
std::cout << node->val;
}
(4)二叉树的创建:
template<typename T>
TreeNode<T>* BTree<T>::Create(T a[], int size, int nodeId, T nullNode) {
if (nodeId >= size || a[nodeId] == nullNode) {
return NULL;
}
TreeNode<T>* nowNode = GetTreeNode(nodeId);
nowNode->val = a[nodeId];
nowNode->right = Create(a, size, nodeId*2 + 1, nullNode);
nowNode->left = Create(a, size, nodeId*2, nullNode);
return nowNode;
}
template<typename T>
void BTree<T>::CreateTree(T a[], int size, T nullNode) {
root = Create(a, size, 1, nullNode);
}
对于二叉树的创建采用递归创建的模式,递归的出口是传入的节点编号超过最大节点数量以及该节点是nullNode(前文所说的”-“)
(5)遍历:
template<typename T>
void BTree<T>::preOrder(TreeNode<T>* node) {
if (node) {
visit(node);
preOrder(node->left);
preOrder(node->right);
}
}
template<typename T>
void BTree<T>::inOrder(TreeNode<T>* node) {
if (node) {
inOrder(node->left);
visit(node);
inOrder(node->right);
}
}
template<typename T>
void BTree<T>::postOrder(TreeNode<T>* node) {
if (node) {
postOrder(node->left);
postOrder(node->right);
visit(node);
}
}
template<typename T>
void BTree<T>::preOrderTraverse() {
preOrder(root);
}
template<typename T>
void BTree<T>::inOrderTraverse() {
inOrder(root);
}
template<typename T>
void BTree<T>::postOrderTraverse() {
postOrder(root);
}
(6)测试:
void test(){
const char nullnode = '-';
char a[15] = {
nullnode, 'a', 'b', 'c', 'd',
nullnode, 'e', 'f', 'g', 'h',
nullnode, nullnode, nullnode, 'i'
};
BTree<char> T(15);
T.CreateTree(a, 15, nullnode);
T.preOrderTraverse();
cout << endl;
T.postOrderTraverse();
cout << endl;
T.inOrderTraverse();
}