二叉树的遍历
我们这里将遍历方法分为:4类7种
4类分别是先序遍历,中序遍历,后序遍历,层次遍历
其中前三个又分为递归与不递归的方法
二叉树的结点定义:
我们一一来看:
1.递归的先序遍历
思路:利用递归的特性,我们将树扁平化处理,所以遍历方法就是按先序的顺序进行遍历
先打印当前结点,再递归调用打印树的左孩子和右孩子
void PreOrder(BtNode *ptr)
{
if(ptr != NULL)
{
cout << ptr->data << " ";
PreOrder(ptr->leftchild);
PreOrder(ptr->rightchild);
}
}
2.递归的中序遍历
思路:利用递归的特性,我们将树扁平化处理,所以遍历方法就是按中序的顺序进行遍历
先递归调用打印树的左孩子,再打印当前结点,再递归调用打印树的右孩子
void InOrder(BtNode *ptr)
{
if(ptr != NULL)
{
InOrder(ptr->leftchild);
cout << ptr->data << " ";
InOrder(ptr->rightchild);
}
}
3.递归的后序遍历
思路:利用递归的特性,我们将树扁平化处理,所以遍历方法就是按后序的顺序进行遍历
先递归调用打印树的左孩子和右孩子,再打印当前结点
void PastOrder(BtNode *ptr)
{
if(ptr != NULL)
{
PastOrder(ptr->leftchild);
PastOrder(ptr->rightchild);
cout << ptr->data << " ";
}
}
4.非递归的先序遍历
思路:
- 申请一个栈,先将根节点入栈,然后进入while循环(栈不空)
- 当栈不为空时,获取栈顶元素进行打印,并出栈
- 然后进行判断,获取的栈顶元素节点的右孩子不空时,将右孩子入栈
- 再进行判断,获取的栈顶元素节点的左孩子不空时,将左孩子入栈
- 特别注意:这里右孩子比左孩子先入栈
//非递归先序遍历二叉树
void NicePreOrder(BtNode *ptr)
{
if(NULL == ptr) return;
stack<BtNode*> st;
st.push(ptr); //非递归先序遍历二叉树 因为 第一个打印根 所以先将根节点入栈
while(!st.empty())//循环条件只是栈不为空即可 然后 当栈不空的时候 出栈一个 然后入栈两个
{ //入的两个在不为NULL的情况下顺序是 右孩子先入栈 左孩子再入 打印的时候才能中左右
BtNode *p = st.top();
st.pop();
cout << p->data << " ";
if(p->rightchild != NULL)
{
st.push(p->rightchild);
}
if(p->leftchild != NULL)
{
st.push(p->leftchild);
}
}
cout << endl;
}
5.非递归的中序遍历
思路:
- 申请一个栈,先将根节点入栈,然后进入while循环(栈不空或者ptr不为NULL)
- 又是一个while循环,当ptr不为NULL时,判断ptr节点的左孩子不空时,将左孩子依次循环入栈
- 循环出来后,获取栈顶元素,并出栈,并进行打印
- 再让ptr指向其右孩子,从头开始进入循环判断
//非递归中序遍历二叉树
void NiceInOrder(BtNode *ptr)
{
if(NULL == ptr) return;
stack<BtNode*> st;//非递归中序遍历二叉树 因为 第一个打印不是根 所以先不要将根节点入栈
while(ptr != NULL || !st.empty()) //循环条件是ptr不为空 ptr指向需要打印的节点 当ptr为NULL 且栈也空的时候 就结束了
{
while(ptr != NULL) //进去后 先从根开始 将左孩子一一入栈 当ptr为空时 左孩子都到头了
{
st.push(ptr);
ptr = ptr->leftchild;
}
ptr = st.top(); //这时候,出栈一个 这时候情况类似于 左孩子为空 出的栈为左根右的中(根) 所以 用ptr指向 将其打印
st.pop();
cout << ptr->data << " ";
ptr = ptr->rightchild; //再将ptr指向这个打印的值的右孩子 将其作为根 从while循环头开始判断是否有左孩子 有的话继续入栈
}
cout << endl;
}
6.非递归的后序遍历
思路:
- 申请一个栈,先将根节点入栈,再申请一个节点用来保存最近访问并打印的元素,然后进入while循环(栈不空或者ptr不为NULL)
- 又是一个while循环,当ptr不为NULL时,判断ptr节点的左孩子不空时,将左孩子依次循环入栈
- 循环出来后,获取栈顶元素,并出栈,这时并不着急先打印
- 这时判断出栈元素的右孩子是否为空或者右孩子是最近访问并打印的元素,如果满足则打印
- 如果不满足,让ptr从新入栈,再让ptr指向其右孩子,从头开始进入循环判断
//非递归后序遍历二叉树
void NidePastOrder(BtNode *ptr)
{
if(ptr == NULL) return;
stack<BtNode*> st;//非递归中序遍历二叉树 因为 第一个打印不是根 所以先不要将根节点入栈
BtNode *flag = NULL;//这个标记位 是记录上一个打印的值, 防止无限循环进入右孩子进行打印
while(ptr!=NULL || !st.empty()) //循环条件是ptr不为空 ptr指向需要打印的节点 当ptr为NULL 且栈也空的时候 就结束了
{
while(ptr != NULL) //进去后 先从根开始 将左孩子一一入栈 当ptr为空时 左孩子都到头了
{
st.push(ptr);
ptr = ptr->leftchild;
}
ptr = st.top(); //这时候,出栈一个 这时候情况类似于 左孩子为空 出的栈为左根右的中(根) 所以出栈后 用ptr指向
st.pop();//但是这是后序遍历,顺序是左右根(中) 所以有可能右孩子不为空 所以先不着急打印中间孩子(注意:这里只是从栈里取出来了,并未进行打印,需要判断其右孩子情况)
if(ptr->rightchild == NULL || ptr->rightchild == flag)//如果指向的这个值的右孩子为空 或者 右孩子是刚刚才打印了值
{ //则表示它的右孩子已经遍历完了 该打印自己了 遵守了左右根(中)的规则
cout << ptr->data << " "; //这个时候 可以将ptr指向的节点打印
flag = ptr; //然后将flag标记位 更新一下 记录为最近刚刚打印的值ptr
ptr = NULL; //ptr指向的值打印了之后,就将其置空 以便于再次从栈中取值(取其上一级节点(父节点))从新进行判断
}
else //如果if为假 则证明这个ptr指向的节点的右孩子不为空 且这个ptr指向的值还没有进行过打印
{
st.push(ptr); //所以将刚从栈中取出的值ptr 从新入栈 然后让ptr指向其右孩子 将其作为根 再回到while循环头
ptr = ptr->rightchild; //从while循环头开始判断是否有左孩子 有的话继续入栈
}
}
cout << endl;
}
7.包含stk节点的非递归中序遍历以及非递归后序遍历
首先我们知道我们可以用出栈的次数来判断是否打印。
所以Stknode结构体节点中包含了二叉树结点以及出栈次数的信息
Stknode结构体如下:
7.1:包含stk节点的非递归中序遍历:
思路:
- 申请一个栈,先将根节点入栈,然后进入while循环(栈不空)
- 当栈不为空时,获取栈顶元素,并出栈,此时不着急进行打印
- 将其出栈次数++后进行判断,当此时出栈次数为2时,则将其打印,并判断其右孩子是否存在,若不空时,将右孩子入栈
- 如果次数不足2次,则将刚出栈的结点从新入栈,然后再进行判断,若其左孩子不空时,将左孩子入栈
- 特别注意:这里右孩子比左孩子先入栈
//非递归中序遍历二叉树 包含stk节点
void StkNiceInOrder(BtNode *ptr)
{
if(NULL == ptr) return;
stack<Stknode> st;
st.push(Stknode(ptr));
while(!st.empty())
{
Stknode pnewnode = st.top();
st.pop();
if(++pnewnode.pop_num == 2)//中序遍历规则:当出栈两次时,则进行打印
{
cout << pnewnode.pnode->data << " ";
if(pnewnode.pnode->rightchild != NULL)
{
st.push(Stknode(pnewnode.pnode->rightchild));
}
}
else
{
st.push(pnewnode);//这里必须传pnewnode 因为此时pnewnode的pop_num已经++过了 如果传的是Stknode(pnewnode.pnode)则显式转化后的stknode节点的pop_num还是0
if(pnewnode.pop_num == 1 && pnewnode.pnode->leftchild != NULL)
{
st.push(Stknode(pnewnode.pnode->leftchild));
}
}
}
cout << endl;
}
7.2:包含stk节点的非递归后序遍历:
思路:
- 申请一个栈,先将根节点入栈,然后进入while循环(栈不空)
- 当栈不为空时,获取栈顶元素,并出栈,此时不着急进行打印
- 将其出栈次数++后进行判断,当此时出栈次数为3时,则将其打印
- 不然判断其出栈次数是否为1并且其左孩子是否存在,若出栈次数为1且左孩子还存在,则将左孩子入栈
- 不然判断其出栈次数是否为2并且其右孩子是否存在,若出栈次数为2且右孩子还存在,则将右孩子入栈
- 特别注意:这里1入左孩子,2入右孩子
//非递归后序遍历二叉树 包含stk节点
void StkNicePastOrder(BtNode *ptr)
{
if(NULL == ptr) return;
stack<Stknode> st;
st.push(Stknode(ptr));
while(!st.empty())
{
Stknode pnewnode = st.top();
st.pop();
if(++pnewnode.pop_num == 3)//后序遍历规则:当出栈三次时,则进行打印
{
cout << pnewnode.pnode->data << " ";
}
else
{
st.push(pnewnode);//这里必须传pnewnode 因为此时pnewnode的pop_num已经++过了 如果传的是Stknode(pnewnode.pnode)则显式转化后的stknode节点的pop_num还是0
if(pnewnode.pop_num == 1 && pnewnode.pnode->leftchild != NULL)
{
st.push(Stknode(pnewnode.pnode->leftchild));
}
else if(pnewnode.pop_num == 2 && pnewnode.pnode->rightchild != NULL)
{
st.push(Stknode(pnewnode.pnode->rightchild));
}
}
}
cout << endl;
}
8.层次遍历
8.1非递归层次打印:
思路:
- 申请一个队列,先将根节点入队,然后进入while循环(队列不空)
- 当队列不为空时,获取栈顶元素,并出栈,并进行打印
- 再判断其左孩子是否存在,若不为空,则将其左孩子入队列
- 再判断其右孩子是否存在,若不为空,则将其右孩子入队列
- 特别注意:这里左孩子比右孩子先判断入队列
//层序遍历 非递归
void NiceLevelOrder(BtNode *ptr)
{
if(NULL == ptr) return;
queue<BtNode*> qu;
qu.push(ptr);
while(!qu.empty())
{
BtNode *pnewnode = qu.front();
qu.pop();
cout << pnewnode->data << " ";
if(pnewnode->leftchild != NULL)
{
qu.push(pnewnode->leftchild);
}
if(pnewnode->rightchild != NULL)
{
qu.push(pnewnode->rightchild);
}
}
cout << endl;
}
8.2非递归S形层次打印:
思路:
- 申请两个栈st1和st2,先将根节点入栈st1,然后进入while循环(两个栈不同时为空)
- while循环,当栈st1不为空时,进去后获取st1栈顶元素,并出栈,并进行打印
- 再先判断其左孩子是否存在,若不为空,则将其左孩子入栈st2,接着再判断其右孩子是否存在,若不为空,则将其右孩子入栈st2(栈st1规则:先判左后判右)
- while循环,当栈st2不为空时,进去后获取st2栈顶元素,并出栈,并进行打印
- 再先判断其右孩子是否存在,若不为空,则将其右孩子入栈st1,接着再判断其左孩子是否存在,若不为空,则将其左孩子入栈st1(栈st2规则:先判右后判左)
- 特别注意:这里栈st1和栈st2规则不一样
void SNiceLevelOrder(BtNode *ptr)
{
if(NULL == ptr) return;
stack<BtNode *> st1;
stack<BtNode *> st2;
st1.push(ptr);
while(!st1.empty() || !st2.empty())
{
while(!st1.empty())
{
BtNode *pnewnode = st1.top();
st1.pop();
cout << pnewnode->data << " ";
if(pnewnode->leftchild != NULL)
{
st2.push(pnewnode->leftchild);
}
if(pnewnode->rightchild != NULL)
{
st2.push(pnewnode->rightchild);
}
}
while(!st2.empty())
{
BtNode *pnewnode = st2.top();
st2.pop();
cout << pnewnode->data << " ";
if(pnewnode->rightchild != NULL)
{
st1.push(pnewnode->rightchild);
}
if(pnewnode->leftchild != NULL)
{
st1.push(pnewnode->leftchild);
}
}
}
cout << endl;
}
8.3递归打印第k层的元素
思路:
- 判断此时层数是否为k,如果为k则将其指向的数据打印
- 否则进入递归,调用此函数,将其左孩子的地址与k-1传入进去
- 否则再进入递归,调用此函数,将其右孩子孩子的地址与k-1传入进去
void Print_KLevel(BtNode *ptr,int k)
{
if(NULL == ptr) return;
if(k == 0)
{
cout << ptr->data << " ";
}
else
{
Print_KLevel(ptr->leftchild, k-1);
Print_KLevel(ptr->rightchild, k-1);
}
}
8.4递归打印二叉树的深度
思路:
- 进入递归,将其左孩子的地址传入进去
- 进入递归,将其右孩子的地址传入进去
- 再将上述两个递归的返回值进行比较,选取其中较大的值并+1
int Depth(BtNode *ptr)
{
if(NULL == ptr) return 0;
return max(Depth(ptr->leftchild), Depth(ptr->rightchild))+1;
}