二叉树之基本操作

一、存储结构

1、静态结构

只适合完全二叉树,或者一些节点缺省较少的情况,用数组来存储,对应下标的位置存储对应的值,如果缺省较多,则造成过多的空间浪费

2、动态结构(链式存储)

分为二叉链和三叉连,三叉练主要是为了回溯,通过子节点找到父节点,本文主要实现的是二叉链

二、节点的定义

代码如下:

template<class T>
struct BinaryTreeNode
{
public:
	BinaryTreeNode<T>* _left;	//左孩子
	BinaryTreeNode<T>* _right;	//右孩子
	T _data;					//数据
public:
	BinaryTreeNode(const T& x)//模版尽量加&,这样效率高,除了一些故意不加引用的情况,不调拷贝构造函数,里面不改变加const
		:_data(x)
		, _left(NULL)
		, _right(NULL)
	{}
};

三、二叉树的基本功能

代码如下:

template<class T>
class BinaryTree
{
	typedef BinaryTreeNode<T> Node;//受类域保护,万一外面也定义了Node分不清

public:
	BinaryTree();
	BinaryTree(const T* a, size_t size, const T& invalid);//为什么不能加引用
	BinaryTree(const BinaryTree<T>& t);
	BinaryTree<T>& operator=(const BinaryTree<T>& t);//传统写法和现代写法
	~BinaryTree();

public:
	size_t Size();//节点个数
	size_t Depth();
	size_t LeafSize();
	size_t GetKLevel(int k);//获得第K层的节点数

public:
	void PrevOrder();
	void InOrder();
	void PosOrder();
	void LeveOrder();//层序遍历
	void PrevOrder_NonR();//前序的非递归遍历
	void InOrder_NonR();//非递归中序遍历
	void PosOrder_NonR();//后序遍历的非递归写法

protected:
	Node* _GreateTree(const T* a, size_t size, size_t& index, const T& invalid);
	Node* _Copy(Node* root);
	void _Destroy(Node* root);
	size_t _Size(Node* root);
	size_t _Depth(Node* root);
	//方法一、二、三
	size_t _LeafSize(Node* root);
	//方法四
	void _LeafSize(Node* root, size_t& size);
	//方法一、二
	size_t _GetKLevel(Node* root, int k);
	//方法三
	void _GetKLevel(Node* root, int k, size_t level, size_t& kSize);//注意这里level不加&,而kSize加引用

protected:
	void _PrevOrder(Node* root);
	void _Inorder(Node* root);
	void _PosOrder(Node* root);

private:
	Node* _root;
};

四、二叉树具体操作的实现

本文主要讲解二叉树的构造、拷贝构造、赋值运算符重载、析构、节点个数、树的深度、叶子节点个数、获得第K层节点的个数

而二叉树的遍历将作为单独的一篇博客来讲解

1、构造函数

(1)无参的构造函数

BinaryTree()
	:_root(NULL)
{}

(2)有参的构造函数

通过读取一个数组元素来建立二叉树,遇到特定的非法值则代表该节点NULL节点,使用递归赖建立二叉树,但是每次又要将创建好的节点与上面已经创建好的节点连接起来,所以最好在单独定义一个创建节点的函数,浩然_root连接后面的节点

代码如下:

主函数:

BinaryTree(const T* a, size_t size, const T& invalid)//为什么不能加引用,因为数组名是左值,带有常性,不能被改变
	{
		size_t index = 0;
		_root = _GreateTree(a, size, index, invalid);
	}
cosnt T*&  a,,这个时候const修饰的是a所指向的对象,a本身不具有const性质,假如写成const T*& const a则是正确的

递归函数:

Node* _GreateTree(const T* a, size_t size, size_t& index, const T& invalid)
	{
		Node* root = NULL;
		if (index < size&&a[index] != invalid)
		{
			root = new Node(a[index]);
			root->_left = _GreateTree(a, size, ++index, invalid);//一定是++index,像index++和index+1都是错误的
			root->_right = _GreateTree(a, size, ++index, invalid);
		}
		return root;
	}

index++和index+1的返回的多事临时变量,size_t& index将是错误的,引用要用const修饰

2.析构函数

主函数:

~BinaryTree()
{
	_Destroy(_root);
}
递归遍历释放函数:

void _Destroy(Node* root)
{
	if (root == NULL)
	{
		return;
	}
	_Destroy(root->_left);
	_Destroy(root->_right);
	delete root;
}

3、拷贝构造函数

主函数:

BinaryTree(const BinaryTree<T>& t)
{
	_root = _Copy(t._root);
}

递归拷贝函数:

Node* _Copy(Node* root)
{
	if (root == NULL)
	{
		return NULL;
	}
	Node* newRoot = new Node(root->_data);
	newRoot->_left = _Copy(root->_left);
	newRoot->_right = _Copy(root->_right);
	return newRoot;
}

4、赋值运算符重载

(1)传统写法

主函数:

BinaryTree<T>& operator=(const BinaryTree<T>& t)//传统写法
{
	if (this != &t)
	{
		Node* tmp = _Copy(t._root);
		_Destroy(_root);
		_root = tmp;
	}
	return *this;
}
(2)现代写法

主函数:

BinaryTree<T>& operator=( BinaryTree<T> t)//现代写法
{
	std::swap(_root, t._root);
	return *this;
}

5、总节点的个数

主函数:

size_t Size()//节点个数
{
	return _Size(_root);
}
递归求总个数:

size_t _Size(Node* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return _Size(root->_left) + _Size(root->_right) + 1;
}

6,二叉树的深度

主函数:

size_t Depth()
{
	return _Depth(_root);
}

递归求取二叉树深度:

size_t _Depth(Node* root)
{
	if (root == NULL)
	{
		return 0;
	}
	size_t leftDepth = _Depth(root->_left);//含义为左子树深度,+1则含义不好
	size_t rightDepth = _Depth(root->_right);
	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

7、叶子节点个数

(1)方法一:

主函数:

size_t LeafSize()
{
	return _LeafSize(_root);
}

递归求解函数:

size_t _LeafSize(Node* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->_left == NULL&&root->_right == NULL)
	{
		return 1;
	}
	return _LeafSize(root->_left) + _LeafSize(root->_right);
}
(2)方法二:

在类外定义一个全局的size,涉及线程安全问题

主函数:

size_t LeafSize()
{
	return _LeafSize(_root);
}

递归打印函数:

size_t _LeafSize(Node* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->_left == NULL&&root->_right == NULL)
	{
		++size;
	}
	_LeafSize(root->_left);
	_LeafSize(root->_right);
	return size;
}
(3)方法三:

和方法二大体相同,不过定义的是一个局部的size,通过函数传参传递进去

主函数:

size_t LeafSize()
{
	size_t size = 0; 
	 _LeafSize(_root,size);
	 return size;
}

递归打印函数:

void _LeafSize(Node* root,size_t& size)
{
	if (root == NULL)
	{
		return ;
	}
	if (root->_left == NULL&&root->_right == NULL)
	{
		++size;
	}
	_LeafSize(root->_left,size);
	_LeafSize(root->_right,size);
}

8、获得第K层节点数

(1)方法一:

是第一层的K层,是第二层的K-1层,是第K-1层的第1层

主函数:

size_t GetKLevel(int k)
{
	return _GetKLevel(_root, k);
}

递归函数代码:

size_t _GetKLevel(Node* root, int k)
{
	if (root==NULL)
	{
		return 0;
	}
	if (k == 1)//非子问题
	{
		return 1;
	}
	return _GetKLevel(root->_left, k - 1) + _GetKLevel(root->_right, k - 1);
}

(2)方法二:

主函数:

size_t GetKLevel(int k)
{
	size_t kSize = 0;
	size_t level = 1;
	_GetKLevel(_root, k, level, kSize);
	return kSize;
}

递归函数代码:

void _GetKLevel(Node* root, int k,size_t level,size_t& kSize)//注意这里level不加&,而kSize加引用
{
	if (root == NULL)
	{
		return;
	}
	if (level == k)
	{
		++kSize;
		return;
	}
	_GetKLevel(root->_left, k, level + 1, kSize);
	_GetKLevel(root->_right, k, level + 1, kSize);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值