二叉树实例:
二叉树结点定义:
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