1. 定义二叉树
首先定义一个简单的二叉树,以供测试:
template<class T>
struct BinaryNode
{
BinaryNode<T>* _left;
BinaryNode<T>* _right;
T _val;
BinaryNode(const T& val = T())
:_left(nullptr)
, _right(nullptr)
, _val(val)
{}
};
void InitABinaryTree(BinaryNode<char>*& Proot)
{
Proot = new BinaryNode<char>('a');
Proot->_left = new BinaryNode<char>('b');
Proot->_right = new BinaryNode<char>('c');
Proot->_left->_left = new BinaryNode<char>('d');
Proot->_right->_left = new BinaryNode<char>('e');
Proot->_right->_right = new BinaryNode<char>('f');
}
为了看清这个二叉树的整体架构,可以使用Graphviz(快速掌握)打印出来,贫道写了一个demo以供参考:
#include <iostream>
//前序遍历
void WriteTree2File(FILE* pFile, BinaryNode<char>* root, int& iNum) // iNum记录当前递归的次数,用以区分Node
{
if (root == nullptr)
{
//fputs("\t Null%d[label=\"null\"]\n", pFile);
return;
}
if (root->_left != nullptr)
{
fprintf(pFile, "\t%c -> %c[weight = 30]\n", root->_val, root->_left->_val);
fprintf(pFile, "\tMidNode%d[shape = point, style=invis]\n", iNum); //设置一个占位node,使成图正
fprintf(pFile, "\t%c -> MidNode%d[weight=30, style=invis]\n", root->_val, iNum);
}
else // 打印空节点,若不想打印空节点,则删除else即可
{
fprintf(pFile, "\t NullL%d[label=\"null\"]\n", iNum);
fprintf(pFile, "\t%c -> NullL%d[weight = 30]\n", root->_val, iNum);
fprintf(pFile, "\tMidNode%d[shape = point, style=invis]\n", iNum); //设置一个占位node,使成图正
fprintf(pFile, "\t%c -> MidNode%d[weight=10, style=invis]\n", root->_val, iNum);
}
if (root->_right != nullptr)
{
fprintf(pFile, "\t%c -> %c[weight = 30]\n", root->_val, root->_right->_val);
}
else
{
fprintf(pFile, "\t NullR%d[label=\"null\"]\n", iNum);
fprintf(pFile, "\t%c -> NullR%d[weight = 30]\n", root->_val, iNum);
}
iNum++;
WriteTree2File(pFile, root->_left, iNum);
WriteTree2File(pFile, root->_right, iNum);
}
int main()
{
//构造一个二叉树
BinaryNode<char> *Proot = nullptr;
InitABinaryTree(Proot);
FILE* pFile = fopen("BinaryTree.dot", "w+"); //将Graphviz的代码写到文件中
if (pFile == nullptr)
{
perror("Create File Failed!\n");
return -1;
}
int i = 0;
fputs( "digraph Graph{ \n", pFile);
WriteTree2File(pFile, Proot, i);
fputs( "}\n",pFile);
fclose(pFile);
system("dot -Tpng -O BinaryTree.dot"); //Graphviz编译
system("BinaryTree.dot.png");
return 0;
}
结果显示:
2. 二叉树递归遍历
二叉树的递归遍历比较简单,根据节点访问的顺序分为前序、中序、后序遍历。要理解递归的过程,可以展开递归的栈。
// 前序
void PreOerder(BinaryNode<char>* root)
{
if (root == nullptr)
return;
std::cout << root->_val << " "; //书写位置不同,可以实现遍历顺序的改变
PreOerder(root->_left);
PreOerder(root->_right);
}
// 中序
void PreOerder(BinaryNode<char>* root)
{
if (root == nullptr)
return;
PreOerder(root->_left);
std::cout << root->_val << " ";
PreOerder(root->_right);
}
// 后序
void PreOerder(BinaryNode<char>* root)
{
if (root == nullptr)
return;
PreOerder(root->_left);
PreOerder(root->_right);
std::cout << root->_val << " ";
}
以前序为例,画出展开图,为了更好看到递归的过程,将代码改写:
// 前序
void PreOerder(BinaryNode<char>* root, std::vector<char> &vec, int &num)
{
if (root == nullptr)
return;
vec.push_back(root->_val);
std::cout << num++ << " ";
PreOerder(root->_left, vec, num);
PreOerder(root->_right, vec, num);
}
作展开图:
3. 二叉树迭代遍历
3.1 前序遍历
迭代法需要用到栈或者队列,下面先看运行的动图(图中贴了个二维码防盗,哈哈~~,还没有写多少东西的公众号㊣):
根据图写出代码:
void PreOrderStack(BinaryNode<char>* root, std::vector<char>& vec)
{
if (root == nullptr)
return;
std::stack< BinaryNode<char>*> st;
st.push(root);
while (!st.empty())
{
BinaryNode<char>* tp = st.top();
vec.push_back(tp->_val);
st.pop();
if (tp->_right != nullptr)
st.push(tp->_right);
if (tp->_left != nullptr)
st.push(tp->_left);
}
}
3.2 中序遍历
根据图写出代码:
void MidOrderStack(BinaryNode<char>* root, std::vector<char>& vec)
{
if (root == nullptr)
return;
std::stack< BinaryNode<char>*> st;
st.push(root);
BinaryNode<char>* cur = root->_left; //防止根被入栈两次
//重新返回根节点访问右子树时,stack为空,cur不为空,
//此时需要添加条件cur != nullptr判断,否则无法访问右子树
while (cur != nullptr || !st.empty())
{
if (cur != nullptr)
{
st.push(cur);
cur = cur->_left;
}
else //cur == nullptr
{
cur = st.top();
vec.push_back(cur->_val);
st.pop();
cur = cur->_right;
}
}
}
3.3 后序遍历
二叉树后序遍历可以使用前序遍历的方式。遍历顺序:左——右——根,反过来是根——右——左,只要中序的代码交换入栈的左右子树顺序即可。最后记得反转输出。
void PostOrderStack(BinaryNode<char>* root, std::vector<char>& vec)
{
if (root == nullptr)
return;
std::stack< BinaryNode<char>*> st;
st.push(root);
while (!st.empty())
{
BinaryNode<char>* tp = st.top();
vec.push_back(tp->_val);
st.pop();
if (tp->_left != nullptr) //这里交换了入栈的顺序
st.push(tp->_left);
if (tp->_right != nullptr)
st.push(tp->_right);
}
reverse(vec.begin(), vec.end());
}
3.4 层序遍历
根据图写出代码:
void LayerOrder(BinaryNode<char>* root, std::vector<char>& vec)
{
if (root == nullptr)
return;
std::queue<BinaryNode<char>*> que;
que.push(root);
while (!que.empty())
{
BinaryNode<char>* cur = que.front();
vec.push_back(cur->_val);
que.pop();
if(cur->_left)
que.push(cur->_left);
if (cur->_right)
que.push(cur->_right);
}
}
如果要将每一层分别写到一个数组里,类似于[[a], [b, c], [d, e, f]] ,则可以这样:
void LayerOrder(BinaryNode<char>* root, std::vector<std::vector<char>>& ret)
{
if (root == nullptr)
return;
std::vector<char> vec;
std::queue<BinaryNode<char>*> que;
que.push(root);
while (!que.empty())
{
int size = que.size(); //记录当前队列的大小,也即当前层的元素数量
for (int i = 0; i < size; i++) //将当前层元素保存,并将当前层的所有元素的子节点添加到队列
{
BinaryNode<char>* cur = que.front();
vec.push_back(cur->_val);
que.pop();
if (cur->_left)
que.push(cur->_left);
if (cur->_right)
que.push(cur->_right);
}
ret.push_back(vec);
vec.clear();
}
}
//遍历可以这样写
//std::vector<std::vector<char>> ret;
//LayerOrder(Proot, ret);
//for (int i = 0; i < ret.size(); i++)
//{
// std::cout << "[";
// for (int j = 0; j < ret[i].size(); j++)
// std::cout << ret[i][j] << ",";
// std::cout << "]" << " ,";
//}