Conversion example of recursion and iteration - writing non-recursive binary tree traversal

1. Recursive traversal:

      Recursion is well suited for self-similar (fractal) class data structures. Recursion of some linear data structures can be changed to tail recursion so that recursive stack frames are no longer accumulated. However, the recursion of nonlinear data structures may call itself in different ways and parameters due to the stack frame of each layer, such as the recursion of binary tree traversal. There are two self-calls in each layer of the function stack, which cannot be tail recursion. In extreme cases There is a possibility of stack explosion. Therefore, it is necessary to imitate the characteristics of the function stack to write equivalent iterative code.

        The logical structure of binary tree post-order recursion is as follows:

      

void read(treenode *node)
{
    if(node!=NULL) //①
    {
        read(node->lchild); //②
        read(node->rchild);// ③
        display(node->val);// ④
    }
}

        



Obviously, there is a significant difference from recursive flow diagrams of linear data structures such as linked lists:


Flow chart of recursive deletion process of linked list




Then, the recursive process of the nonlinear data structure of binary tree recursion is as follows:

Branches - need to use multiple if else implementations

Marking - In the recursive process, it is obvious that the function call executed in the previous layer will execute the function call of the next layer (push the stack) after the end of the return (pop), and will not be called repeatedly. However, because the loop will be re-executed from the beginning every time, in order to avoid an infinite loop of reading the leftmost node of the left subtree and its parent node, it is necessary to mark the data that has been read.

Stack - The function stack is used in the recursion to make the stack push operation. But in the loop, the stack has to be written manually.


Process reasoning:


legend:

Yellow: currently marked nodes

red: the currently popped (and displayed) node











According to the characteristics and phenomena summarized, I need to change the post-order recursion to an equivalent loop:
       1 stack

1 marker sheet

1 if else-if else structure and mark table are combined to achieve the function of 3 sentences in recursion




2. Analysis of cycle substitution
Then, the cycle analysis is roughly as follows:

first look at the intermediate process. Let's start with the operation process of the left subtree of the head node:




 


Obviously, this recursive process is non-linear, instead of pushing and popping all the way to the function stack, but alternately pushing and popping. Linear recursion can be easily replaced by simple loops (for example, recursive creation of linked lists and loop creation are both simple), but for this nonlinear recursion to loop, equivalent flow control must be performed through multiple branch statements in the loop.
According to the characteristics and phenomena summarized, to change the post-order recursion to an equivalent loop, I need:


1 stack ,


1 mark table


, 1 if else-if else structure and mark table combined to achieve



the operation process corresponding to the functions of the three sentences in the recursion that is:

The current recursive layer node is "2 nodes":

Is the left child of the node empty? no. So the left child enters the next recursion (the function stack is pushed into the stack one level).

Yes. Is the right child of the node empty? no. Therefore , the right child enters the next recursion (the function stack is pushed into the stack one level).

                                      Yes. Display the layer node to end the layer recursion (the layer exits the function stack)

The corresponding loop logic is: Is the left child of the current node empty? no. So push the "node's left node" on the stack.

Otherwise, is the right child of the node empty? no. So push the "node's left node" on the stack.

Otherwise, the node is popped and displayed. Set the top of the stack to the previous node

According to the above analysis, it is obvious that the judgment statement of the if_else if_else structure is needed, and each judgment in if should be the left node and the right node of the current node.

The incomplete pseudocode corresponding to the node is:


if (the left node of the current node is not empty)


{


}


else if (the right node of the current node is not empty)


{


}


else


{


}


the first time:


while (end condition)
    {
        if (node ​​has left child)
        {
                   Push the left child node on the stack, marked as read
        }

        else if (node ​​has right child)
        {
                   Push the right child of the stack, marked as read
        }    

        else
        {
                   No left and right child nodes are popped from the stack and the node is displayed
        }

}

 

After thinking about it, it is found that the node cannot move down step by step from the left and right subtrees of the head node. Then continue to deepen:


the second time:

temporary node = head node

while (end condition)
{
    if (temporary node has left child)
	{
		Push the left child of the temporary node on the stack, marked as read
		The temporary node is reassigned as the left node of the temporary node
	}
	else if (temporary node has right child)
	{
			Push the right child of the temporary node on the stack, marked as read
			The temporary node is reassigned as the right node of the temporary node
	}
	else
	{
			There is no left or right child node, pop the stack, display the stack node
			temporary node = node after popping again

			(Partial analysis: If the current state of the stack is 1 2 4, it will be 1 2 after popping the stack once, and the popped 2 will be assigned to the temporary node after popping again, leaving only 1 on the stack. This will cause the stack to fall back. The top element has become 1, not the 2 it should have, so it needs to be added back to 2, so that when the stack becomes 1 2, the temporary node starts to traverse from 2 again)

			re-push temporary node
	}

}


After thinking about it, it is found that the node will jump back and forth between the last node of the leftmost left subtree and the parent node. It is necessary to mark the elements that have been pushed into the stack so that it will not repeatedly traverse the two nodes in an infinite loop.




 the third time:

temporary node = head node

while (end condition)
{
    if(临时结点 有左子结点 而且 已标记表 中没有 (临时结点 的 左子结点))
	{
			入栈 临时结点 的 左子结点,标记为已读
			临时结点 重新被赋值为 临时结点的左结点
			临时结点 的 左子结点 加入已标记表
	}
	else if(临时结点 有右子结点 而且 已标记表 中没有 (临时结点 的 右子结点))
	{
			入栈 临时结点 的右子结点,标记为已读
			临时结点 重新被赋值为 临时结点的右结点
			临时结点 的 右子结点 加入已标记表
	}
	else
	{
			没有左右子结点,出栈,显示出栈结点
			临时结点 = 再次出栈之后的结点

			(局部分析:如栈当前状态为1 2 4,则出栈一次后为1 2,再出栈后出栈的2赋值给了临时结点,栈只剩下1。这样会导致回退后栈顶元素成了1,而不是该有的2,因此要补充回去2,使栈变会1 2 的同时,临时结点重新从2开始遍历)

			重新入栈 临时结点
	}

}



思考后这次应能正常后序遍历二叉树了。最后加上循环结束条件即可。

第四次:

临时结点 = 头结点

while(头结点已经遍历了两次 这件事 为 还没发生)
{
    if(临时结点 有左子结点 而且 已标记表 中没有 (临时结点 的 左子结点))
	{
					   入栈 临时结点 的 左子结点,标记为已读
					   临时结点 重新被赋值为 临时结点的左结点
					   临时结点 的 左子结点 加入已标记表
	}
	else if(临时结点 有右子结点 而且 已标记表 中没有 (临时结点 的 右子结点))
	{
					   入栈 临时结点 的右子结点,标记为已读
					   临时结点 重新被赋值为 临时结点的右结点
					   临时结点 的 右子结点 加入已标记表
	}
	else
	{
			没有左右子结点 或 左右子结点都入过栈了,出栈,显示出栈结点
					   
			临时结点 = 再次出栈之后的结点

			(局部分析:如栈当前状态为1 2 4,则出栈一次后为1 2,再出栈后出栈的2赋值给了临时结点,栈只剩下1。这样会导致回退后栈顶元素成了1,而不是该有的2,因此要补充回去2,使栈变会1 2 的同时,临时结点重新从2开始遍历)

			重新入栈 临时结点

	}
}


三、具体算法和演示如下:

#include "stdio.h"
#include "stdlib.h"

#define new_tree_node (treenode*)malloc(sizeof(treenode))


typedef struct tnode
{
    int val;
    struct tnode *leftchild,*rightchild;
}treenode;

 

treenode* create_tree()
{
    treenode *head = new_tree_node;
    head->val = 1;

    head->leftchild = new_tree_node;
    head->leftchild->val = 2;
 

    head->rightchild = new_tree_node;
    head->rightchild->val = 3;


    head->leftchild->leftchild = new_tree_node;
    head->leftchild->leftchild->val = 4;
    head->leftchild->leftchild->leftchild = new_tree_node;
    head->leftchild->leftchild->leftchild->val = 8;
    head->leftchild->leftchild->leftchild->leftchild = NULL;
    head->leftchild->leftchild->leftchild->rightchild = NULL;
    head->leftchild->leftchild->rightchild = NULL;

    head->leftchild->rightchild = new_tree_node;
    head->leftchild->rightchild->val = 5;
    head->leftchild->rightchild->leftchild = NULL;
    head->leftchild->rightchild->rightchild = NULL;

    head->rightchild->leftchild = new_tree_node;
    head->rightchild->leftchild->val = 6;
    head->rightchild->leftchild->leftchild = NULL;
    head->rightchild->leftchild->rightchild = NULL;

    head->rightchild->rightchild = new_tree_node;
    head->rightchild->rightchild->val = 7;
    head->rightchild->rightchild->leftchild = NULL;
    head->rightchild->rightchild->rightchild = NULL;

    return head;

}

 

void print_node_val(treenode *t)
{
    printf("%d\n",t->val);
}

 

void read(treenode *head)
{
    treenode *llist[100];
    treenode *fake_stack[100];
    int ReadFinished = 0;
    int i,j=0,k=0;
    int node_lchild_is_read=0,node_rchild_is_read=0;
    int head_show_times = 0;
    treenode *temp = head;
    fake_stack[j++] = temp;
    llist[k++] = temp;//遍历标记
    while(ReadFinished == 0)
    {
             for(i=0;i<100;i++)
             {
                       if(llist[i]==temp->leftchild)   node_lchild_is_read = 1;//检测结点的左结点是否在已遍历标记表中
                       if(llist[i]==temp->rightchild) node_rchild_is_read = 1;//检测结点的右结点是否在已遍历标记表中

             }
             if(temp->leftchild!=NULL && node_lchild_is_read == 0)  //如果结点有左结点而且在没在表中登记为已遍历,则入栈,对应递归语句第一条
             {
                       fake_stack[j++] = temp->leftchild;
                       llist[k++] = temp->leftchild;
                       temp = temp->leftchild;
             }
             else if(temp->rightchild!=NULL  && node_rchild_is_read == 0) //否则,如果结点有右结点而且在没在表中登记为已遍历,则入栈,对应递归语句第二条
             {
                       fake_stack[j++] = temp->rightchild;        
                       llist[k++] = temp->rightchild;
                       temp = temp->rightchild;
             }
             else                                                                                                                                  //否则,左右结点都没有,则结点显示,把自己出栈,把遍历结点从自己的上一个开始——即再出栈一次被赋值给遍历结点,

                                                                                                                                                             //对应递归的第三句——函数栈的本层结束,返回上层
             {
                       print_node_val(temp);
                       fake_stack[--j];
                       temp = fake_stack[--j];
                       fake_stack[j++] = temp;
                       if(temp==head) head_show_times++;
                       if(head_show_times==2)  ReadFinished = 1; //如果头结点在这里出现了2次,则代表头结点左右子树都完成遍历,结束循环
             }
             node_lchild_is_read=0;  //设置为刚开始的状态,准备下次遍历
             node_rchild_is_read=0;

    }
    print_node_val(temp); //最后显示头结点

/*对比起递归,等效的循环需要把递归自身的特性手动实现,例如递归第一句结束后,会自动执行第二句。但是循环如果不设置

      标志和搭配的判断句,将会引起出栈回到上一层数据点后又把已经遍历过的再遍历一次。所以要通过标记表把已经处理过的数据跳开。

      简单来说:若自己要实现等效于递归函数栈的迭代,并且每层栈有多层语句,则自己要手动实现返回上层函数栈后跳过已执行语句这种现象*/

}

 

 

int main()
{
    treenode *head =  create_tree();
    read(head);
    return 0;
}

由于标记表和栈是用有限数组写成,因此若树的规模很大可能会出问题,因此链表和链栈版本如下:



四、链表和链栈版本:


链表工具类(文件名CJZLinkList.c):

#define new_node (Linklist*)malloc(sizeof(Linklist))


typedef struct node
{
    void *content;
    struct node *prev,*next;
    struct node *head,*last;
    int size;
    void* (*get)(struct node *head,int Count);
    void  (*put)(struct node *head,void *content);
    void  (*remove)(struct node *head,int Count);
    void  (*removeall)(struct node *head);
    void  (*clearalldata)(struct node *head);
    void  (*insert)(struct node *head,int Count,void *Content);
    int   (*getSize)(struct node *head);
}Linklist;

 

void* Linklist_get(Linklist *head,int Count)
{
    int i;
    Linklist *temp = head;
    for(i=0;i<=Count;i++)
    {
             if(temp->next!=NULL) temp = temp->next;
             else return;
    }
    return temp->content;
}

 

void  Linklist_put(Linklist *head,void *content)
{
    Linklist *last = head->last;
    last->next = new_node;
    last->next->prev = last;
    last = last->next;
    last->content = content;
    last->get = Linklist_get;
    last->put = Linklist_put;
    last->next = head;
    head->last = last;
    head->prev = last;
    head->size++;
}

 

int getSize(Linklist *head)
{
    return head->size;
}

 

void Linklist_insert(Linklist *head,int Count,void *Content)
{
    int i;
    Linklist *break_node,*new_llnode,*temp = head;
    for(i=0;i<=Count-1;i++)
    {
             if(temp->next!=NULL) temp = temp->next;
             else return;
    }
    break_node = temp;
    new_llnode = new_node;
    new_llnode->content = Content;
    new_llnode->next = break_node->next;
    new_llnode->prev = break_node;
    break_node->next->prev = new_llnode;
    break_node->next = new_llnode;
    head->size++;
}

 

void Linklist_removeall(Linklist *head) //消灭整个链表,此时链表在内存中已经整个不存在,任何操作均已非法
{
    int i;
    Linklist *temp=head->next;
    for(i = head->size;i>0;i--)
    {
             //printf("delete node address %d\n",head);
             free(head);
             head = temp;
             temp = temp->next;
    }
}

 

void Linklist_clearalldata(Linklist *head)
{
    Linklist *temp=head->last,*temp_prev;
    while(
    {
             temp_prev = temp->prev;
             //printf("delete node address %d\n",temp);
             free(temp);
             temp = temp_prev;
    }
    //printf("End\n");
    //free(head->content);
    head->last = head;
    head->next = head;
    head->prev = head;
    head->head = head;
    head->size = 0;
}

 

void Linklist_remove(Linklist *head,int Count)
{
    int i;
    Linklist *remove_node,*temp = head;
    for(i=0;i<=Count-1;i++)
    {
             if(temp->next!=NULL) temp = temp->next;
             else return;
    }
    if(temp == head->last)
    {
             head->last = head->last->prev;
             head->last->next = head;
             head->prev = head->last;
    }

    if(temp == head)  //如果删除的是链表头,要把之前的链表头的下一个结点变成链表头,要初始化一下这个结点
    {
             remove_node = head;
             head = head->next;
             head->prev = remove_node->prev;
             head->get = Linklist_get;
             head->put = Linklist_put;
             head->getSize = getSize;
             head->insert = Linklist_insert;
             head->remove = Linklist_remove;
             head->removeall = Linklist_removeall;
             head->clearalldata = Linklist_clearalldata;
             head->head = head;
             head->last = remove_node->last;
             head->size = remove_node->size;
    }
    remove_node = temp;
    temp->prev->next = temp->next;
    temp->next->prev = temp->prev;
    free(remove_node);
    head->size--;
}

 

 

Linklist* createLinklist()
{
    Linklist *node = new_node;
    node->get = Linklist_get;
    node->put = Linklist_put;
    node->getSize = getSize;
    node->insert = Linklist_insert;
    node->remove = Linklist_remove;
    node->removeall = Linklist_removeall;
    node->clearalldata = Linklist_clearalldata;
    node->head = node;
    node->last = node;
    node->prev = node->next = NULL;
    node->size = 0;
    return node;

}


栈工具类(文件名CJZStack.c):


#define new_stack_node (Stack*)malloc(sizeof(Stack))


typedef struct stack_node
{
    void *val;
    struct stack_node *next,*prev,*top;
    void (*push)(struct stack_node *start_node,int val);
    void* (*pop)(struct stack_node *start_node,void (*fun)());
    int  (*IsEmpty)(struct stack_node *head);
}Stack;

int Stack_isempty(Stack *head)
{
    if(head->next==NULL) return 1;
    else    return 0;
}

void* Stack_pop(Stack *start_node,void (*fun)())
{
    Stack *temp = start_node->top;
    void *content;
    if(fun!=NULL) fun(temp->prev->val);
    if(temp->prev!=NULL && temp->prev->val!=NULL) content = temp->prev->val;
    start_node->top = temp->prev;
    free(temp->next);
    free(temp);
    start_node->top->next = NULL;
    start_node->top->IsEmpty = Stack_isempty;
    return content;
}
 

void Stack_push(Stack *start_node,void *val)
{
    Stack *temp = start_node->top;
    temp->val = val;
    temp->push = Stack_push;
    temp->pop = Stack_pop;
    temp->IsEmpty = Stack_isempty;
    temp->next = new_stack_node;
    temp->next->prev = temp;
    temp->next->next = NULL;
    start_node->top = temp->next;
}

Stack* createStack()
{
    Stack *stack = new_stack_node;
    stack->val = NULL;
    stack->next = NULL;
    stack->top = stack;
    stack->push = Stack_push;
    stack->pop = Stack_pop;
    stack->IsEmpty = Stack_isempty;
    return stack;
}


主程序:


#include "stdio.h"
#include "stdlib.h"
#include "CJZLinkList.c"
#include "CJZStack.c"
#define new_tree_node (treenode*)malloc(sizeof(treenode))
 
typedef struct tnode
{
    int val;
    struct tnode *leftchild,*rightchild;
}treenode;

 

treenode* create_tree()
{
    treenode *head = new_tree_node;
    head->val = 1;

    head->leftchild = new_tree_node;
    head->leftchild->val = 2;

    head->rightchild = new_tree_node;
    head->rightchild->val = 3;
 
    head->leftchild->leftchild = new_tree_node;
    head->leftchild->leftchild->val = 4;
    head->leftchild->leftchild->leftchild = new_tree_node;
    head->leftchild->leftchild->leftchild->val = 8;
    head->leftchild->leftchild->leftchild->leftchild = NULL;
    head->leftchild->leftchild->leftchild->rightchild = NULL;
    head->leftchild->leftchild->rightchild = NULL;
 
    head->leftchild->rightchild = new_tree_node;
    head->leftchild->rightchild->val = 5;
    head->leftchild->rightchild->leftchild = NULL;
    head->leftchild->rightchild->rightchild = NULL;

    head->rightchild->leftchild = new_tree_node;
    head->rightchild->leftchild->val = 6;
    head->rightchild->leftchild->leftchild = NULL;
    head->rightchild->leftchild->rightchild = NULL;
 
    head->rightchild->rightchild = new_tree_node;
    head->rightchild->rightchild->val = 7;
    head->rightchild->rightchild->leftchild = NULL;
    head->rightchild->rightchild->rightchild = NULL;

    return head;
}
 

void print_node_val(treenode *t)
{
    printf("%d\n",t->val);
}

 

void read(treenode *head,Linklist *llist,Stack *stack)
{
    int ReadFinished = 0;
    int i/*,j=0*/;
    int node_lchild_is_read=0,node_rchild_is_read=0;
    int head_show_times = 0;
    //treenode *fake_stack[100];
    treenode *temp = head;
    stack->push(stack,temp);
    //fake_stack[j++] = temp;
    llist->put(llist,temp);//遍历标记
    while(ReadFinished == 0)
    {
             for(i=0;i<=llist->getSize(llist);i++)
             {
                       if(llist->get(llist,i)==temp->leftchild)         node_lchild_is_read = 1;//检测结点的左结点是否在已遍历标记表中

                       if(llist->get(llist,i)==temp->rightchild)        node_rchild_is_read = 1;//检测结点的右结点是否在已遍历标记表中
             }
             if(temp->leftchild!=NULL && node_lchild_is_read == 0)  //如果结点有左结点而且在没在表中登记为已遍历,则入栈,对应递归语句第一条
             {
                       stack->push(stack,temp->leftchild);
                       //fake_stack[j++] = temp->leftchild;
                       llist->put(llist,temp->leftchild);
                       temp = temp->leftchild;
             }
             else if(temp->rightchild!=NULL  && node_rchild_is_read == 0) //否则,如果结点有右结点而且在没在表中登记为已遍历,则入栈,对应递归语句第二条
             {
                       stack->push(stack,temp->rightchild);
                       //fake_stack[j++] = temp->rightchild;
                       llist->put(llist,temp->rightchild);                        
                       temp = temp->rightchild;
             }
             else                                                                                                                                  //否则,左右结点都没有,则结点显示,把自己出栈,把遍历结点从自己的上一个开始——即再出栈一次被赋值给遍历结点,

                                                                                                                                                             //对应递归的第三句——函数栈的本层结束,返回上层
             {
                       print_node_val(temp);
                       stack->pop(stack,NULL);
                       //fake_stack[--j];
                       temp = stack->pop(stack,NULL);
                       //temp = fake_stack[--j];
                       stack->push(stack,temp);
                       //fake_stack[j++]; //没有这句就会出事,用循环写自相似结构的东西,调试起来挺麻烦的
                       //printf("node is %d\n",temp->val);
                       if(temp==head) head_show_times++;
                       //printf("head_show_times %d\n",head_show_times);
                       if(head_show_times==2)  ReadFinished = 1; //如果头结点在这里出现了2次,则代表头结点左右子树都完成遍历,结束循环
             }
             node_lchild_is_read=0;  //设置为刚开始的状态,准备下次遍历
             node_rchild_is_read=0;
    }
    print_node_val(temp); //最后显示头结点

/*对比起递归,等效的循环需要把递归自身的特性手动实现,例如递归第一句结束后,会自动执行第二句。但是循环如果不设置

      标志和搭配的判断句,将会引起出栈回到上一层数据点后又把已经遍历过的再遍历一次。所以要通过标记表把已经处理过的数据跳开。

      简单来说:若自己要实现等效于递归函数栈的迭代,并且每层栈有多层语句,则自己要手动实现返回上层函数栈后跳过已执行语句这种现象*/

}

 

int main()
{
    treenode *head =  create_tree();
    Linklist *llist = createLinklist(); //已遍历过的表
    Stack *stack = createStack(); //模拟函数栈,实际只放数据
    read(head,llist,stack);
    return 0;
}



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325519115&siteId=291194637