数据结构——C++实现二叉搜索树,前中后序、层序迭代遍历配合仿函数

通过介绍二叉搜索树,到实现最基础的二叉树模型,四种迭代遍历方式。

结点模型

cpp 复制代码
template<class Type>
class binary_tree
{
	/* 二叉树是由多个结点组成的,所以定义一个内部的结点类用于构建树 */
	class BTNode
	{
		/* 不允许无参构造,因为编译器会对m_val采用默认构造,如果是int类型会导致随机值,可能造成问题 */
		BTNode() = delete;
	public:
		/* 防止隐式类型转换 */
		explicit BTNode(const Type& _val) : m_val(_val), m_left(nullptr), m_right(nullptr) {}

		Type m_val;
		BTNode* m_left;
		BTNode* m_right;
	};
	
	/* 创建结点,如果new失败则抛异常,需要在调用的时候捕获 */
	static BTNode* create_node(const Type& _val)
	{
		BTNode* newNode = new BTNode(_val);
		if (newNode == nullptr)
		{
			throw std::exception("create_node failed");
		}
		return newNode;
	}
	

二叉树遍历

前序遍历

cpp 复制代码
/* 
		前中后须遍历,统一使用标记法,使得三种遍历代码结构相似,和层序也有点相似
		如果用第一种方法,前序会跟后序相似,但是中序会有差别 
		
		同时通过传入function对象,使得遍历拿到结点后可以执行自定义操作
		返回值为bool是为了确认是否继续往后遍历,比如find,找到后应该返回true,表示不再往后遍历的,树中保证值是唯一的
	*/
	static void pre_order(BTNode* root, std::function<bool(BTNode*)> func)
	{
		/* 方法一 */
		//if (root == nullptr)
		//	return;

		//std::stack<BTNode*> st;
		//st.push(root);

		//while (!st.empty())
		//{
		//	BTNode* node = st.top();
		//	st.pop();

		//	if (node->m_right)
		//	{
		//		st.push(node->m_right);
		//	}
		//	if (node->m_left)
		//	{
		//		st.push(node->m_left);
		//	}

		//	/* 返回true时不继续操作 */
		//	if (func(node))
		//	{
		//		return;
		//	}
		//}

		/* 方法二:标记法 */
		if (root == nullptr)
			return;
		
		/* 思想:栈模拟递归,为了解决遍历和操作的次序不一致,使用标记法,在要处理的结点后面放一个空 */
		std::stack<BTNode*> st;
		st.push(root);
		while (!st.empty())
		{
			auto node = st.top();
			if (node != nullptr)
			{
				/* 先将结点从栈中拿出,用来访问左右,之后再按顺序插入 */
				st.pop();

				/* 中左右,因此入栈顺序要是右左中 */
				if (node->m_right)
					st.push(node->m_right);

				if (node->m_left)
					st.push(node->m_left);

				/* 访问过,但是没处理,用空来标识 */
				st.push(node);
				st.push(nullptr);
			}
			else
			{
				/* 弹掉nullptr */
				st.pop();

				node = st.top();
				st.pop();

				func(node);
			}
		}
	}

中序遍历

cpp 复制代码
	static void in_order(BTNode* root, std::function<bool(BTNode*)> func)
	{
		/* 方法一 */
		//std::stack<BTNode*> st;
		//BTNode* cur = root;
		//while (!st.empty() || cur)
		//{
		//	if (cur == nullptr)
		//	{
		//		/* 拿到根 */
		//		cur = st.top();
		//		st.pop();
		//		func(cur);
		//		cur = cur->m_right;
		//	}
		//	else
		//	{
		//		st.push(cur);
		//		cur = cur->m_left;
		//	}
		//}

		/* 方法二:标记法 */
		if (root == nullptr)
			return;

		std::stack<BTNode*> st;
		st.push(root);
		while (!st.empty())
		{
			auto node = st.top();
			if (node != nullptr)
			{
				/* 先将结点从栈中拿出,用来访问左右,之后再按顺序插入 */
				st.pop();

				/* 左中右,因此入栈顺序要是右中左 */
				if (node->m_right)
					st.push(node->m_right);

				/* 访问过,但是没处理,用空来标识 */
				st.push(node);
				st.push(nullptr);

				if (node->m_left)
					st.push(node->m_left);
			}
			else
			{
				st.pop();

				node = st.top();
				st.pop();

				func(node);
			}
		}
	}

后序遍历

cpp 复制代码
	static void post_order(BTNode* root, std::function<bool(BTNode*)> func)
	{
		if (root == nullptr)
			return;

		std::stack<BTNode*> st;
		st.push(root);
		while (!st.empty())
		{
			auto node = st.top();
			if (node != nullptr)
			{
				/* 先将结点从栈中拿出,用来访问左右,之后再按顺序插入 */
				st.pop();

				/* 访问过,但是没处理,用空来标识 */
				st.push(node);
				st.push(nullptr);

				/* 左右中,因此入栈顺序要是中右左 */
				if (node->m_right)
					st.push(node->m_right);

				if (node->m_left)
					st.push(node->m_left);
			}
			else
			{
				st.pop();

				node = st.top();
				st.pop();

				func(node);
			}
		}
	}

层序遍历

cpp 复制代码
	static void level_order(BTNode* root, std::function<bool(BTNode*)> func)
	{
		/* 
			广度优先遍历
			每次一个结点入队,要访问时出队,并且将该结点的左右结点入队
			因为访问和操作次序要一致,因此不能用栈,栈是模拟递归的
		*/

		if (root == nullptr)
			return;

		std::queue<BTNode*> q;
		q.push(root);
		BTNode* cur = root;

		while (!q.empty())
		{
			cur = q.front();
			q.pop();

			func(cur);

			if(cur->m_left)
				q.push(cur->m_left);

			if (cur->m_right)
				q.push(cur->m_right);
		}
	}

二分遍历

cpp 复制代码
	static BTNode* binary_search(BTNode* root, const Type& _val)
	{
		BTNode* cur = root;
		BTNode* parent = nullptr;
		while (cur)
		{
			if (cur->m_val == _val)
				return cur;

			parent = cur;
			if (cur->m_val > _val)
			{
				cur = cur->m_left;
			}
			else if (cur->m_val < _val)
			{
				cur = cur->m_right;
			}
		}
		return nullptr;
	}

增删查改实现

cpp 复制代码
public:
	using iterator = BTNode * ;
	
	binary_tree() : m_root(nullptr) {}
	explicit binary_tree(const Type& _val) : m_root(create_node(_val)) {}
	~binary_tree()
	{
		/* 遍历每一个结点逐一析构,return false表示不要提前结束 */
		pre_order(m_root, [](BTNode* node) {delete node; return false; });
	}

	void insert(const Type& _val)
	{
		BTNode* newNode = nullptr;
		try
		{
			newNode = create_node(_val);
		}
		catch (const std::exception& exp)
		{
			std::cout << exp.what() << std::endl;
			return;
		}
		
		/* 目前为空树 */
		if (m_root == nullptr)
		{
			m_root = newNode;
			return;
		}

		BTNode* cur = m_root;
		BTNode* parent = nullptr;
		while (cur)
		{
			if (cur->m_val == _val)
				return;

			parent = cur;
			if (cur->m_val > _val)
			{
				cur = cur->m_left;
			}
			else if (cur->m_val < _val)
			{
				cur = cur->m_right;
			}
		}
		cur = newNode;
		
		if (parent->m_val > _val)
			parent->m_left = cur;
		else
			parent->m_right = cur;
	}

	void erase(const Type& _val)
	{
		BTNode* cur = m_root;
		BTNode* parent = nullptr;
		while (cur)
		{
			if (cur->m_val == _val)
				break;

			parent = cur;
			if (cur->m_val > _val)
			{
				cur = cur->m_left;
			}
			else if (cur->m_val < _val)
			{
				cur = cur->m_right;
			}
		}

		if (cur == nullptr)
			return;

		/* 分三种情况:左为空,右为空,都为空 */
		BTNode* left = cur->m_left;
		BTNode* right = cur->m_right;
		if (left == nullptr)
		{
			if (cur == m_root)
			{
				m_root = cur->m_right;
			}
			if (parent->m_right == cur)
			{
				parent->m_right = cur->m_right;
			}
			else
			{
				parent->m_left = cur->m_right;
			}
			delete cur;
		}
		else if (right == nullptr)
		{
			if (cur == m_root)
			{
				m_root = cur->m_left;
			}
			if (parent->m_right == cur)
			{
				parent->m_right = cur->m_left;
			}
			else
			{
				parent->m_left = cur->m_left;
			}
			delete cur;
		}
		else
		{
			/* 
				左右都不为空,要找左树的最大结点或右树的最小结点
				比如找到左树的最大结点后,交换该结点和cur的值,之后重新链接,最后删除该最大结点
			*/
			/* 要记录目标结点的父结点,因为要判断目标结点是父结点的左还是右,才能正确链接 */
			auto minLeft = cur->m_left;
			auto minLeftParent = cur;

			while (minLeft->m_right)
			{
				minLeftParent = minLeft;
				minLeft = minLeft->m_right;
			}

			cur->m_val = minLeft->m_val;
			
			if (minLeftParent->m_left == minLeft)
			{
				minLeftParent->m_left = minLeft->m_left;
			}
			else
			{
				minLeftParent->m_right = minLeft->m_right;
			}
			delete minLeft;
		}
	}

	iterator find(const Type& _val)
	{
		return binary_search(m_root, _val);
	}

	iterator begin()
	{
		return m_root;
	}	

private:
	BTNode* m_root;
};

运算符重载

cpp 复制代码
/* 重载流提取 */
friend std::ostream& operator<<(std::ostream& out, binary_tree& tree)
{
	auto print = [](BTNode* node) -> bool
	{
		std::cout << node->m_val << ' ';
		return false;
	};

	std::cout << "前序遍历:";
	tree.pre_order(tree.m_root, print);
	std::cout << std::endl;

	std::cout << "中序遍历:";
	tree.in_order(tree.m_root, print);
	std::cout << std::endl;

	std::cout << "后序遍历:";
	tree.post_order(tree.m_root, print);
	std::cout << std::endl;

	std::cout << "层序遍历:";
	tree.level_order(tree.m_root, print);
	std::cout << std::endl;

	return out;
}

测试代码

cpp 复制代码
void tree_test()
{
	binary_tree<int> tree;
	std::vector<int> arr{ 8,3,10,1,6,14,4,7,13,0,16 };
	/*
		8 3 1 0 6 4 7 10 14 13 16

		0 1 3 4 6 7 8 10 13 14 16

		0 1 4 7 6 3 13 16 14 10 8
		
		8 3 10 1 6 14 0 4 7 13 16
	*/
	for (auto& num : arr)
	{
		tree.insert(num);
	}
	
	std::cout << tree << std::endl;

	tree.erase(6);
	std::cout << tree << std::endl;

	if (tree.find(8))
	{
		std::cout << "找到了\n";
	}

	tree.erase(8);

	if (tree.find(8))
	{
		std::cout << "找到了\n";
	}
	std::cout << tree << std::endl;
}

但是二叉搜索树有一个缺点,在遇到插入的值都是比树中所有值都大的结点时,会一直往右插入,从而形成一个单链表。因而失去了二叉树的优势。平衡二叉树(AVL树)可以解决这个问题。

相关推荐
冉佳驹几秒前
数据结构 ——— 快速排序的时间复杂度以及规避最坏情况的方法
c语言·数据结构·算法·排序算法·快速排序算法·三数取中
徐浪老师1 小时前
C语言实现冒泡排序:从基础到优化全解析
c语言·算法·排序算法
hr_net1 小时前
图论入门编程
算法·图论
李小白661 小时前
各种排序算法
数据结构·算法·排序算法
浪前1 小时前
排序算法之冒泡排序篇
数据结构·算法·排序算法
小黄编程快乐屋1 小时前
各个排序算法基础速通万字介绍
java·算法·排序算法
PeterClerk1 小时前
图论基础知识
算法·深度优先·图论
是糖不是唐1 小时前
代码随想录算法训练营第五十八天|Day58 图论
c语言·算法·图论
Eric.Lee20213 小时前
数据集-目标检测系列- 装甲车 检测数据集 armored_vehicles >> DataBall
python·算法·yolo·目标检测·装甲车检测