7.4 7.5 二叉树的基本操作与遍历
来源:互联网 发布:公司淘宝客服规则大全 编辑:程序博客网 时间:2024/05/28 18:45
二叉树类定义
这里的二叉树使用了典型的二叉链实现方法:分为左右两个孩子链。这里和之前的链表实现一样,使用了类内定义的结点结构体BinaryTreeNode
。考虑到插入和删除对于位置的要求比较复杂,因此在此处的二叉树实现中并没有包含(在二叉搜索树中,插入和删除是一个重点过程);同时考虑到这是一个原型二叉树,因此也没有使用泛型,对于树的结点的值的类型默认定义为字符串类型。事实上,对于建立二叉树的构造函数,通过字符串类型和一个转换函数,不难将其实现为通用的树。
这里与前面的一些数据结构实现不同的地方在于,定义了一些私有的辅助函数,用来实现公有方法中的递归算法。这样对于共有接口无需特殊指定递归初值,用起来更自然一些。
另外,这里也大量使用了STL。运用成熟的标准库能够大量减少代码量,并且内置的标准库的安全性、边界容错性都是可以保证的。实际上在学习数据结构时,只要学过了的东西,标准库中对应的都是可以直接拿来使用的。
/*** @brief 二叉树类。为了简化定义,数据类型为字符串类型。*/class BinaryTree{public: /** * @brief 二叉树结点定义 */ struct BinaryTreeNode{ BinaryTreeNode * left; //左孩子 BinaryTreeNode * right; //右孩子 std::string value; //值 BinaryTreeNode(std::string v = ""){ value = v; left = right = nullptr; } };private: BinaryTreeNode * _root; //二叉树根节点 /** * @brief 销毁二叉树 * @param BinaryTreeNode * root 需要销毁的二叉树树根 */ void destory(BinaryTreeNode * root); /** * @brief 转化为字符串的辅助递归函数 * @param BinaryTreeNode * root 需要访问的结点 * @param std::string & s 返回的字符串 */ void to_string_sub(BinaryTreeNode * root, std::string & s); /** * @brief 求树的深度的辅助递归子函数 * @param BinaryTreeNode * root 子树树根 * @return int 当前以root为根的子树的高度 */ const BinaryTreeNode * find_sub(BinaryTreeNode * root, const std::string & v); int depth_sub(BinaryTreeNode * root); /** * @brief 求树的宽度的辅助递归子函数 * @param BinaryTreeNode * root 子树树根 * @param int level 当前root所处的层次 * @param std::vector<int> & level_width 每一层的宽度数组 */ void width_sub(BinaryTreeNode * root, int level, std::vector<int> & level_width); /** * @brief 中序遍历递归辅助函数 * @param BinaryTreeNode * root 子树树根 * @param std::string & str 输出字符串 */ void inorder_traversal_sub(BinaryTreeNode * root, std::string & str);public: /** * @brief 构造函数。根据括号表示法的字符串序列创建一棵新的二叉树。 * @param const std::string & str 需要被创建的用括号表示法表示的二叉树 */ BinaryTree(const std::string & str); /** * @brief 构造函数。创造一棵空的二叉树。 */ BinaryTree(); /** * @brief 析构函数 */ ~BinaryTree(); /** * @brief 根据当前的二叉树生成字符串。 * @return std::string 返回当前二叉树的括号表示法字符串 */ std::string to_string(); /** * @brief 查找指定值的结点 * @param std::string & v 需要查找的值 * @return const BinaryTreeNode * 返回的找到的结点指针。未找到则返回nullptr */ const BinaryTreeNode * find(const std::string & v); /** * @brief 得到树的深度(高度) * @return const int 树高 */ const int depth(); /** * @brief 得到树的宽度 * @return const int 树的宽度 */ const int width(); /** * @brief 返回二叉树的中序遍历序列 * @return std::string 二叉树的中序遍历序列 */ std::string inorder_traversal(); /** * @brief 返回二叉树的后序遍历序列 * @return std::string 二叉树的后序遍历序列 */ std::string postorder_traversal(); /** * @brief 返回二叉树的先序遍历序列 * @return std::string 二叉树的先序遍历序列 */ std::string preorder_traversal(); /** * @brief 返回二叉树的层次遍历序列 * @return std::string 二叉树的层次遍历序列 */ std::string level_traversal();};
基本操作的实现
构造函数
实现根据二叉树的括号表示建立二叉树还是比较复杂的。首先我们可能会考虑用递归的方法实现这个函数。我们以二叉树A(B(C,D),E(,F(G)))
来为例。很明显从左至右A
为树根,而紧接着A
后面的括号和最右边的一个括号构成了A
的两个孩子。事实上,很难在A
结点的左孩子和右孩子,也就是意味着不能将大问题化解为两个规模稍小的子问题。因此就不太容易借助递归的方法生成一棵二叉树。
那么不用递归的话,对于这种具有层次的数据结构,必然需要借助栈来实现。这样对于括号表示的二叉树,我们有如下四种情况需要考虑:
(a)读取到普通字符。我们将普通的字符加入当前结点的字符串current_string
。同时需要判断当前字符的下一个字符是否为要求的特殊字符(
、)
、、,
。如果是,就要创建结点,并把栈顶的左右孩子分别连接起来。
(b)读取到(
,表示层次更深入了一层。这时候就需要将当前结点current
进栈。同时把标志位flag
设置为左孩子(二叉树的括号表示是从左到右表示的)。
(c)读取到)
,表示以栈顶元素为根节点的子树已经处理完毕了。这时候只需要将其弹出即可,恢复current
为栈顶元素。
(d)读取到,
,表示需要从左子树处理右子树,设置标志位flag
为右孩子即可。
实际上,在(a)过程中增加一个条件判断是否应该新建节点,比较麻烦;但是省去了碰到标志字符新建节点时,左右子树判断的麻烦:那样的实现在栈中需要同时保存左右子树信息(类似于手工模拟递归)。
另外在程序的健壮性方面,增加了一系列断言语句判断输入的字符串是否合法。还有一点要说明的是,这里先将树根建立并放入了栈,这样能够保证第一次引用st.top()
不会出现栈空的情况,也有利于配对括号的判断。
BinaryTree::BinaryTree(const std::string & str){ if (str.empty()){ //空串代表空树 _root = nullptr; return; } stack<BinaryTreeNode *> st; //使用栈作为辅助 //左右孩子常量定义。 const int left_child = 0; const int right_child = 1; BinaryTreeNode * current; //表示当前创建的结点指针 std::string current_string; //当前结点的字符串值 int flag = left_child; int i; for (i = 0; i < str.length() && str[i] != '('; i++){ current_string.push_back(str[i]); } current = new BinaryTreeNode(current_string); current_string.clear(); for (; i < str.length(); i++){ if (str[i] == '('){ flag = left_child; st.push(current); } else if (str[i] == ')'){ assert(!st.empty()); //检查括号配对情况 current = st.top(); st.pop(); } else if (str[i] == ','){ flag = right_child; } else{ current_string.push_back(str[i]); //判断下一个字符是否为当前小节的结束符 if (i < str.length() && (str[i + 1] == '(' || str[i + 1] == ')' || str[i + 1] == ',')){ current = new BinaryTreeNode(current_string); if (flag == left_child){ st.top()->left = current; } else{ st.top()->right = current; } current_string.clear(); } } } assert(st.empty()); //检查括号配对情况 _root = current;}
生成括号表示法字串
对于已经建立的二叉树,要生成括号表示法,用递归很容易解决。上面提到了使用to_string_sub
作为递归函数,字符串类型的引用参数s
来存放转化后的结果。
void BinaryTree::to_string_sub(BinaryTreeNode * root, std::string & s){ s.append(root->value); if (!root->left && !root->right) return; s.push_back('('); if (root->left) to_string_sub(root->left, s); if (root->right){ s.push_back(','); to_string_sub(root->right, s); } s.push_back(')'); return;}std::string BinaryTree::to_string(){ std::string result; this->to_string_sub(this->_root, result); return result;}
查找指定值的结点
同上面一样,依然是一个简洁的递归模型。首先判断当前结点的值是否满足,不满足则依次寻找左子树和右子树是否存在满足的结点。
const BinaryTree::BinaryTreeNode * BinaryTree::find(const std::string & v){ return this->find_sub(_root, v);}const BinaryTree::BinaryTreeNode * BinaryTree::find_sub(BinaryTreeNode * root, const std::string & v){ if (!root) return nullptr; if (root->value == v) return root; const BinaryTreeNode * leftnode = this->find_sub(root->left, v); return leftnode?leftnode:this->find_sub(root->right, v);}
求树高
在之前的递归练习题中已经说明了求解思路了:利用二叉树的递归定义特点写出公式
int BinaryTree::depth_sub(BinaryTreeNode * root){ if (!root) return 0; return std::max(depth_sub(root->left), depth_sub(root->right)) + 1;}const int BinaryTree::depth(){ return this->depth_sub(_root);}
求树的宽度
求树的宽度,这里依然使用递归的思想去做。在递归时每扫描一个结点,就把当前结点所处的层次的结点个数加一。最后对所有层次的结点数取一个最大值就可以了。max_element
就是返回在一个迭代器指定的区间内的最大值,返回的也是迭代器,需要使用*
操作符得到具体的值。
void BinaryTree::width_sub(BinaryTreeNode * root, int level, vector<int> & level_width){ if (!root) return; if (level < level_width.size()){ //判断是否达到新的一层 level_width[level]++; } else{ level_width.push_back(1); } //分别处理左右子树 this->width_sub(root->left, level + 1, level_width); this->width_sub(root->right, level + 1, level_width); return;}const int BinaryTree::width(){ vector<int> levels; this->width_sub(_root, 0, levels); return *max_element(levels.begin(), levels.end()); //返回最大值即为树的宽度}
树的销毁
销毁树和前面的操作没有太大区别,就是需要先保留根指针,通过根寻找左右子树,销毁左右子树以后才能销毁根。
void BinaryTree::destory(BinaryTreeNode * root){ if (!root) return; destory(root->left); destory(root->right); delete root; return;}BinaryTree::~BinaryTree(){ destory(_root);}
树的遍历
因为树是非线性结构,因此其遍历也不想线性表的遍历那么简单。总的来说,分为广度遍历(又叫层次遍历)和深度遍历。而对于二叉树深度遍历,又可以分为先序遍历,中序遍历和后序遍历。
对于广度遍历来说,在前面的队列中也介绍过,需要借助队列来进行辅助操作。
std::string BinaryTree::level_traversal(){ std::string result; std::queue<BinaryTreeNode *> q; //使用队列作为层次遍历的辅助容器 q.push(_root); while(!q.empty()){ result.append(q.front()->value); result.push_back(' '); if(q.front()->left) q.push(q.front()->left); if(q.front()->right) q.push(q.front()->right); q.pop(); } return result;}
对于深度遍历,先序序列就是“根左右”,中序序列就是“左根右”,后序序列就是“左右根”。
下面分别给出中序遍历:
void BinaryTree::inorder_traversal_sub(BinaryTreeNode * root, std::string & str){ if(!root) return; this->inorder_traversal_sub(root->left, str); str.push_back(' '); str.append(root->value); str.push_back(' '); this->inorder_traversal_sub(root->right, str); return;}std::string BinaryTree::inorder_traversal(){ std::string result; this->inorder_traversal_sub(_root, result); return result;}
其他的两种遍历方法较为类似。
对于遍历的非递归化,依然是要借助栈来辅助。以中序遍历为例,其访问顺序是先左子树,再访问树根,最后访问右子树。因此,栈中存放的都是未访问的结点。在整个栈不空的条件下,首先需要进入左子树的最里层,访问最左边的叶子结点后,将其弹出,然后开始处理其右子树。
std::string BinaryTree::inorder_traversal(){ std::string result; std::stack<BinaryTreeNode *> st; BinaryTreeNode * p = _root; while(!st.empty() || p != nullptr){ //先寻找最左结点(可能包含右子树) while(p != nullptr){ st.push(p); p = p->left; } if(!st.empty()){ //弹出并访问最左边结点 p = st.top(); st.pop(); result.append(p->value); result.push_back(' '); //将其右子树进栈,准备下一轮处理。 //对于其右子树的处理,依然从最左边开始 //遵循“左根右”的顺序。 p = p->right; } } return result;}
测试代码
int main(){ string s = "Von.Neuuman(Turing(,Floyd(Donald.E.Knuth)),Dijkstra(Prim,Yann Lecun(,Andrew Y. Ng)))"; string dest = "Dijkstra"; //cin >> s; BinaryTree bt(s); //output the tree cout << "(1)BinaryTree: " << bt.to_string() << endl; auto hnode = bt.find(dest); cout << "(2)the lchild and rchild node of \"Dijkstra\" node: " << hnode->left->value << "," << hnode->right->value << endl; cout << "(3)the depth of BinaryTree: " << bt.depth() << endl; cout << "(4)the width of BinaryTree: " << bt.width() << endl; cout << "(5)inorder traversal: " << bt.inorder_traversal() << endl; cout << "(6)level traversal: " << bt.level_traversal() << endl; cout << "(7)Destroy BinaryTree."; return 0;}
输出
(1)BinaryTree: Von.Neuuman(Turing(,Floyd(Donald.E.Knuth)),Dijkstra(Prim,Yann Lecun(,Andrew Y. Ng)))(2)the lchild and rchild node of "Dijkstra" node: Prim,Yann Lecun(3)the depth of BinaryTree: 4(4)the width of BinaryTree: 3(5)inorder traversal: Turing Donald.E.Knuth Floyd Von.Neuuman Prim Dijkstra Yann Lecun Andrew Y. Ng(6)level traversal: Von.Neuuman Turing Dijkstra Floyd Prim Yann Lecun Donald.E.Knuth Andrew Y. Ng(7)Destroy BinaryTree.
- 7.4 7.5 二叉树的基本操作与遍历
- 线索二叉树的遍历与基本操作(史上最全)
- 二叉树的基本操作及遍历
- 二叉树的基本操作和遍历
- 二叉树基本操作 遍历
- 查找二叉树的基本操作以及层次遍历
- 二叉树的构建,遍历等基本操作
- 【数据结构】二叉树的简单遍历及基本操作
- PHP数据结构之九 PHP储存二叉树,二叉树的创建与二叉树的基本操作 遍历二叉树算法
- 二叉树的建立与基本操作
- 二叉树的定义与基本操作
- 二叉树的基本操作,前序遍历,后续遍历,中序遍历
- 二叉树基本操作及层次遍历
- 数据结构(十二) 二叉树的基本操作 --- 创建一个二叉树 前中后序遍历二叉树
- 二叉树的遍历操作
- 二叉树的遍历操作
- 二叉树的遍历操作
- 二叉树的遍历操作
- *C语言操作符总结*
- U3D工程自动保存
- Spring boot + redis 实现session 共享管理
- 【算法】差分约束系统
- 酷炫的Activity切换动画,打造更好的用户体验
- 7.4 7.5 二叉树的基本操作与遍历
- 网络编程UDP
- 关于socket一个新的连接是否会产生一个新的socket通信
- 框体
- Java三大主流开源工作流引擎技术分析
- POJ 1979 Red and Black (简单dfs)
- win10下注册MSCOMM32控件
- 浏览器实现复制内容到剪贴板 -- clipboard.js
- TCP和UDP