二叉树的C++实现


一、二叉树存储的物理结构

1.1 二叉树基础知识

(1)二叉树的定义: 每个节点最多有两个叶子节点的树结构,二叉树共有5种基本结构:空二叉树、根节点左子树为空、根节点右子树为空、根节点左右子树皆为空、根节点左右子树都不为空。
在这里插入图片描述

(2)二叉树的性质:
① 第i层的节点数目最多为 2 i − 1 ( i ≥ 1 ) 2^{i-1}(i \geq 1) 2i1i1
② 深度为k的二叉树,最多有 2 k − 1 ( k ≥ 0 ) 2^k-1 (k\geq0) 2k1k0 个节点;
③ 包含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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值