通过介绍二叉搜索树,到实现最基础的二叉树模型,四种迭代遍历方式。
结点模型
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树)可以解决这个问题。