zoukankan      html  css  js  c++  java
  • 递归转循环的通法

    前段时间看书发现,但凡提到递归的地方,都会说一句,递归和循环是可以相互转化的。刚开始,也没有想到将所有递归转为循环的办法。像计算阶乘,那自然没什么好说的。但是有些问题,用递归真的很方便,可以不用管具体的实现顺序,只要分析清楚终止条件和一次处理的逻辑就行了。那如果要把递归转为循环,忍不住就要想几个问题:

    1、递归的原理是什么,若转为循环,那么原理依旧相同吗?还是说另辟蹊径?

    2、在循环的每一次迭代中,怎么保证下次迭代的顺序和正确递归的顺序保持一致?

    3、循环的条件和递归的终结条件的异同。

    第一个问题,也看了一些书籍和博客,并没有发现别的思路,递归转循环无非就是显式地使用栈,思想是一样的,对需要保存的信息压栈,当前处理完毕之后出栈。其好处在于,与尾递归相似,不用多次创建栈,效率会有提升。

    第二个问题,这个要根据情况处理,如果循环内可以有个完美的判断链,自然可以使得顺序与递归同步。如果情况复杂一点,需要自己设置额外变量,帮助判断下次循环的运行方向。

    第三个问题,循环的条件和递归的终结条件基本相同,对于循环使用的栈的判断可以放到循环的逻辑里,不用加到判断条件中。

    鄙人不才,用python实现了两个递归转循环的实例。介绍两个实例之前,给出自己的栈、节点、完全二叉树实现代码:

    NONE_POINTER = -1
    # 有二叉链表和三叉链表,区别在于是否包含parent节点的信息
    class Node():
        # def __init__(self, data, parent=-1, left_child=-1, right_child=-1):
        def __init__(self, data):
            self.data = data
            self.left_child = NONE_POINTER
            self.right_child = NONE_POINTER
            self.l_tag = LINK
            self.r_tag = LINK
            # self.parent = NONE_POINTER
    
    class Binary_Tree():
        logger_instance = my_logger('binary_tree')
        logger = logger_instance.initial_logger()
    
        def __init__(self, data_list):
            self.data_list = data_list
            self.check_data_list()
            self.root = Node(data_list.pop(0))
            self.level = 1
            self.level_nodes = [self.root]
            self.pre_node = NONE_POINTER
            self.create_bi_tree()
    
        def check_data_list(self):
            if len(self.data_list) <= 1:
                raise Exception('Error hmm_segger list!')
    
        # 使用层级遍历的方式构造完全二叉树
        def create_bi_tree(self):
            temp_nodes = []
            self.level += 1
    
            for node in self.level_nodes:
                if len(self.data_list) > 0:
                    node.left_child = Node(self.data_list.pop(0))
                else:
                    break
                if len(self.data_list) > 0:
                    node.right_child = Node(self.data_list.pop(0))
                else:
                    break
                temp_nodes.append(node.left_child)
                temp_nodes.append(node.right_child)
    
            if len(self.data_list) > 0:
                self.level_nodes = temp_nodes
                self.create_bi_tree()

    准备代码完成之后,展示python的中序遍历的循环代码。

    插,为什么要设置额外变量,因为中序遍历对应的循环内容有两种可能,一个是将自己压栈向左子树前进遍历,一个是遍历自己后向右子树前进遍历。正好对应一次递归中的两次子递归的过程,所以需要额外变量帮助判断转向。

    见下:

    def in_order_traverse(binary_tree):
        tree_stack = my_stack()
        cur_node = binary_tree  # 记录当前处理节点
        state = 0               # (靠额外变量判断循环的迭代方向)记录节点遍历的方向,0表示向左遍历,1表示输出自己后向右遍历
    
        # 对左子树进行遍历
        while cur_node != NONE_POINTER:
            if state == 0:
                if cur_node.left_child != NONE_POINTER:     # 不为空,继续向左遍历
                    tree_stack.push(cur_node)
                    cur_node = cur_node.left_child
                else:
                    print '',
                    print cur_node.data,                     # 为空输出自己,出栈并将遍历方向改为输出自己向右遍历
                    cur_node = tree_stack.pop() if not tree_stack.is_empty() else NONE_POINTER
                    state = 1
            else:
                print '',
                print cur_node.data,
                if cur_node.right_child != NONE_POINTER:    # 右不为空,则右子树代替当前节点,从右子树根节点向左遍历
                    cur_node = cur_node.right_child
                    state = 0
                else:
                    cur_node = tree_stack.pop() if not tree_stack.is_empty() else NONE_POINTER

    快速排序的循环版本代码如下:

    def quick_sort(nums):
        start, end = 0, len(nums)-1
        nums_stack = my_stack()
    
        while start < end:
            flag = nums[start]
            cur_index = start
            # 快排逻辑
            for i in xrange(start+1, end+1):
                if nums[i] < flag:
                    nums[cur_index] = nums[i]
                    nums[i] = nums[cur_index+1]
                    cur_index += 1
            nums[cur_index] = flag
    
            # 安排下一次迭代起止位
            if cur_index - start > 1:
                if end - cur_index > 1:     # 压栈
                    nums_stack.push([cur_index+1, end])
                end = cur_index - 1
    
            elif end - cur_index > 1:
                start = cur_index + 1
    
            elif not nums_stack.is_empty():
                [start, end] = nums_stack.pop()     # 出栈
            else:
                start = end = 0

    代码有点糙,格式不太规范,请轻拍。

    后续分界线


    时隔两个月,在思考DFS算法时,我又想到了用栈显式的去实现。想了好一会,写出了一段伪代码,惭愧。。。。 先给出相关数据结构的定义,此处采用邻接链表的形式存储图。

    // 图节点
    class Node<T>{
        T data;
        neighborNode neighbor;
    }
    
    // 邻接链表节点
    class neighborNode{
        int index;
        neighborNode next;
    }

    在写之前,我问了自己几个问题,并如下作答:

    • 什么时候压栈?

    向更深层次遍历的时候,将下个邻居压栈。

    • 什么时候出栈?

    无更深层次节点可遍历 或者说 所有邻居都遍历过 的时候。

    • 下个遍历节点的优先级?

    1>未访问过的邻居   2>未访问过的栈顶节点

    • 什么时候结束?

    需要出栈但栈为空的时候。(也是让我再写一版的原因)

    然后我就写了如下伪代码:

     1 DFS(w)    //邻接链表,节点保存在数组中,每个节点都有个指向其邻居链表的指针
     2     visit w and mark it as visited;
     3     if w.next is not null then
     4         Stack.push(w.next);
     5     end if
     6     
     7     while(Stack is not empty) do    
     8         pointer ← Stack.pop();    //一次回退
     9         while(arr[pointer.index] is visited and pointer.next is not null) do
    10             pointer ← pointer.next;
    11         end while
    12         
    13         //深入遍历
    14         while(arr[pointer.index] is not visited) do
    15             visit arr[pointer.index] and mark it as visited;
    16             
    17             for each neighbor p of arr[pointer.index] do
    18                 if arr[p.index] is not visited then
    19                     pointer ← p;
    20                     Stack.push(pointer.next); //向下一层遍历之前,将下个位置压栈
    21                     break;
    22                 end if
    23             end for
    24         end while

    说实话,写了好几次,最后才定义清楚 终止条件和一次循环的工作内容。 刚开始觉得非常晦涩,难懂也难记。在鄙人看来,真正好的算法应该是思路清晰且简单的,看了之后可以举一反三,所以对于上面的代码我是拒绝的。就此代码来说,如果定义栈空为终止条件,那么势必要额外处理边界情况。而且一次出栈为一个循环的话,一次循环的工作内容又过多,写出来又要花很多时间思考。

    后来,我又来翻看这篇博客,看到二叉树中序遍历的循环写法,一直萦绕心头的循环条件终于有了新的出路。栈不一定要为空,每次循环只做一件事,即便利后确定下个遍历节点。对,无节点可遍历就是循环终止条件。所以,我又写了一个版本,代码本身是可以更短的,这里为了方便理解,就不缩减了。

     1 DFS(w)    //邻接链表,节点保存在数组中,每个节点都有个指向其邻居链表的指针
     2     visit w and mark it as visited;
     3     if w.next is not null then
     4         pointer ← w.next;
     5     end if
     6     
     7     while(arr[pointer.index] is not visited) do    //一次只遍历一个,在无可遍历节点时退出
     8         visit arr[pointer.index] and mark it as visited;
     9         
    10         for each neighbor p of arr[pointer.index] do //优先选取未遍历的邻居
    11             if arr[p.index] is not visited then
    12                 pointer ← p;
    13                 Stack.push(pointer.next); //向下一层遍历之前,将下个位置压栈
    14                 break;
    15             end if
    16         end for
    17         
    18         while(arr[pointer.index] is visited and Stack is not empty) //邻居都遍历过,退栈
    19             pointer ← Stack.pop();
    20             //若节点已遍历,再优先选取其未遍历的邻居
    21             while(arr[pointer.index] is visited and pointer.next is not null) do
    22                 pointer ← pointer.next;
    23             end while
    24         end while

    以上


  • 相关阅读:
    python限定方法参数类型、返回值类型、变量类型等
    双拼自然码
    关于将汉语拼音字母“ü”改成“v”的设想和建议
    数据库转模型图
    python中的捕获异常、异常跟踪
    内部教师爆料:某些民办学校真正的内幕
    炸薯条
    IntelliJ IDEA添加JavaDOC注释 方法 快捷键
    java获取当前路径的方法
    java获取全部子类或接口的全部实现
  • 原文地址:https://www.cnblogs.com/ustcwx/p/7756783.html
Copyright © 2011-2022 走看看