【数据结构与算法】【C++】二叉树实验报告(四)

目录

阅读建议:

一、实验目的

二、实验内容

三、实验过程

四、代码结构

五、测试结果


阅读建议:

1.实验的软硬件环境要求:

(1)硬件环境要求:PC机
(2)软件环境要求:Windows 环境下的 Microsoft Visual Studio

2.该实验采用了头文件(.h)和源文件(.cpp)相结合的形式。


一、实验目的

1.熟练掌握二叉链的存储特点;

2.熟练掌握二叉树的基本操作;

3.熟练掌握基于二叉链的二叉树操作算法实现;

4.能灵活使用二叉树解决具体的问题。


二、实验内容

1.定义二叉链类,实现二叉树的基本操作算法;

2.在主函数中定义对象,并调用成员函数,验证二叉链的基本操作。具体包括:

        (1)建立二叉链存储的二叉树

        (2)遍历二叉树:前序、中序、后序和层序

        (3)求二叉树的深度

        (4)交换二叉树所有结点的左右子树

        (5)统计二叉树叶子结点的个数

        (6)前序次序打印二叉树的叶子结点

        (7)计算二叉树的最大宽度,即结点数目最多的那一层的结点个数

        (8)非递归的方式先序或者后序遍历二叉树

        注:操作算法可不局限于以上内容


三、实验过程

1.结点结构定义

template<typename T>
struct BiNode
{
	T data;
	BiNode<T>* lchild, * rchild;
};

2.构造函数,根据输入的字符信息构建一棵二叉树。

工作原理:

  1. 函数首先定义了一个字符变量ch,用于存储从标准输入流中读取的字符。
  2. 然后,函数使用cin从标准输入读取一个字符。
  3. 如果读取的字符是'#',则将传入的二叉树根节点指针bt设置为NULL,表示建立一棵空树。
  4. 如果读取的字符不是'#',则执行以下操作:
    a. 为新节点动态分配内存,并将指针赋值给bt
    b. 将读取的字符赋值给新节点的数据域。
    c. 递归调用Creat函数,分别构建左子树和右子树。这是通过将当前节点的左子节点和右子节点作为参数传递给Creat函数来实现的。
    d. 将构建的左子树和右子树分别赋值给当前节点的左子节点和右子节点。
  5. 最后,返回当前节点的指针。
template<typename T>
BiNode<T>* BiTree<T>::Creat(BiNode<T>* bt)
{
	char ch;
	cin >> ch;  //输入节点的数据信息,假设为字符 
	if (ch == '#') {
		bt=NULL;  //建立一棵空树
	}else{
		bt = new BiNode<T>;
		bt->data = ch;
		bt->lchild = Creat(bt->lchild);  //递归建立左子树
		bt->rchild = Creat(bt->rchild);  //递归建立右子树
		
	}
	return bt;
}

3.析构函数,用于释放二叉树所占用的内存资源。

工作原理:

  1. 函数首先检查传入的二叉树根节点指针bt是否为NULL。如果是,则直接返回,不执行任何操作。
  2. 如果bt不为NULL,则执行以下操作:
    a. 递归调用Release函数,释放当前节点的左子树所占用的内存资源。这是通过将当前节点的左子节点作为参数传递给Release函数来实现的。
    b. 递归调用Release函数,释放当前节点的右子树所占用的内存资源。这是通过将当前节点的右子节点作为参数传递给Release函数来实现的。
    c. 删除当前节点所占用的动态内存,即执行delete bt;
  3. 最后,返回。
template<typename T>
void BiTree<T>::Release(BiNode<T>* bt)
{
	if (bt == NULL) {
		return;
	}else{
		Release(bt->lchild);
		Release(bt->rchild);
		delete bt;
	}
}

4.前序遍历。

工作原理:

  1. 函数首先检查传入的二叉树根节点指针bt是否为NULL。如果是,则直接返回,不执行任何操作。
  2. 如果bt不为NULL,则执行以下操作:
    a. 输出当前节点的数据域的值。
    b. 递归调用PreOrder函数,对当前节点的左子树进行前序遍历。这是通过将当前节点的左子节点作为参数传递给PreOrder函数来实现的。
    c. 递归调用PreOrder函数,对当前节点的右子树进行前序遍历。这是通过将当前节点的右子节点作为参数传递给PreOrder函数来实现的。
  3. 最后,返回。
template<typename T>
void BiTree<T>::PreOrder(BiNode<T>* bt)
{
	if (bt == NULL) {
		return;
	}
	else
	{
		cout << bt->data << " ";
		PreOrder(bt->lchild);
		PreOrder(bt->rchild);
	}
}

5.中序遍历。

工作原理:

  1. 函数首先检查传入的二叉树根节点指针bt是否为NULL。如果是,则直接返回,不执行任何操作。
  2. 如果bt不为NULL,则执行以下操作:
    a. 递归调用InOrder函数,对当前节点的左子树进行中序遍历。这是通过将当前节点的左子节点作为参数传递给InOrder函数来实现的。
    b. 输出当前节点的数据域的值。
    c. 递归调用InOrder函数,对当前节点的右子树进行中序遍历。这是通过将当前节点的右子节点作为参数传递给InOrder函数来实现的。
  3. 最后,返回。
template<typename T>
void BiTree<T>::InOrder(BiNode<T>* bt)
{
	if (bt == NULL) {
		return;
	}
	InOrder(bt->lchild);
	cout << bt->data << " ";
	InOrder(bt->rchild);
	
}

6.后序遍历。

工作原理:

  1. 函数首先检查传入的二叉树根节点指针bt是否为NULL。如果是,则直接返回,不执行任何操作。
  2. 如果bt不为NULL,则执行以下操作:
    a. 递归调用PostOrder函数,对当前节点的左子树进行后序遍历。这是通过将当前节点的左子节点作为参数传递给PostOrder函数来实现的。
    b. 递归调用PostOrder函数,对当前节点的右子树进行后序遍历。这是通过将当前节点的右子节点作为参数传递给PostOrder函数来实现的。
    c. 输出当前节点的数据域的值。
  3. 最后,返回。
template<typename T>
void BiTree<T>::PostOrder(BiNode<T>* bt)
{
	if (bt == NULL) {
		return;
	}
	PostOrder(bt->lchild);
	PostOrder(bt->rchild);
	cout << bt->data << " ";
}

7.层序遍历。

工作原理:

  1. 定义一个队列Q,用于存储二叉树的节点。队列具有两个指针frontrear,分别表示队首和队尾的位置。
  2. 初始化队列为空,并将队首和队尾指针设置为-1。
  3. 检查二叉树的根节点是否为空。如果为空,则直接返回。
  4. 将根节点加入队列中,更新队尾指针。
  5. 进入循环,直到队首指针与队尾指针相等为止。
  6. 在循环中,首先从队列中取出一个节点,并输出该节点的数据域的值。
  7. 然后,递归地将该节点的左子节点和右子节点加入队列中,更新队尾指针。
  8. 继续循环,直到队列为空。
  9. 返回。
template<typename T>
void BiTree<T>::LevelOrder()
{
	BiNode<T>* q = NULL, * Q[100];    
	int front = -1, rear = -1;  //队列初始化       
	if (root == NULL) return; 
	Q[++rear] = root;                      
	while (front != rear)       
	{
		q = Q[++front];                
		cout << q->data << " ";
		if (q->lchild != NULL)  Q[++rear] = q->lchild;
		if (q->rchild != NULL)  Q[++rear] = q->rchild;
	}
}

8.求二叉树的深度。

工作原理:

  1. 如果传入的二叉树根节点指针bt为空,则返回深度为0。
  2. 如果bt不为空,则递归地计算左子树和右子树的深度。
  3. 比较左子树和右子树的深度,返回较大的深度值,并加上1(当前节点的深度)。
  4. 返回最终计算得到的深度值。
template<typename T>
int BiTree<T>::GetDepth(BiNode<T>* bt)
{
	if (bt == NULL) {
		return 0;
	}else{
		int leftDepth = GetDepth(bt->lchild);
		int rightDepth = GetDepth(bt->rchild);
		return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1;
	}
}

9.交换二叉树所有结点的左右子树。

工作原理:

  1. 如果传入的二叉树根节点指针bt为空,则直接返回,不执行任何操作。
  2. 如果bt不为空,则交换当前节点的左右子树。
  3. 递归地调用SwapSubTrees函数,交换当前节点的左子树和右子树的所有子节点。
  4. 递归地调用SwapSubTrees函数,继续交换左子树和右子树的所有子节点。
  5. 返回。
template<typename T>
void BiTree<T>::SwapSubTrees(BiNode<T>* bt) {
	if (bt == NULL) {
		return;
	}
	swap(bt->lchild, bt->rchild);
	SwapSubTrees(bt->lchild);
	SwapSubTrees(bt->rchild);
}

10.统计二叉树叶子结点的个数。

工作原理:

  1. 如果传入的二叉树根节点指针bt为空,则返回叶子节点的个数为0。
  2. 如果当前节点为叶子节点(即左右子树都为空),则返回叶子节点的个数为1。
  3. 如果当前节点不是叶子节点,则递归地统计左子树和右子树的叶子节点个数,并将它们相加。
  4. 返回最终计算得到的叶子节点个数。
template<typename T>
int BiTree<T>::CountLeaves(BiNode<T>* bt) {
	if (bt == NULL) {
		return 0;
	}else if (bt->lchild == NULL && bt->rchild == NULL) { 
		return 1;
	}else {
		return CountLeaves(bt->lchild) + CountLeaves(bt->rchild);
	}
}

11.前序次序打印二叉树的叶子结点。

工作原理:

  1. 如果传入的二叉树根节点指针bt为空,则直接返回,不执行任何操作。
  2. 如果当前节点是叶子节点(即左右子树都为空),则输出该节点的数据域的值。
  3. 递归地调用PrintLeafNodesInPreOrder函数,先遍历左子树,再遍历右子树。
  4. 返回。
template<typename T>
void BiTree<T>::PrintLeafNodesInPreOrder(BiNode<T>* bt) {
	if (bt == NULL) {
		return;
	}
	else if (bt->lchild == NULL && bt->rchild == NULL) { 
		cout << bt->data << " ";
	}
	PrintLeafNodesInPreOrder(bt->lchild);
	PrintLeafNodesInPreOrder(bt->rchild);
}

12.计算二叉树的最大宽度。

工作原理:

  1. 如果二叉树的根节点指针bt为空,即二叉树为空,则返回0,表示最大宽度为0。
  2. 创建一个队列q,用于进行广度优先搜索(BFS)遍历。
  3. 将根节点加入队列中。
  4. 初始化最大宽度maxWidth为0。
  5. 进入循环,直到队列为空。
  6. 在循环中,首先获取当前层的节点数量levelSize
  7. 更新最大宽度maxWidth为当前层节点数量和当前最大宽度的较大值。
  8. 遍历当前层的所有节点,并将它们的子节点加入队列中。
  9. 继续循环,直到队列为空。
  10. 返回最终计算得到的最大宽度maxWidth
template<typename T>
int BiTree<T>::GetMaxWidth(BiNode<T>* bt) {
	if (root == NULL) return 0; // 空树的情况  
	queue<BiNode<T>*> q; // 使用队列来进行BFS遍历  
	q.push(root);
	int maxWidth = 0; // 记录最大宽度  
	while (!q.empty()) {
		int levelSize = q.size(); // 当前层的节点数量  
		maxWidth = max(maxWidth, levelSize); // 更新最大宽度  
		// 遍历当前层的所有节点,并将它们的子节点加入队列  
		for (int i = 0; i < levelSize; i++) {
			BiNode<T>* currentNode = q.front();
			q.pop();
			if (currentNode->lchild != NULL) {
				q.push(currentNode->lchild);
			}
			if (currentNode->rchild != NULL) {
				q.push(currentNode->rchild);
			}
		}
	}
	return maxWidth;
}

13.非递归的方式先序遍历二叉树。

工作原理:

  1. 如果二叉树的根节点指针root为空,即二叉树为空,则直接返回,不执行任何操作。
  2. 创建一个栈stk,用于辅助遍历。
  3. 将根节点入栈。
  4. 进入循环,直到栈为空。
  5. 在循环中,首先取出栈顶元素,即当前节点。
  6. 访问当前节点,输出其数据域的值。
  7. 将当前节点的右子节点入栈。
  8. 将当前节点的左子节点入栈。
  9. 继续循环,直到栈为空。
template<typename T>
void BiTree<T>::PreOrderTraversal() {
	if (root == NULL) return; // 空树的情况,直接返回  

	stack<BiNode<T>*> stk; // 使用栈来辅助遍历  
	stk.push(root); // 将根节点入栈  
	while (!stk.empty()) {
		BiNode<T>* currentNode = stk.top(); // 取出栈顶元素  
		stk.pop();
		cout << currentNode->data << " "; // 访问当前节点  
		// 先将右孩子入栈,再将左孩子入栈(因为栈是后进先出的)  
		if (currentNode->rchild != NULL) {
			stk.push(currentNode->rchild);
		}
		if (currentNode->lchild != NULL) {
			stk.push(currentNode->lchild);
		}
	}
}

14.主函数

int main()
{
	cout << "请输入二叉树的前序遍历序列(#表示空节点):" << endl;
	BiTree<char>T;  //定义对象变量T
	
	cout << "前序遍历:";
	T.PreOrder();
	cout << "\n中序遍历:";   
	T.InOrder();
	cout << "\n后序遍历:";
	T.PostOrder();
	cout << "\n层序遍历:";
	T.LevelOrder();

	cout << "\n二叉树的深度:";
	cout<<T.GetDepth();
	cout << "\n【交换二叉树所有结点的左右子树】";
	T.SwapSubTrees();
	cout << "\n层序遍历:";
	T.LevelOrder();
	cout << "\n二叉树叶子结点总数:";
	cout<<T.CountLeaves();
	cout << "\n前序次序打印二叉树的叶子结点:";
	T.PrintLeafNodesInPreOrder();
	cout << "\n二叉树的最大宽度:";
	cout << T.GetMaxWidth();
	cout << "\n非递归的方式先序遍历:";
	T.PreOrderTraversal();

	cout << "\n【释放空间】";
	T.~BiTree();
	return 0;
}

四、代码结构


五、测试结果


        完整代码链接:https://download.csdn.net/download/weixin_73286497/88758670

        希望大家可以在该篇实验报告中有所收获,同时也感谢各位大佬的支持。文章如有任何问题请在评论区留言斧正,鸿蒙会尽快回复您的建议!

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸿·蒙

您的赞赏是我进步的燃料,感谢您

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值