数据结构学习笔记8——简单二叉树的实现与遍历

本文介绍了一种简单的二叉树实现方法,通过定义二叉树节点类并实现三种基本的遍历方式:前序、中序和后序遍历。提供了完整的代码示例及运行结果验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1,简单二叉树的实现

回忆链表的实现,我们定义了两个类模板,一个用于表示链表里的结点,另一个用于表示链表本身;但是在二叉树的实现中,我们只定义二叉树的结点,而不再为整个二叉树定义类模板。

这里实现的二叉树是最简单的二叉树,结点不区分叶结点与分支结点。即,即便是叶结点,在类中也同样有左子结点与右子结点,只不过左右子结点都是空指针。
文件结构如下:


BinNode.h定义二叉树结点的ADT,是基类;
BSTNode.h定义简单的二叉树结点,继承基类;

1.1 BinNode.h

/********************************************************/
// 用模板实现二叉树(Binary Node)结点的基类定义
/********************************************************/
#pragma once
#include "Public.h"

// 二叉树结点的抽象类,ADT
template<typename E> 
class BinNode
{
public:
	virtual ~BinNode() {};

	//返回结点值
	virtual E& element() = 0; //纯虚函数

	//设置结点值
	virtual void setElement(const E& e) = 0;

	//返回左子结点
	virtual BinNode* left() const = 0;

	//设置左子结点
	virtual void setLeft(const BinNode*) = 0;

	//返回右子结点
	virtual BinNode* right() const = 0;

	//设置右子结点
	virtual void setRight(const BinNode*) = 0;
};

1.2 BSTNode.h

/********************************************************/
// 用模板实现二叉树(Binary Node)结点的简单定义
// Pointer-Based Node
// 不区分叶结点和分支结点
/********************************************************/
#pragma once
#include "BinNode.h"

template<typename Key, typename E>
class BSTNode : public BinNode<E>
{
private:
	Key k;
	E it;
	BSTNode* lc;
	BSTNode* rc;

public:
	BSTNode() :lc(NULL), rc(NULL) {}//若定义在局部作用域中,则k和it没有被初始化
	BSTNode(Key K, E e, BSTNode* l=NULL, BSTNode* r=NULL) :k(K), it(e), lc(l), rc(r) {}

	~BSTNode() {}

	E& element() { return it; }
	void setElement(const E& e) { it = e; }
	//Key& key()const { return k; } ,error C2440: “return”: 无法从“const char”转换为“char &”
	// c++ primer P377,不能从const成员函数返回指向类对象的普通引用
	const Key& key()const { return k; }
	void setKey(const Key& K) { k = K; }

	BSTNode* left() const { return lc; }
	BSTNode* right() const { return rc; }

	//为什么输入参数为BinNode<E>*,而不直接用BSTNode*????????
	//可能原因是基类中的setLeft输入参数类型就是BinNode<E>*
	void setLeft(const BinNode<E>* b) { lc = (BSTNode*)b; }//???????????????????????????????
	void setRight(const BinNode<E>* b) { rc = (BSTNode*)b; }//???????????????????????????????
	void setAll(const BinNode<E>* bl, const BinNode<E>* br) 
	{
		lc = (BSTNode*)bl;
		rc = (BSTNode*)br;
	}
	
	//判断是否为叶结点
	bool isLeaf() { return (lc == NULL) && (rc == NULL); }
};

这里有一个需要注意的地方,因为我们把返回key值的函数Key& key()定义成了const成员函数,又让其返回指向类成员的引用而不是值,那么其返回类型就必须只能是const引用而不能是普通引用。其实想想,const成员函数是不能改变成员的值的,但是如果返回的是普通引用,则对const成员函数返回值进行赋值,不就改变了类的成员?语法上的原因是,普通的成员函数的this指针是指向类对象的const指针,指针的值不能改变,但其指向的值可以改变;但是const成员函数的thsi指针是指向const对象的const指针。所以在const成员函数中,成员k的类型是const Key。详见《C++ Primer 3rd》P377:“不能从 const 成员函数返回指向类对象的普通引用。”

2,二叉树的3种遍历

3种遍历的定义见 前一篇博文

2.1 traversal.h

/********************************************************/
// 实现简单二叉树的前序遍历、后序遍历与中序遍历
/********************************************************/
#pragma once
#include "Public.h"
#include "BSTNode.h"

//前序遍历
template<typename E> void preorder(BinNode<E>* root);

//后序遍历
template<typename E> void postorder(BinNode<E>* root);

//中序遍历
template<typename E> void midorder(BinNode<E>* root);

//前序遍历
template<typename Key, typename E> void preorder_2(BSTNode<Key,E>* root);

//后序遍历
template<typename Key, typename E> void postorder_2(BSTNode<Key, E>* root);

//中序遍历
template<typename Key, typename E> void midorder_2(BSTNode<Key, E>* root);

2.2 traversal_Def.h

/********************************************************/
// 实现简单二叉树的前序遍历、后序遍历与中序遍历
/********************************************************/ 
#include "BSTNode.h"

template<typename E> 
void preorder(BinNode<E>* root)
{
	if (root == NULL)
	{
		return;
	}
	cout << root->element() << '\t';
	preorder(root->left());
	preorder(root->right());
}

template<typename E>
void postorder(BinNode<E>* root)
{
	if (root == NULL)
	{
		return;
	}

	postorder(root->left());
	postorder(root->right());
	cout << root->element() << '\t';

}

template<typename E>
void midorder(BinNode<E>* root)
{
	if (root == NULL)
	{
		return;
	}

	midorder(root->left());
	cout << root->element() << '\t';	
	midorder(root->right());
}

template<typename Key, typename E> 
void preorder_2(BSTNode<Key, E>* root)
{
	if (root == NULL)
	{
		return;
	}
	cout << root->key();
	preorder_2(root->left());
	preorder_2(root->right());
}

template<typename Key, typename E> 
void postorder_2(BSTNode<Key, E>* root)
{
	if (root == NULL)
	{
		return;
	}

	postorder_2(root->left());
	postorder_2(root->right());
	cout << root->key();

}

template<typename Key, typename E>
void midorder_2(BSTNode<Key, E>* root)
{
	if (root == NULL)
	{
		return;
	}

	midorder_2(root->left());
	cout << root->key();
	midorder_2(root->right());
}


这里需要注意的一定是,定义模板函数与定义普通函数的文件结构是不一样的。不能写一个头文件放模板函数的声明,再写一个源文件放模板函数的定义,这样编译的时候是找不到模板函数的定义的;必须将模板函数的声明与定义都放在头文件中,并且在main()函数所在的文件中包含定义模板函数的头文件。详见 C++学习笔记60——模板编译模型

3.使用二叉树

/********************************************************/
// 主函数
// 用于测试编写的各函数与数据结构
/********************************************************/
#include "Public.h"
#include "Tools.h"
#include "traversal_Def.h"
#include "BSTNode.h"
int main()
{
	/********************************************************/
	// 7,简单的二叉树
	///********************************************************/
	BSTNode<char, int> root('A', 1);
	BSTNode<char, int> NodeB('B', 2);
	BSTNode<char, int> NodeC('C', 3);
	BSTNode<char, int> NodeD('D', 4);
	BSTNode<char, int> NodeE('E', 5);
	BSTNode<char, int> NodeF('F', 6);
	BSTNode<char, int> NodeG('G', 7);
	BSTNode<char, int> NodeH('H', 8);
	BSTNode<char, int> NodeI('I', 9);

	root.setAll (&NodeB, &NodeC);
	NodeB.setAll(NULL,   &NodeD);
	NodeC.setAll(&NodeE, &NodeF);
	NodeE.setAll(&NodeG, NULL);
	NodeF.setAll(&NodeH, &NodeI);

	// 前序遍历
	preorder_2(&root);
	cout << endl;

	// 后序遍历
	postorder_2(&root);
	cout << endl;

	// 中序遍历
	midorder_2(&root);
	cout << endl;

	system("pause");
	return 0;
}
这里生成的二叉树就是《数据结构与算法分析》里图5.1给的二叉树例子。

运行的结果是:

与书本上给出的3种遍历的结果一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值