二叉树遍历的基本概念
1、二叉树遍历原理:二叉树的遍历是指从根结点出发,按照某种次序依次访问所有结点,使得每个结点被访问一次且仅被访问一次。
2、二叉树遍历方法:
(1)前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
(2)中序遍历:若树为空,则空操作返回,否则从根结点开始,中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
(3)后序遍历:若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。
(4)层序遍历:若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
二叉树遍历举例:
二叉树的前序遍历序列: ABCDEFGH
二叉树的中序遍历序列:CBEDFAGH
二叉树的后续遍历序列:CEFDBHGA
二叉树的层序遍历举例:ABGCDHEF
二叉树遍历算法
1、前序遍历算法
(1)递归算法
void PreOrder(BtNode *ptr)
{
if (NULL != ptr)
{
printf("%c ",ptr->data);
PreOrder(ptr->leftChild);
PreOrder(ptr->rightChild);
}
}
(2非递归算法
思路1:
对于任一结点pCur:
1)访问结点pCur,并将结点pCur入栈,结点pCur一直左走 ;
2)判断结点pCur的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点pCur,循环至 1);若不为空,则将P的左孩子置为当前的结点pCur;
3)直到pCur为NULL并且栈为空,则遍历结束。
void NicePreOrder(BtNode *ptr)
{
if (ptr == NULL)
{
return;
}
stack<BtNode *> stackTreeNodes;
BtNode *pCur = ptr;//指向当前要访问的结点
while(pCur != NULL || !stackTreeNodes.empty()) //判断pCur是否为NULL是为处理右单子树出现的情况
{
while(pCur != NULL)//一直往左走
{
printf("%c ",pCur->data);
stackTreeNodes.push(pCur);
pCur = pCur->leftChild;
}
if(!stackTreeNodes.empty())//在出栈和访问栈顶元素之前要先检查是否为空
{
pCur = stackTreeNodes.top();//pCur刚跳出上面的while循环时值为NULL 故要将其指向当前的栈顶结点
stackTreeNodes.pop();
pCur = pCur->rightChild;
}
}
}
思路2:
1)先判断树是否为空,若为空则返回空操作,否则将根结点入栈;
2)然后判断栈为不为空,不为空则访问栈顶元素并弹出栈顶元素;
3)判断右子树是否为空,不为空则将右子树根结点压入栈,再判断左子树是否为空,不为空则将左子树根结点压入栈,继续进行2)操作,直到栈为空为止。
void NicePreOrder(BtNode *ptr)
{
if(NULL == ptr)
{
return ;
}
Stack st;
Init_Stack(&st);
push(&st,ptr);
while(!empty(&st))
{
ptr = top(&st);
pop(&st);
printf("%c ",ptr->data);
if(ptr->rightchild != NULL)
push(&st,ptr->rightchild);
if(ptr->leftchild != NULL)
push(&st,ptr->leftchild);
}
}
2、中序遍历算法
(1)递归算法
void InOrder(BtNode *ptr)
{
if (ptr != NULL)
{
InOrder(ptr->leftChild);
printf("%c ",ptr->data);
InOrder(ptr->rightChild);
}
}
(2)非递归算法
思路:
对于任一结点pCur:
1)将结点pCur入栈,结点pCur一直左走
2)判断结点pCur的左孩子是否为空,若为空,访问结点pCur,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点pCur,循环至 1);若不为空,则将P的左孩子置为当前的结点pCur;
3)直到pCur为NULL并且栈为空,则遍历结束。
void NiceInOrder(BtNode *ptr)
{
if (ptr == NULL)
{
return;
}
stack<BtNode *> stackTreeNodes;
BtNode *pCur = ptr;//指向当前子树的根结点
while(pCur != NULL || !stackTreeNodes.empty())
{
while(pCur != NULL)//一直往左走
{
stackTreeNodes.push(pCur);
pCur = pCur->leftChild;
}
if(!stackTreeNodes.empty())//在出栈和访问栈顶元素之前要先检查是否为空
{
pCur = stackTreeNodes.top();//pCur刚跳出上面的while循环时值为NULL 故要将其pucr当前的栈顶结点
printf("%c ",pCur->data);
stackTreeNodes.pop();
pCur = pCur->rightChild;
}
}
}
3、后序遍历算法
(1)递归算法
void PastOrder(BtNode *ptr)
{
if (ptr != NULL)
{
PostOrder(ptr->leftChild);
PostOrder(ptr->rightChild);
printf("%c ",ptr->data);
}
}
(2)非递归算法
思路1:
1)设立两个指针pCur (初始指向根结点),和preVisted(指向被访问结点),对于任一结点pCur,将其入栈;
2)然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,此时要判断此结点是否满足输出条件,即:当前结点的右孩子如果为空或者已经被访问,则访问当前结点 ,否则将pCur置为其右结点,直到pCur为NULL并且栈为空,则遍历结束。
void NicePastOrder(BtNode *ptr)
{
if(ptr == NULL)
{
return;
}
stack<BtNode *> stackTreeNodes;
BtNode *pCur = ptr;//指向当前子树的根结点
BtNode *preVisted = NULL;//指向前一被访问的结点
while(pCur != NULL || !stackTreeNodes.empty())
{
while(pCur != NULL)//一直向左走
{
stackTreeNodes.push(pCur);
pCur = pCur->leftChild;
}
if(!stackTreeNodes.empty())//在出栈和访问栈顶元素之前要先检查是否为空
{
//pCur刚跳出上面的while循环时值为NULL,故要将pCur指向当前的栈顶结点
pCur = stackTreeNodes.top();
if(pCur->rightChild == NULL || pCur->rightChild==preVisted)//当前节点的右孩子如果为空或者已经被访问,则访问当前节点
{
printf("%c ",pCur->data);
stackTreeNodes.pop();
preVisted = pCur;//设置当前结点已被访问过
pCur = NULL;//防止当前结点又被压入栈中
}
else//如果当前结点的右子结点存在且没被访问过
{
pCur = pCur->rightChild;
}
}
}
}
思路2:本质上和思路1一样。
1)设立标记结点指针,以此来判断右子树是否已被访问过,先将根结点入栈;
2)然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,将该结点出栈,并判断该结点的右孩子是否为空或者已经被访问(即等于标记结点指针),若是则访问该结点 ,并将该结点指针赋值给标记结点,该结点指针置为空,否则将该结点重新压入栈顶,直到当前结点指针和栈都为空,则遍历结束。
void NicePastOrder(BtNode *ptr)
{
if(ptr == NULL) return ;
Stack st;
Init_Stack(&st);
BtNode *tag = NULL;
while(ptr != NULL || !empty(&st))
{
while(ptr != NULL)
{
push(&st,ptr);
ptr = ptr->leftchild;
}
ptr = top(&st);
pop(&st);
if(ptr->rightchild == NULL || ptr->rightchild == tag)
{
printf("%c ",ptr->data);
tag = ptr;
ptr = NULL;
}
else
{
push(&st,ptr);
ptr = ptr->rightchild;
}
}
}
4、层序遍历算法
方法1:有点类似非递归的前序遍历,只不过换成了队列。
思路:
1)先判断树是否为空,不为空则将根结点入队列;
2)然后判断队列是否为空,不为空则出队头元素,并访问此结点;
3)先判断当前结点左子树是否为空,不为空则将左孩子结点入队列,再判断当前结点的右子树是否为空,不为空则将右孩子结点入队列,继续进行2)操作,直到队列为空为止。
void LevelOrder(BtNode *ptr)
{
assert(ptr != NULL);
queue<BtNode *> queueTreeNodes;
queueTreeNodes.push(ptr);
while(!queueTreeNodes.empty())
{
BtNode *pNode=queueTreeNodes.front();
queueTreeNodes.pop();
printf("%c ",pNode->data);
if(pNode->leftChild!=NULL)
{
queueTreeNodes.push(pNode->leftChild);
}
if(pNode->rightChild!=NULL)
{
queueTreeNodes.push(pNode->rightChild);
}
}
}
方法2:
思路:使用vector保存每个结点的指针,存入vector中的元素顺序是按照层数递增并且每层按照从左到右。由于vector的底层是数组,所以可以通过下标依次访问每个结点的数据。
void LevelOrder(BTNode *ptr)
{
if (ptr == NULL)
{
return;
}
vector<BTNode*> vec;
BTNode *p = ptr;
vec.push_back(p);
//存放当前结点在vector中的下标
int cur = 0;
while (cur < vec.size())
{
printf("%c", vec[cur]->data);
if (vec[cur]->leftChild != NULL)
{
vec.push_back(vec[cur]->leftChild);
}
if (vec[cur]->rightChild != NULL)
{
vec.push_back(vec[cur]->rightChild);
}
++cur;
}
}