【问题标题】:iterative version of recursive algorithm to make a binary tree递归算法的迭代版本,以制作二叉树
【发布时间】:2010-09-19 03:35:35
【问题描述】:

鉴于此算法,我想知道是否存在迭代版本。另外,我想知道迭代版本是否可以更快。

这是某种伪python...

算法返回对树根的引用

make_tree(array a)
  if len(a) == 0
        return None;

  node = pick a random point from the array
  calculate distances of the point against the others
  calculate median of such distances
  node.left = make_tree(subset of the array, such that the distance of points is lower to the median of distances)
  node.right = make_tree(subset, such the distance is greater or equal to the median)
  return node

【问题讨论】:

标签: recursion iteration binary-tree non-recursive


【解决方案1】:

制作迭代版本只是使用您自己的堆栈而不是普通的语言调用堆栈。我怀疑迭代版本会更快,因为普通调用堆栈已为此目的进行了优化。

【讨论】:

    【解决方案2】:

    是的,可以使任何递归算法迭代。隐式地,当您创建递归算法时,每次调用都会将先前的调用放入堆栈中。您要做的是将隐式调用堆栈变为显式调用堆栈。迭代版本不一定会更快,但您不必担心堆栈溢出。 (在我的回答中使用网站名称会获得徽章吗?

    【讨论】:

      【解决方案3】:

      虽然在一般意义上,将递归算法直接转换为迭代算法确实需要显式堆栈,但有一个特定的算法子集可以直接以迭代形式呈现(不需要堆栈) .这些渲染可能没有相同的性能保证(迭代函数列表与递归解构),但它们确实经常存在。

      【讨论】:

        【解决方案4】:

        您获得的数据是随机的,因此树可以是任意二叉树。对于这种情况,您可以使用线程二叉树,它可以被遍历和构建而无需递归且无需堆栈。节点有一个标志,指示该链接是否是到另一个节点的链接或如何到达“下一个节点”。

        来自http://en.wikipedia.org/wiki/Threaded_binary_tree

        【讨论】:

          【解决方案5】:

          一个只有一次递归调用的递归函数通常可以不费太多力气就变成尾递归函数,然后再转换成迭代函数就很简单了。这里的典型例子是阶乘:

          # naïve recursion
          def fac(n):
              if n <= 1:
                  return 1
              else:
                  return n * fac(n - 1)
          
          # tail-recursive with accumulator
          def fac(n):
              def fac_helper(m, k):
                  if m <= 1:
                      return k
                  else:
                      return fac_helper(m - 1, m * k)
              return fac_helper(n, 1)
          
          # iterative with accumulator
          def fac(n):
              k = 1
              while n > 1:
                  n, k = n - 1, n * k
              return k
          

          但是,您的案例涉及两次递归调用,除非您对算法进行重大修改,否则您需要保留一个堆栈。管理自己的堆栈可能比使用 Python 的函数调用堆栈快一点,但增加的速度和深度可能不值得复杂。这里的典型例子是斐波那契数列:

          # naïve recursion
          def fib(n):
              if n <= 1:
                  return 1
              else:
                  return fib(n - 1) + fib(n - 2)
          
          # tail-recursive with accumulator and stack
          def fib(n):
              def fib_helper(m, k, stack):
                  if m <= 1:
                      if stack:
                          m = stack.pop()
                          return fib_helper(m, k + 1, stack)
                      else:
                          return k + 1
                  else:
                      stack.append(m - 2)
                      return fib_helper(m - 1, k, stack)
              return fib_helper(n, 0, [])
          
          # iterative with accumulator and stack
          def fib(n):
              k, stack = 0, []
              while 1:
                  if n <= 1:
                      k = k + 1
                      if stack:
                          n = stack.pop()
                      else:
                          break
                  else:
                      stack.append(n - 2)
                      n = n - 1
              return k
          

          现在,您的情况比这要困难得多:简单的累加器将难以用指向需要生成子树的位置的指针来表达部分构建的树。你会想要一个zipper——用像 Python 这样的非真正功能语言来实现并不容易。

          【讨论】:

            【解决方案6】:

            根据您如何定义“迭代”,前面的答案没有提到另一种解决方案。如果“迭代”只是意味着“不受堆栈溢出异常的影响”(但“允许使用 'let rec'”),那么在支持尾调用的语言中,您可以使用延续(而不是“显式堆”)。下面的 F# 代码说明了这一点。它类似于您的原始问题,因为它从数组中构建了一个 BST。如果数组是随机打乱的,那么树是相对平衡的,递归版本不会创建太深的堆栈。但是关闭混洗,树变得不平衡,递归版本堆栈溢出,而迭代与延续版本继续愉快地进行。

            #light 
            open System
            
            let printResults = false
            let MAX = 20000
            let shuffleIt = true
            
            // handy helper function
            let rng = new Random(0)
            let shuffle (arr : array<'a>) = // '
                let n = arr.Length
                for x in 1..n do
                    let i = n-x
                    let j = rng.Next(i+1)
                    let tmp = arr.[i]
                    arr.[i] <- arr.[j]
                    arr.[j] <- tmp
            
            // Same random array
            let sampleArray = Array.init MAX (fun x -> x) 
            if shuffleIt then
                shuffle sampleArray
            
            if printResults then
                printfn "Sample array is %A" sampleArray
            
            // Tree type
            type Tree =
                | Node of int * Tree * Tree
                | Leaf
            
            // MakeTree1 is recursive
            let rec MakeTree1 (arr : array<int>) lo hi =  // [lo,hi)
                if lo = hi then
                    Leaf
                else
                    let pivot = arr.[lo]
                    // partition
                    let mutable storeIndex = lo + 1
                    for i in lo + 1 .. hi - 1 do
                        if arr.[i] < pivot then
                            let tmp = arr.[i]
                            arr.[i] <- arr.[storeIndex]
                            arr.[storeIndex] <- tmp 
                            storeIndex <- storeIndex + 1
                    Node(pivot, MakeTree1 arr (lo+1) storeIndex, MakeTree1 arr storeIndex hi)
            
            // MakeTree2 has all tail calls (uses continuations rather than a stack, see
            // http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!171.entry 
            // for more explanation)
            let MakeTree2 (arr : array<int>) lo hi =  // [lo,hi)
                let rec MakeTree2Helper (arr : array<int>) lo hi k =
                    if lo = hi then
                        k Leaf
                    else
                        let pivot = arr.[lo]
                        // partition
                        let storeIndex = ref(lo + 1)
                        for i in lo + 1 .. hi - 1 do
                            if arr.[i] < pivot then
                                let tmp = arr.[i]
                                arr.[i] <- arr.[!storeIndex]
                                arr.[!storeIndex] <- tmp 
                                storeIndex := !storeIndex + 1
                        MakeTree2Helper arr (lo+1) !storeIndex (fun lacc ->
                            MakeTree2Helper arr !storeIndex hi (fun racc ->
                                k (Node(pivot,lacc,racc))))
                MakeTree2Helper arr lo hi (fun x -> x)
            
            // MakeTree2 never stack overflows
            printfn "calling MakeTree2..."
            let tree2 = MakeTree2 sampleArray 0 MAX
            if printResults then
                printfn "MakeTree2 yields"
                printfn "%A" tree2
            
            // MakeTree1 might stack overflow
            printfn "calling MakeTree1..."
            let tree1 = MakeTree1 sampleArray 0 MAX
            if printResults then
                printfn "MakeTree1 yields"
                printfn "%A" tree1
            
            printfn "Trees are equal: %A" (tree1 = tree2)
            

            【讨论】:

            • 可能需要警告:您可能会用完堆空间,而不是用完堆栈空间,因为k 已经变得太大了——这实际上是一回事! +1 因为延续传递风格比管理你自己的堆栈更容易解决这个问题。不幸的是,Python 使 CPS 困难
            • 每个解决方案都是如此。所有说“使用自己的堆栈”的解决方案也可能会用完堆空间。我不会说这是“同一件事”,因为虽然堆栈和堆都是有限的,但在大多数系统上堆栈要小得多。所有递归->迭代都将交易栈转换为堆。
            • 我之所以提到它,是因为保持一个真实的stack 可以让您清楚地了解它所消耗的空间量,而一种连续的方式会默默地关闭它捕获的所有内容。但是,是的,堆空间通常比(调用)堆栈空间丰富得多。
            • 真;延续使分配不那么明显/不明显。
            【解决方案7】:

            这是基于堆栈的迭代解决方案(Java):

            public static Tree builtBSTFromSortedArray(int[] inputArray){
            
                Stack toBeDone=new Stack("sub trees to be created under these nodes");
            
                //initialize start and end 
                int start=0;
                int end=inputArray.length-1;
            
                //keep memoy of the position (in the array) of the previously created node
                int previous_end=end;
                int previous_start=start;
            
                //Create the result tree 
                Node root=new Node(inputArray[(start+end)/2]);
                Tree result=new Tree(root);
                while(root!=null){
                    System.out.println("Current root="+root.data);
            
                    //calculate last middle (last node position using the last start and last end)
                    int last_mid=(previous_start+previous_end)/2;
            
                    //*********** add left node to the previously created node ***********
                    //calculate new start and new end positions
                    //end is the previous index position minus 1
                    end=last_mid-1; 
                    //start will not change for left nodes generation
                    start=previous_start; 
                    //check if the index exists in the array and add the left node
                    if (end>=start){
                        root.left=new Node(inputArray[((start+end)/2)]);
                        System.out.println("\tCurrent root.left="+root.left.data);
                    }
                    else
                        root.left=null;
                    //save previous_end value (to be used in right node creation)
                    int previous_end_bck=previous_end;
                    //update previous end
                    previous_end=end;
            
                    //*********** add right node to the previously created node ***********
                    //get the initial value (inside the current iteration) of previous end
                    end=previous_end_bck;
                    //start is the previous index position plus one
                    start=last_mid+1;
                    //check if the index exists in the array and add the right node
                    if (start<=end){
                        root.right=new Node(inputArray[((start+end)/2)]);
                        System.out.println("\tCurrent root.right="+root.right.data);
                        //save the created node and its index position (start & end) in the array to toBeDone stack
                        toBeDone.push(root.right);
                        toBeDone.push(new Node(start));
                        toBeDone.push(new Node(end));   
                    }
            
                    //*********** update the value of root ***********
                    if (root.left!=null){
                        root=root.left; 
                    }
                    else{
                        if (toBeDone.top!=null) previous_end=toBeDone.pop().data;
                        if (toBeDone.top!=null) previous_start=toBeDone.pop().data;
                        root=toBeDone.pop();    
                    }
                }
                return result;  
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2023-03-05
              • 1970-01-01
              • 2011-02-15
              • 1970-01-01
              • 2019-05-18
              • 2011-01-18
              • 2010-10-02
              • 2011-02-01
              相关资源
              最近更新 更多