Recursion or Iteration?

前端 未结 30 2148
小鲜肉
小鲜肉 2020-11-22 14:44

Is there a performance hit if we use a loop instead of recursion or vice versa in algorithms where both can serve the same purpose? Eg: Check if the given string is a palind

相关标签:
30条回答
  • 2020-11-22 15:31

    Recursion? Where do I start, wiki will tell you “it’s the process of repeating items in a self-similar way"

    Back in day when I was doing C, C++ recursion was a god send, stuff like "Tail recursion". You'll also find many sorting algorithms use recursion. Quick sort example: http://alienryderflex.com/quicksort/

    Recursion is like any other algorithm useful for a specific problem. Perhaps you mightn't find a use straight away or often but there will be problem you’ll be glad it’s available.

    0 讨论(0)
  • 2020-11-22 15:31

    Using just Chrome 45.0.2454.85 m, recursion seems to be a nice amount faster.

    Here is the code:

    (function recursionVsForLoop(global) {
        "use strict";
    
        // Perf test
        function perfTest() {}
    
        perfTest.prototype.do = function(ns, fn) {
            console.time(ns);
            fn();
            console.timeEnd(ns);
        };
    
        // Recursion method
        (function recur() {
            var count = 0;
            global.recurFn = function recurFn(fn, cycles) {
                fn();
                count = count + 1;
                if (count !== cycles) recurFn(fn, cycles);
            };
        })();
    
        // Looped method
        function loopFn(fn, cycles) {
            for (var i = 0; i < cycles; i++) {
                fn();
            }
        }
    
        // Tests
        var curTest = new perfTest(),
            testsToRun = 100;
    
        curTest.do('recursion', function() {
            recurFn(function() {
                console.log('a recur run.');
            }, testsToRun);
        });
    
        curTest.do('loop', function() {
            loopFn(function() {
                console.log('a loop run.');
            }, testsToRun);
        });
    
    })(window);
    

    RESULTS

    // 100 runs using standard for loop

    100x for loop run. Time to complete: 7.683ms

    // 100 runs using functional recursive approach w/ tail recursion

    100x recursion run. Time to complete: 4.841ms

    In the screenshot below, recursion wins again by a bigger margin when run at 300 cycles per test

    0 讨论(0)
  • 2020-11-22 15:32

    Loops may achieve a performance gain for your program. Recursion may achieve a performance gain for your programmer. Choose which is more important in your situation!

    0 讨论(0)
  • 2020-11-22 15:33

    Recursion is more simple (and thus - more fundamental) than any possible definition of an iteration. You can define a Turing-complete system with only a pair of combinators (yes, even a recursion itself is a derivative notion in such a system). Lambda calculus is an equally powerful fundamental system, featuring recursive functions. But if you want to define an iteration properly, you'd need much more primitives to start with.

    As for the code - no, recursive code is in fact much easier to understand and to maintain than a purely iterative one, since most data structures are recursive. Of course, in order to get it right one would need a language with a support for high order functions and closures, at least - to get all the standard combinators and iterators in a neat way. In C++, of course, complicated recursive solutions can look a bit ugly, unless you're a hardcore user of FC++ and alike.

    0 讨论(0)
  • 2020-11-22 15:34

    it depends on "recursion depth". it depends on how much the function call overhead will influence the total execution time.

    For example, calculating the classical factorial in a recursive way is very inefficient due to: - risk of data overflowing - risk of stack overflowing - function call overhead occupy 80% of execution time

    while developing a min-max algorithm for position analysis in the game of chess that will analyze subsequent N moves can be implemented in recursion over the "analysis depth" (as I'm doing ^_^)

    0 讨论(0)
  • 2020-11-22 15:34

    I found another differences between those approaches. It looks simple and unimportant, but it has a very important role while you prepare for interviews and this subject arises, so look closely.

    In short: 1) iterative post-order traversal is not easy - that makes DFT more complex 2) cycles check easier with recursion

    Details:

    In the recursive case, it is easy to create pre and post traversals:

    Imagine a pretty standard question: "print all tasks that should be executed to execute the task 5, when tasks depend on other tasks"

    Example:

        //key-task, value-list of tasks the key task depends on
        //"adjacency map":
        Map<Integer, List<Integer>> tasksMap = new HashMap<>();
        tasksMap.put(0, new ArrayList<>());
        tasksMap.put(1, new ArrayList<>());
    
        List<Integer> t2 = new ArrayList<>();
        t2.add(0);
        t2.add(1);
        tasksMap.put(2, t2);
    
        List<Integer> t3 = new ArrayList<>();
        t3.add(2);
        t3.add(10);
        tasksMap.put(3, t3);
    
        List<Integer> t4 = new ArrayList<>();
        t4.add(3);
        tasksMap.put(4, t4);
    
        List<Integer> t5 = new ArrayList<>();
        t5.add(3);
        tasksMap.put(5, t5);
    
        tasksMap.put(6, new ArrayList<>());
        tasksMap.put(7, new ArrayList<>());
    
        List<Integer> t8 = new ArrayList<>();
        t8.add(5);
        tasksMap.put(8, t8);
    
        List<Integer> t9 = new ArrayList<>();
        t9.add(4);
        tasksMap.put(9, t9);
    
        tasksMap.put(10, new ArrayList<>());
    
        //task to analyze:
        int task = 5;
    
    
        List<Integer> res11 = getTasksInOrderDftReqPostOrder(tasksMap, task);
        System.out.println(res11);**//note, no reverse required**
    
        List<Integer> res12 = getTasksInOrderDftReqPreOrder(tasksMap, task);
        Collections.reverse(res12);//note reverse!
        System.out.println(res12);
    
        private static List<Integer> getTasksInOrderDftReqPreOrder(Map<Integer, List<Integer>> tasksMap, int task) {
             List<Integer> result = new ArrayList<>();
             Set<Integer> visited = new HashSet<>();
             reqPreOrder(tasksMap,task,result, visited);
             return result;
        }
    
    private static void reqPreOrder(Map<Integer, List<Integer>> tasksMap, int task, List<Integer> result, Set<Integer> visited) {
    
        if(!visited.contains(task)) {
            visited.add(task);
            result.add(task);//pre order!
            List<Integer> children = tasksMap.get(task);
            if (children != null && children.size() > 0) {
                for (Integer child : children) {
                    reqPreOrder(tasksMap,child,result, visited);
                }
            }
        }
    }
    
    private static List<Integer> getTasksInOrderDftReqPostOrder(Map<Integer, List<Integer>> tasksMap, int task) {
        List<Integer> result = new ArrayList<>();
        Set<Integer> visited = new HashSet<>();
        reqPostOrder(tasksMap,task,result, visited);
        return result;
    }
    
    private static void reqPostOrder(Map<Integer, List<Integer>> tasksMap, int task, List<Integer> result, Set<Integer> visited) {
        if(!visited.contains(task)) {
            visited.add(task);
            List<Integer> children = tasksMap.get(task);
            if (children != null && children.size() > 0) {
                for (Integer child : children) {
                    reqPostOrder(tasksMap,child,result, visited);
                }
            }
            result.add(task);//post order!
        }
    }
    

    Note that the recursive post-order-traversal does not require a subsequent reversal of the result. Children printed first and your task in the question printed last. Everything is fine. You can do a recursive pre-order-traversal (also shown above) and that one will require a reversal of the result list.

    Not that simple with iterative approach! In iterative (one stack) approach you can only do a pre-ordering-traversal, so you obliged to reverse the result array at the end:

        List<Integer> res1 = getTasksInOrderDftStack(tasksMap, task);
        Collections.reverse(res1);//note reverse!
        System.out.println(res1);
    
        private static List<Integer> getTasksInOrderDftStack(Map<Integer, List<Integer>> tasksMap, int task) {
        List<Integer> result = new ArrayList<>();
        Set<Integer> visited = new HashSet<>();
        Stack<Integer> st = new Stack<>();
    
    
        st.add(task);
        visited.add(task);
    
        while(!st.isEmpty()){
            Integer node = st.pop();
            List<Integer> children = tasksMap.get(node);
            result.add(node);
            if(children!=null && children.size() > 0){
                for(Integer child:children){
                    if(!visited.contains(child)){
                        st.add(child);
                        visited.add(child);
                    }
                }
            }
            //If you put it here - it does not matter - it is anyway a pre-order
            //result.add(node);
        }
        return result;
    }
    

    Looks simple, no?

    But it is a trap in some interviews.

    It means the following: with the recursive approach, you can implement Depth First Traversal and then select what order you need pre or post(simply by changing the location of the "print", in our case of the "adding to the result list"). With the iterative (one stack) approach you can easily do only pre-order traversal and so in the situation when children need be printed first(pretty much all situations when you need start print from the bottom nodes, going upwards) - you are in the trouble. If you have that trouble you can reverse later, but it will be an addition to your algorithm. And if an interviewer is looking at his watch it may be a problem for you. There are complex ways to do an iterative post-order traversal, they exist, but they are not simple. Example:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/

    Thus, the bottom line: I would use recursion during interviews, it is simpler to manage and to explain. You have an easy way to go from pre to post-order traversal in any urgent case. With iterative you are not that flexible.

    I would use recursion and then tell: "Ok, but iterative can provide me more direct control on used memory, I can easily measure the stack size and disallow some dangerous overflow.."

    Another plus of recursion - it is simpler to avoid / notice cycles in a graph.

    Example (preudocode):

    dft(n){
        mark(n)
        for(child: n.children){
            if(marked(child)) 
                explode - cycle found!!!
            dft(child)
        }
        unmark(n)
    }
    
    0 讨论(0)
提交回复
热议问题