二叉树的建立
二叉树我这里采用了递归的方法建立,从根节点出发,先递归左子树,再递归右子树。空节点用'#'表示,建立的顺序如下图。注意在输入节点的时候也要按照建立的顺序输入,图中节点的顺序:ABC##D##E##
给出代码如下:
//递归生成二叉树
void Createtree(BTree& T)
{
char aa;
cin >> aa;
if (aa == '#')
{
T = NULL;
}
else {
T = new TreeNode;
T->val = aa; //生成根节点
Createtree(T->left); //递归创建左子树
Createtree(T->right); //递归创建右子树
}
}
二叉树的遍历
先序遍历
先序遍历用递归很好实现,先访问根节点,然后一直递归访问左子树节点,再访问右子树节点。代码如下:
void Front(BTree T)
{
vector<char> res;
if (T != nullptr)
{
res.push_back(T->val);
visit(res); //访问根节点
Front(T->left); //访问左子树
Front(T->right); //访问右子树
}
}
如果不用递归呢?可以采用栈来模拟递归。先序遍历的顺序:根左右,所以可以先把根节点放入栈中,弹出根节点后,再分别把左右节点放入,那么此时问题来了,是先放左节点还是右节点呢? 我们知道栈是一种先入后出的数据结构,如果先入左节点再入右节点,那么在出栈的时候则是:根右左,不满足先序遍历的顺序,所以在入栈的时候先入右节点再入左节点,出来的顺序则是:根左右。然后一直按照根右左入栈下去,一直找到左子树最左边节点。
先序遍历非递归
void Front(BTree T)
{
if (T == NULL) return ; //传入节点不合法
stack<TreeNode*> sk;
vector<char> res;
sk.push(T); //根节点入栈
while (!sk.empty())
{
TreeNode* node = sk.top();
sk.pop();
res.push_back(node->val); //访问根节点
if (node->right) sk.push(node->right); //根右节点入栈
if (node->left) sk.push(node->left); //根左节点入栈
}
visit(res);
}
中序遍历
已经了解了先序遍历的思想,那么中序遍历又是怎么样子的呢?中序遍历顺序:左根右 ,也就是说得先找到最左边的一个节点处理他。
如果是这种情况:
一直找到最左边是C,把C入栈,再弹出该节点存入数组中,然后此时栈最上面一定是B(因为先一直找左节点),再把B节点弹出,存入数组,最后把D放入栈中,类似的再弹出放入数组。
如果是这种情况:
此时没有右节点,前两步与第一种情况相同,把B弹出后,继续往上找。
前两种都是最左边节点无孩子的情况。总的来说最左边无孩子,则往上找节点,再找右节点。
还有一种情况,最左边节点有孩子的情况
B为最左边节点,B入栈然后弹出,然后找他的右孩子D,放入栈中然后弹出。再向上找B的根节点,再重复以上三种情况。
总的来说:最左边有孩子,先找该节点的右孩子,再往上找。
总结三种情况:在弹出最左边一个节点后,则可以用一个判断语句判断:如果当前节点无右孩子节点,则向上找,如果有右孩子节点,则把右节点放入栈中。
给出代码:
//中序遍历非递归
void Inorder(BTree T)
{
vector<char> vv;
stack<BTree> sk;
TreeNode* cur = T;
while (cur ||!sk.empty())
{
if (cur != nullptr)
{
sk.push(cur);
cur = cur->left; //最左边找节点
}
else
{
cur = sk.top(); //处理最左边节点
sk.pop();
vv.push_back(cur->val);
cur = cur->right; //处理右孩子节点
} //if else 是如果右孩子不存在,则向上找
//存在则把右孩子放入栈中
}
visit(vv);
}
递归则很好写出:
void Inorder(BTree T)
{
vector<char> res;
if (T != nullptr)
{
Inorder(T->left); //一直左找
res.push_back(T->val); //处理左节点
visit(res);
Inorder(T->right); //在处理右节点
}
}
后序遍历
后续遍历,前面已经用栈实现了先序遍历,如果我们把入栈的顺序改一下,先入根节点,然后弹出,再入左节点,再入右节点的话,最后得到的顺序是:根右左。很有意思,如果把它倒过来,则是左右根,就是后序遍历,所以后序遍历只需要改变左右节点的入栈顺序,再让输出反转则得到了后序遍历结果。(注下面动图没有让输出反转,只是模拟了改变左右节点入栈的顺序)
给出递归和非递归代码:
后序遍历非递归
void Back(BTree T)
{
if (T == NULL) return ;
stack<TreeNode*> sk;
vector<char> res;
sk.push(T);
while (!sk.empty())
{
TreeNode* node = sk.top();
sk.pop();
res.push_back(node->val);
if (node->left) sk.push(node->left); //改变顺序
if (node->right) sk.push(node->right);
}
reverse(res.begin(), res.end()); //输出反转
visit(res);
}
层序遍历
层序遍历用一个队列模拟,先存入根节点,弹出;然后放入根节点的左孩子与右孩子,在弹出根节点的左孩子,同时存入左孩子的左孩子与右孩子,再在队首弹出根节点的左孩子,再在队尾入根节点右孩子的左孩子与右孩子。即出一个节点,就要存入该节点的左孩子与右孩子。
//层序遍历
void level(TreeNode* T)
{
vector<char> res;
queue<TreeNode*> que;
if (T != nullptr) que.push(T); //存入根节点
while (!que.empty())
{
int size = que.size();
for (int i = 0; i < size; ++i)
{
TreeNode* node = que.front();
que.pop();
res.push_back(node->val); //根节点的值存在数组
if (node->left) que.push(node->left); //队列存入根节点左孩子
if (node->right) que.push(node->right); //队列存入根节点右孩子
}
}
visit(res);
}
完整代码
二叉树数据结构
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
};
using BTree = TreeNode*;
二叉树访问函数
void visit(vector<char> nums)
{
for (int i=0;i<nums.size();++i)
cout << nums[i] << " ";
}
主函数
int main()
{
TreeNode* T;
cout << "建立二叉树:" << endl;
Createtree(T);
cout << "前序遍历:" << endl;
Front(T); cout << endl;
cout << "中序遍历:" << endl;
Inorder(T); cout << endl;
cout << "后序遍历:" << endl;
Back(T); cout << endl;
cout << "层序遍历" << endl;
level(T); cout << endl;
//ABC##D##E##
}