目录
二叉树
二叉树是一种树形数据结构,其中每个节点最多有两个子节点,一个左子节点和一个右子节点。二叉树通常使用递归方式定义,并将其定义为一个空节点或者是一个具有两个子节点的节点。(如相关概念中的图,就是一颗二叉树)。
二叉树有一些重要的性质,包括:
1. 深度限制:一个二叉树中,根节点的深度为0,子节点的深度为父节点的深度+1。在一个二叉树中,任何节点的深度不会超过树的高度。
2. 节点数量限制:有n个节点的二叉树,它的叶子节点数量是n+1的二次幂。
3. 遍历中序:二叉树的节点中序遍历结果是一个递增的有序序列。
4. 遍历前序与后序:树的前序遍历遍历根节点,然后遍历左子树和右子树。后序遍历遍历左子树和右子树,然后遍历根节点。
二叉树是一种常见的数据结构,在计算机科学中有广泛的应用。例如,它们可以用于表示文件系统,解析二叉表达式或搜索和排序算法。
相关概念
-
根节点(Root):二叉树中的第一个节点称为根节点,它没有父节点。(上图中的0就是这棵二叉树的根节点。)
-
父节点(Parent):除了根节点外,每个节点都有一个父节点。
-
上图中的0是4和1的父节点。同理4和1是0的两个子节点。
-
-
子节点(Child):每个节点可能有 0、1 或 2 个子节点。
-
上图中的9、8、6、7有0个子节点,1和5只有一个子节点(右孩子7),0,4,2都有左右两个子节点(孩子)。
-
-
叶子节点(Leaf):没有子节点的节点称为叶子节点、终端节点或外部节点。
-
也就是子节点个数为0的节点:9、8、6、7
-
-
内部节点(Internal):至少有一个子节点的节点称为内部节点.
-
左子树(Left Subtree):一个节点下的左边所有节点组成的子树。
-
上图二叉树的左子树:
-
-
右子树(Right Subtree):一个节点下的右边所有节点组成的子树.
-
上图二叉树的右子树:
-
-
深度(Depth):从根节点到某一个节点的唯一路径上的边数。
-
高度(Height):从某一个节点到它下面的所有叶子节点所经过的路径上的边数,也就是从这个节点开始的子树的高度。
C++实现
代码
#ifndef TEST_RANDOM_TREE_H
#define TEST_RANDOM_TREE_H
#include <iostream>
#include <map>
#include <cmath>
#include <string>
#include <random>
/**
* 生成1-10的随机数
* @return
*/
static int genRandomValue(){
static std::random_device rd; // 随机设备,用于获取随机数种子
static std::mt19937 gen(rd()); // 使用 Mersenne Twister 算法的伪随机数生成器
static std::uniform_int_distribution<> dist(1, 10); // 定义随机数分布和范围
return dist(gen);
}
template<typename T>
class TreeNode {
public:
explicit TreeNode(T val) : val(val), left(nullptr), right(nullptr) {}
T val;
TreeNode<T> *left;
TreeNode<T> *right;
};
template<typename T>
class RandomTree {
public:
/**
* 添加元素
* @param val
*/
void insert(T val) {
if (this->root == nullptr) {
this->root = new TreeNode<T>(val);
} else {
this->insertNode(this->root, val);
}
}
/**
* 指定节点添加元素
* 如果val值大于当前节点的值,插入左子树
* 否则插入右子树
* @param node
* @param val
*/
void insertNode(TreeNode<T> *node, T val) {
if (genRandomValue() % 2 == 0) {
if (node->left == nullptr) {
node->left = new TreeNode<T>(val);
} else {
this->insertNode(node->left, val);
}
} else {
if (node->right == nullptr) {
node->right = new TreeNode<T>(val);
} else {
this->insertNode(node->right, val);
}
}
}
void preorderTraversal() {
this->preorderTraversal(this->root);
}
void inorderTraversal() {
this->inorderTraversal(this->root);
}
void postorderTraversal() {
this->postorderTraversal(this->root);
}
void printTree() {
this->printTree(this->root);
}
void deleteNode(T val){
this->deleteNode(this->root, val);
}
private:
void preorderTraversal(TreeNode<T> *node) {
if (node == nullptr) {
return;
}
std::cout << node->val << std::endl;
preorderTraversal(node->left);
preorderTraversal(node->right);
}
void inorderTraversal(TreeNode<T> *node) {
if (node == nullptr) {
return;
}
postorderTraversal(node->left);
std::cout << node->val << std::endl;
postorderTraversal(node->right);
}
void postorderTraversal(TreeNode<T> *node) {
if (node == nullptr) {
return;
}
postorderTraversal(node->left);
postorderTraversal(node->right);
std::cout << node->val << std::endl;
}
/**
* 从上到下打印二叉树
* @param node
*/
void printTree(TreeNode<T> *node) {
if (node == nullptr) {
return;
}
// map<元素在二叉树中的序号,元素值> nodes ,从上到下从左到右:
// 例如:(数字为序号)
// 1
// / \
// 2 3
// / \ \
// 4 5 7
// /
// 14
std::map<int, T> nodes{};
if (node == this->root) {
nodes.insert({1, node->val}); // 根节点的序号为1
this->markNodes(node, nodes);
}
int len = nodes.size();
// width = 二叉树宽度*2
int width = static_cast<int>(pow(2, this->depth + 1));
std::cout << "//" << std::string(width, '-') << "BinaryTree" << std::string(width, '-') << "//" << std::endl;
// 分层遍历 二叉树 并输出节点值
for (int i = 0; i <= this->depth; i++) {
std::map<int, T> lineMap;
// 遍历nodes,获取第i层的所有节点数据
for (auto it = nodes.begin(); it != nodes.end(); ++it) {
// 使用log2(节点编号)获取当前节点的层数
if (static_cast<int>(log2(it->first)) == i) {
lineMap.insert({it->first, it->second});
}
}
// 打印第i层数据
// 第0层数据居中
// 计算每层的元素平均间距int step = width / pow(2,i+1);
// 以及每个元素在当前层的偏移量offset = pair.first - pow(2,i);
int preElemPos = static_cast<int>(pow(2, i)); // 每行第一个元素的位置编号
int x = 0;
for (auto &pair: lineMap) {
int offset = pair.first - preElemPos;
int step = width / static_cast<int>(pow(2, i + 1));
std::cout << std::string(offset * 2 * step, ' ');
if (x == 0) {
std::cout << std::string(step, ' '); // 每行第一个元素的(控制台输出)位置
}
std::cout << pair.second; // 当前节点元素值
x++;
preElemPos = pair.first;
}
lineMap.clear();
std::cout << std::endl; // 换行
}
std::cout << "//" << std::string(width, '-') << "BinaryTree" << std::string(width, '-') << "//" << std::endl;
}
/**
* 先序遍历二叉树,记录{nodes}信息,并计算二叉树深度{this.depth}
* root的序号{nodeNo}为1,左子树序号为2*{nodeNo},右子树的序号为2*root.{nodeNo} + 1,以此类推
* @param node
* @param nodes
* @param nodeNo
*/
void markNodes(TreeNode<T> *node, std::map<T, int> &nodes, int nodeNo = 1) {
if (node == nullptr) {
return;
}
if (node->left != nullptr) {
nodes.insert({nodeNo * 2, node->left->val});
this->depth = std::max(this->depth, static_cast<int>(log2(nodeNo * 2)));
this->markNodes(node->left, nodes, nodeNo * 2);
}
if (node->right != nullptr) {
nodes.insert({nodeNo * 2 + 1, node->right->val});
this->depth = std::max(this->depth, static_cast<int>(log2(nodeNo * 2 + 1)));
this->markNodes(node->right, nodes, nodeNo * 2 + 1);
}
}
/**
* 从左子树中选择一个叶子节点作为新的根节点
* @param node
* @return
*/
TreeNode<T> *findLeafNodeFromLeft(TreeNode<T> *node) {
while (node->left != nullptr || node->right != nullptr) {
if(node->left != nullptr){
node = node->left;
}else if(node->right != nullptr){
node = node->right;
}
}
return node;
}
TreeNode<T> *deleteNode(TreeNode<T> *node, T val) {
if (node == nullptr) return node;
if(node->val == val) {
// 左子树为空
if (node->left == nullptr) {
TreeNode<T> *temp = node->right;
delete node;
return temp;
} else if (node->right == nullptr) { // 右子树为空
TreeNode<T> *temp = node->left;
delete node;
return temp;
}
// 左右子树均不为空
TreeNode<T> *tempNode = this->findLeafNodeFromLeft(node);
node->val = tempNode->val;
node->left = this->deleteNode(node->left,tempNode->val);
}else{
node->left = this->deleteNode(node->left, val);
node->right = this->deleteNode(node->right, val);
}
return node;
}
TreeNode<T> *root = nullptr;
int depth{}; // 二叉树 深度
};
#endif //TEST_RANDOM_TREE_H
实现分析
插入和删除
这段代码定义了一个模板类 `RandomTree`,使用泛型 `T` 表示节点的值类型。
代码实现了一个随机二叉树(Random Tree),包括插入和删除节点的操作。这里的树既不是二叉搜索树也不是平衡树他只是一个普通的二叉树,每个值都是随即插入到左子树或右子树中的.支持先/中/后序遍历.
insert 函数用于插入一个新节点。如果树为空,即根节点为空,就创建一个新节点作为根节点。如果树不为空,就调用 `insertNode` 函数来插入节点。
insertNode 函数用于插入一个新节点到指定的节点下。首先通过调用 `genRandomValue()` 函数生成一个随机值,然后根据该值判断插入到左子树还是右子树。如果左(右)子树为空,就在这个位置插入新节点;如果不为空,则递归调用自身继续查找插入位置。
findLeafNodeFromLeft 函数用于找到从左侧开始的最深的叶子节点。该函数在删除节点时用到。
deleteNode 函数用于删除指定值的节点。
首先判断当前节点是否为空,如果为空就直接返回。
然后判断当前节点的值是否等于要删除的值。如果是,则根据不同情况进行不同处理:如果要删除的节点没有左子节点,就将右子节点替换当前节点,并释放当前节点;
如果要删除的节点没有右子节点,就将左子节点替换当前节点,并释放当前节点;
如果要删除的节点既有左子节点又有右子节点,就找到左子树中最深的叶子节点,将其值赋给当前节点,然后递归删除该叶子节点。如果当前节点的值不等于要删除的值,就递归调用自身分别在左子树和右子树中删除指定值的节点。
先/中/后序遍历
preorderTraversal
函数实现了先序遍历二叉树,即先访问根节点,然后递归访问左子树和右子树。当访问到空节点时,直接返回。
inorderTraversal
函数实现了中序遍历二叉树,即先递归访问左子树,然后访问根节点,最后递归访问右子树。当访问到空节点时,直接返回。
postorderTraversal
函数实现了后序遍历二叉树,即先递归访问左子树,然后递归访问右子树,最后访问根节点。当访问到空节点时,直接返回。
分层打印二叉树
printTree 函数分层打印二叉树的功能,具体实现步骤如下:
1. 如果传入的二叉树节点为空,则直接返回;
2. 如果传入的二叉树节点为根节点,则将其标记为序号为1,并调用markNodes()函数,对二叉树的每个节点进行标号,并将节点的数据存入map中,从左到右从上到下依次编号;
3. 计算二叉树打印宽度width,其中宽度为2^(depth+1);
4. 使用分层遍历的方式,逐行输出二叉树的节点值,对于每一行,先将该行的节点数据存入临时的map lineMap 中;
5. 根据lineMap中各节点的位置,输出该行节点值,并控制各节点的空格数量,使输出的树形结构更加整齐;
6. 最后输出一行“//”与“BinaryTree”间有width个“-”的字符以装饰。
其中,markNodes()函数使用递归实现,给每个节点标号,并更新树的深度。各参数含义如下:
1. node:当前节点;
2. nodes:存储节点编号和对应数据的map;
3. nodeNo:当前节点的编号,默认为1。
printTree 函数中没有考虑元素自身打印时所占宽度.如果上下两行元素个数或元素位数相差较大,可能无法正确打印二叉树结构.
整个过程中使用了STL的map容器,对于简单的二叉树数据结构而言,复杂度并不高。