基于栈的辅助,详解二叉树的三种非递归遍历方式

二叉树实例:

二叉树结点定义:
typedef struct TreeNode {
    
    
 int val;
 struct TreeNode *left;
 struct TreeNode *right;
}Node;

在这里插入图片描述

定义一个栈及其出入栈的操作:

typedef struct st_stack
{
    
    
    Node *array[10000];//节点指针数组,用于存放二叉树的节点指针
    int top;
    int tail;
    int size;
}STACK;

//栈初始化
void stack_init(STACK *pstack)
{
    
    
	if (pstack == NULL)
    {
    
    
        return;
    }
    memset(pstack,0,sizeof(STACK));
}

//元素入栈
void in_stack(STACK *pstack,Node *pnode)
{
    
    
    pstack->array[pstack->top] = pnode;
    pstack->top++;
    pstack->size++;
}

//判断栈是否为空
bool stack_empty(STACK *pstack)
{
    
    
    if (pstack->size == 0)
    {
    
    
        return true;
    }

    return false;
}

//出栈--栈顶元素出栈
Node* out_stack(STACK *pstack)
{
    
    
    Node* ptmp = pstack->array[pstack->top-1];
    pstack->top--;
    pstack->size--;
    return ptmp;
}

Node* top_stack(STACK *pstack)//访问栈顶元素
{
    
    
    Node* ptmp = pstack->array[pstack->top-1];
    return ptmp;
}

1.先序遍历DLR

对于先序遍历,先访问头节点,再访问其左子节点,右子节点。所以借助栈,将root先入栈,在while过程中进行二叉树的遍历,while循环的条件是栈非空。

int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
    
    
    int *res = NULL;
    STACK g_stack;
    stack_init(&g_stack);
    Node *ptmp = NULL;
    in_stack(&g_stack,root);

    while (false == stack_empty(&g_stack))
	{
    
    
        ptmp = out_stack(&g_stack);
        if (ptmp != NULL)
        {
    
    
            res = realloc(res,sizeof(int)*(*returnSize+1));
            res[*returnSize] = ptmp->val;
            (*returnSize) += 1;
            
            if (ptmp->right)
            {
    
    
                in_stack(&g_stack,ptmp->right);
            }
            
            if (ptmp->left)
            {
    
    
                in_stack(&g_stack,ptmp->left);
            }
        }
    }
}
为什么在DLR中,while循环条件是栈非空?

按照上面的代码的实现,先将root入栈,所以栈中元素此时非空,按照遍历顺序,当指向root的指针指向子节点,无论是左还是右子节点,以先序遍历的设定,该节点都成为了D点,需要再次入栈。那么一定可以在栈非空的时候知道有结点尚未被遍历,因此,这个实现,while循环中的条件就是栈非空。

为什么是先ptmp->right判断是否入栈,再判断是否ptmp->left是否入栈?

因为先序遍历是D–>L–>R,但是栈的属性是LIFO(后入先出),所以为了能先访问到L,所以必须将L后入栈。

2.中序遍历LDR

对于中序遍历,先将root入栈,再将其所有左子节点入栈,最后取栈顶元素,进行访问,若其右子节点非空,又按照上述方式将其右子节点的左子节点入栈。

int* inorderTraversal(struct TreeNode* root, int* returnSize)
{
    
    
    int *res = NULL;
    Node *ptmp = NULL;
    STACK g_stack;
    stack_init(&g_stack);

    *returnSize = 0;

    if (root == NULL)
    {
    
    
        return res;
    }
    ptmp = root;
    while (ptmp!=NULL || false ==stack_empty(&g_stack))
    {
    
    
        while (ptmp)
        {
    
    
            in_stack(ptmp);
            ptmp = ptmp->left;
        }
        
        ptmp = out_stack(&g_stack);//取栈顶元素
        
        if (ptmp)
        {
    
    
            res = realloc(res,sizeof(int)*(*returnSize+1));
            res[*returnSize] = ptmp->val;
            (*returnSize)+=1;
            ptmp = ptm->right;//ptmp指向其右子节点
        }
    }
    
    return res;
}
在LDR中,栈为空的情形

对于以上二叉树,其结点出入栈过程如下图所示:
在这里插入图片描述

由此可见,在遍历过程中,栈为空的情形是会出现的,但此时仍有节点未遍历完,所以,while循环的判断条件为ptmp!=NULL || 栈非空,即,只要两者有其一,就说明尚未遍历完。

3.后序遍历LRD

后序遍历,即L–>R–>D。

L很简单,判断root节点的左子节点不为空就可入栈,一直到最左下角。然后取栈顶元素,此时判断栈顶元素的右子节点是否非空,非空的话要入栈,重复上述入栈过程直到叶子节点。此时栈顶元素是一个叶子节点,对它进行访问,然后将其退栈。由于入栈顺序,可知此节点的左子结点,一定入过栈且退栈了,所以不需要再将其左子节点入栈,但是其右子节点呢?访问顺序是LRD,并不知道当前的栈顶元素的上一个栈顶元素是否已经访问了,如果它已经访问了,又把它入栈,那么就会造成重复访问导致失败。所以,应该有一个辅助节点指针,指向刚访问过的元素,这样进行下一次访问的时候,就不会造成上述情形!

int* postorderTraversal(struct TreeNode* root, int* returnSize){
    
    
    int *res = NULL;
    Node *ptmp = NULL;
    Node *pcur = NULL;
    STACK g_stack;
    stack_init(&g_stack);

    *returnSize = 0;

    if (root == NULL)
    {
    
    
        return res;
    }

    ptmp = root;
    while (ptmp != NULL || false == stack_empty(&g_stack))
    {
    
    
        while (ptmp)
        {
    
    
            in_stack(&g_stack,ptmp);
            ptmp = ptmp->left;
        }

        ptmp = top_stack(&g_stack);
        if (ptmp->right && pcur != ptmp->right)//ptmp的右子节点非空且没被访问过,则对ptmp的右分支进行入栈
        {
    
    //这个步骤是由于LRD,那么对于一个结点,它的右子结点的访问在它之前,那么就要将它的右子结点压在它的上面
            ptmp = ptmp->right;
        }
		else// ptmp->right == NULL或者已经被访问过了,那么就可以访问ptmp了
		{
    
    
			res = realloc(res,sizeof(int)*(*returnSize+1));
			res[*returnSize] = ptmp->val;
            (*returnSize)++;
			pcur = ptmp;
			ptmp = NULL;
		}
    }

    return res;
}

参考文档:
https://www.cnblogs.com/grandyang/p/4297300.html
https://www.cnblogs.com/grandyang/p/4146981.html
https://www.cnblogs.com/grandyang/p/4251757.html

猜你喜欢

转载自blog.csdn.net/dengwodaer/article/details/114503081