数据结构实验报告
实验名称: 实验四 二叉树实验
- 实验目标
- 掌握二叉树的动态链表存储结构及表示。
- 掌握二叉树的三种遍历算法(递归和非递归两类)。
- 运用二叉树三种遍历的方法求解有关问题。
- 实验要求
4.2 实验要求
- 结构定义和算法实现放入库文件,如“BiTree.h”中;
- 二叉树的测试数据用文本文件方式给出,例如测试数据名为bt151.btr的二叉树,可参考发来的二叉树形状和参考存储文件;
- 二叉树创建方法可自行选择;
- 可多次连续测试。
4.3 实验任务
设计二叉树的二叉链表存储结构,编写算法实现下列问题的求解。
- 设计算法按中序次序输出二叉树中各结点的值及其所对应的层次数。
实验测试数据基本要求:
第一组数据: bt151.btr
第二组数据: bt21.btr
- 求二叉树的叶子结点数和1度结点数。
实验测试数据基本要求:
第一组数据: bt261.btr
第二组数据: bt21.btr
- 键盘输入一个元素x,求其父节点、兄弟结点、子结点的值,不存在时给出相应提示信息。对兄弟结点和孩子结点,存在时要明确指出是左兄弟、左孩子、右兄弟或右孩子。
实验测试数据基本要求:
第一组数据: bt31.btr
第二组数据: bt21.btr
- 键盘输入一个元素x,求其在树中的层次。不存在时给出相应提示信息。
实验测试数据基本要求:
第一组数据: bt26.btr
第二组数据: bt21.btr
- 将按顺序方式存储在数组中的二叉树转换为二叉链表形式。(数组中要扩展为完全二叉树)。
实验测试数据基本要求:
第一组数据: bt8.btr
第二组数据: bt14.btr
- 输出二叉树从每个叶子结点到根结点的路径(经历的结点)。
实验测试数据基本要求:
第一组数据: bt261.btr
第二组数据: bt21.btr
- 对二叉链表表示的二叉树,按从上到下,从左到右打印结点值,即按层次遍历序打印。(提示:需要使用队列)
实验测试数据基本要求:
第一组数据: bt261.btr
第二组数据: bt21.btr
4.4* 二叉树扩展实验
非必做内容,有兴趣的同学选做,
- 复制一棵二叉树T到T1。
实验测试数据基本要求:
第一组数据: bt151.btr
第二组数据: bt21.btr
- 交换二叉树中每个结点的左右孩子指针的值。(即:左子树变为右子树,右子树变为左子树)。
实验测试数据基本要求:
第一组数据: bt151.btr
第二组数据: bt21.btr
- 对二叉链表表示的二叉树,求2个结点最近的共同祖先。
实验测试数据基本要求:
第一组数据: bt261.btr
第二组数据: bt21.btr
实验内容
- 数据结构设计(所有存储结构的封装描述)
struct BiNode {
char data;
BiNode* lChild;
BiNode* rChild;
- };算法设计
- 定义了三个函数,用于创建二叉树节点、销毁二叉树以及按中序遍历输出二叉树中各节点的值和层次数。createNode函数创建一个新的二叉树节点,destroyTree函数销毁整个二叉树,而inorderTraversal函数按中序遍历方式输出二叉树节点的值和层次数。这些函数可以在构建和处理二叉树数据结构时使用,以便创建、操作和释放二叉树。
struct TreeNode {
int value;
TreeNode* left;
TreeNode* right;
};
// 中序遍历二叉树并输出节点值和层次数
void inorderTraversal(TreeNode* root, int level) {
if (root == nullptr) {
return;
}
inorderTraversal(root->left, level + 1);
std::cout << "Node: " << root->value << ", Level: " << level << std::endl;
inorderTraversal(root->right, level + 1);
}
- `countLeafAndDegreeOneNodes`函数是一个用于统计二叉树中叶子节点数量和度为1的节点数量的函数。它通过递归地遍历二叉树的所有节点,根据节点的子节点情况来确定节点的类型,并相应地增加叶子节点或度为1的节点的计数器。当遍历到叶子节点时,将叶子节点计数器加1;当遍历到度为1的节点时,将度为1的节点计数器加1。通过该函数,可以方便地获取二叉树中叶子节点和度为1的节点的数量信息。
struct TreeNode {
int value;
TreeNode* left;
TreeNode* right;
};
void countLeafAndDegreeOneNodes(TreeNode* root, int& leafNodes, int& degreeOneNodes) {
if (root == nullptr) {
return;
}
if (root->left == nullptr && root->right == nullptr) {
leafNodes++;
return;
}
countLeafAndDegreeOneNodes(root->left, leafNodes, degreeOneNodes);
countLeafAndDegreeOneNodes(root->right, leafNodes, degreeOneNodes);
if ((root->left != nullptr && root->right == nullptr) || (root->left == nullptr && root->right != nullptr)) {
degreeOneNodes++;
}
}
3、一下代码是一个用于在二叉树中查找目标节点的父节点、兄弟节点、左子节点和右子节点的函数。它通过递归遍历树来查找,并在找到目标节点时更新相应的指针。最终,函数返回目标节点的相关信息,或者如果目标节点不存在,则返回空指针。
void findRelatives(BiNode* root, char x, BiNode*& parent, BiNode*& sibling, BiNode*& leftChild, BiNode*& rightChild) {
// 递归结束条件:当节点为空时,直接返回
if (root == nullptr)
return;
// 如果当前节点的数据等于目标节点x
if (root->data == x) {
// 如果当前节点有左子节点,则将其赋值给leftChild
if (root->lChild) {
leftChild = root->lChild;
}
// 如果当前节点有右子节点,则将其赋值给rightChild
if (root->rChild) {
rightChild = root->rChild;
}
}
else {
// 如果当前节点的左子节点存在且值等于目标节点x
if (root->lChild && root->lChild->data == x) {
// 将当前节点设置为目标节点x的父节点
parent = root;
// 如果当前节点有右子节点,则将其赋值给sibling
if (root->rChild)
sibling = root->rChild;
}
// 如果当前节点的右子节点存在且值等于目标节点x
else if (root->rChild && root->rChild->data == x) {
// 将当前节点设置为目标节点x的父节点
parent = root;
// 如果当前节点有左子节点,则将其赋值给sibling
if (root->lChild)
sibling = root->lChild;
}
// 递归调用,查找目标节点x的父节点、兄弟节点、左子节点和右子节点
findRelatives(root->lChild, x, parent, sibling, leftChild, rightChild);
findRelatives(root->rChild, x, parent, sibling, leftChild, rightChild);
}
}
4、该函数是一个用于在二叉树中查找目标节点的层级的函数。它使用递归的方式,在树中查找目标节点x,并返回其所在的层级。如果找不到目标节点,则返回-1。函数的时间复杂度为O(n),其中n是二叉树中的节点数量。
int findLevel(BiNode* root, char x, int level) {
// 递归结束条件:当节点为空时,返回-1表示未找到目标节点
if (root == nullptr)
return -1;
// 如果当前节点的数据等于目标节点x,返回当前层级
if (root->data == x)
return level;
// 在左子树中递归查找目标节点x
int leftLevel = findLevel(root->lChild, x, level + 1);
// 如果在左子树中找到了目标节点x,则返回其层级
if (leftLevel != -1)
return leftLevel;
// 在右子树中递归查找目标节点x
int rightLevel = findLevel(root->rChild, x, level + 1);
// 如果在右子树中找到了目标节点x,则返回其层级
return rightLevel;
}
5、该函数将字符数组转换为二叉树。它使用层序遍历的方法逐个处理字符数组的元素,并构建相应的二叉树节点。函数首先检查数组大小是否为零,然后创建一个队列,并将根节点加入队列。在每次循环中,函数取出队列的头节点,并根据字符数组的元素创建左子节点。如果字符数组的当前位置不是字符 '#',则创建一个新的节点,并将字符数组的值赋给节点的数据域。接着将新的左子节点加入队列。如果循环未结束且数组还有剩余元素,函数继续处理右子节点。如果字符数组的当前位置不是字符 '#',则创建一个新的节点,并将字符数组的值赋给节点的数据域。最后,将新的右子节点加入队列。当循环结束时,字符数组中的所有元素已经被处理,二叉树的构建也完成了。
void convertToBinaryTree(BiNode*& root, char A[], int size) {
if (size == 0)
return;
// 使用队列进行层序遍历w
queue<BiNode*> q;
q.push(root);
int i = 1;
while (!q.empty() && i < size) {
// 出队当前节点
BiNode* current = q.front();
q.pop();
// 处理左子节点
if (A[i] != '#') {
BiNode* leftNode = new BiNode;
leftNode->data = A[i];
leftNode->lChild = nullptr;
leftNode->rChild = nullptr;
current->lChild = leftNode;
q.push(leftNode);
}
i++;
// 处理右子节点
if (i < size && A[i] != '#') {
BiNode* rightNode = new BiNode;
rightNode->data = A[i];
rightNode->lChild = nullptr;
rightNode->rChild = nullptr;
current->rChild = rightNode;
q.push(rightNode);
}
i++;
}
}
6、实现了打印从二叉树叶子节点到根节点的所有路径。它使用递归的方式遍历二叉树,并将经过的节点值存储在一个栈中。当遍历到叶子节点时,将路径栈中的元素倒序打印出来。最后,通过回溯在递归调用结束后,将当前节点的值从路径栈中弹出,确保路径栈的正确性。
void printPathsToRoot(BiNode* root, std::stack<char>& path) {
if (root == nullptr)
return;
path.push(root->data);
if (root->lChild == nullptr && root->rChild == nullptr) {
// 打印路径
std::stack<char> temp = path;
while (!temp.empty()) {
std::cout << temp.top();
temp.pop();
}
std::cout << std::endl;
}
printPathsToRoot(root->lChild, path);
printPathsToRoot(root->rChild, path);
path.pop();
}
- 这段代码实现了对二叉树的层序遍历。它利用队列的先进先出特性,按层级遍历每个节点,并输出节点的值。函数首先检查根节点是否为空,如果是,则直接返回。然后创建一个队列,并将根节点加入队列。在循环中,每次从队列中取出一个节点,并输出节点的值。如果该节点有左子节点,则将左子节点加入队列。如果该节点有右子节点,则将右子节点加入队列。循环继续,直到队列为空,完成了对整个二叉树的层序遍历。
void levelOrderTraversal(BiNode* root) {
if (root == nullptr)
return;
std::queue<BiNode*> q;
q.push(root);
while (!q.empty()) {
BiNode* node = q.front();
q.pop();
std::cout << node->data << " ";
if (node->lChild)
q.push(node->lChild);
if (node->rChild)
q.push(node->rChild);
}
}
*1、实现了复制二叉树的功能。它使用递归的方式复制二叉树的每个节点和其子树。函数首先检查根节点是否为空,如果是,则返回空指针。然后创建一个新节点`newNode`,将根节点的值赋给新节点,并递归调用`copyTree`函数复制根节点的左子树和右子树。最后,返回复制后的新根节点`newNode`。这样,通过不断递归复制每个节点,就可以创建一个与原二叉树结构相同的新二叉树。
BiNode* copyTree(BiNode* root) {
if (root == nullptr)
return nullptr;
BiNode* newNode = new BiNode{ root->data, nullptr, nullptr };
newNode->lChild = copyTree(root->lChild);
newNode->rChild = copyTree(root->rChild);
return newNode;
}
*2、使用递归的方式遍历二叉树,并对每个节点进行左右子节点的交换操作。函数首先检查根节点是否为空,如果是,则直接返回。然后,创建一个临时指针`temp`,将根节点的左子节点赋值给`temp`,将根节点的左子节点指向根节点的右子节点,将根节点的右子节点指向`temp`。接着,递归调用`swapChildNodes`函数分别对根节点的左子节点和右子节点进行交换操作。通过递归调用,函数实现了对整个二叉树节点的左右子节点的交换。最终,所有节点的左右子节点都被成功交换了。
void swapChildNodes(BiNode* root) {
if (root == nullptr)
return;
BiNode* temp = root->lChild;
root->lChild = root->rChild;
root->rChild = temp;
swapChildNodes(root->lChild);
swapChildNodes(root->rChild);
}
- 运行和测试
1、
2、
3、
4、
5、
6、
7、
*1、
*2、
- 总结、心得和建议
总结:
从栈的实验中,我深入理解了二叉树的基本概念和操作,并学会了如何实现二叉树这一数据结构。通过实践,我加深了对二叉树在不同场景下应用的认识,并学会了如何灵活运用二叉树解决实际问题。
在实验过程中,我重视了二叉树的基本操作的实现,并注重考虑边界情况,以确保操作的正确性和健壮性。我积极思考问题的解决方案,并尝试不同的方法,从中学会了如何灵活运用二叉树解决复杂的问题。
我意识到持续学习和实践对于掌握栈这一数据结构非常重要。因此,我计划继续深入学习数据结构和算法,并不断提升自己的编程能力和解决问题的能力,以便更好地应用二叉树及其他数据结构。
此外,我也认识到加强算法思维训练和参与项目实践的重要性。我打算通过解决更多的算法题目和参与实际项目的开发来提高自己的能力和应用栈的能力。
最后,我强调了与他人交流学习的好处。通过与同学、老师或其他开发者进行交流学习,分享问题、解决方案和经验,我可以获得不同的观点和思路,加速自己的学习和成长。
总结起来,通过栈的实验,我深入理解了二叉树的基本概念和操作,学会了如何使用二叉树,并在实践中掌握了二叉树在不同场景下的应用。我将继续学习、实践和与他人交流,以提高自己的能力,并在编程和问题解决方面取得更大的成就。