итерационная версия рекурсивного алгоритма для построения бинарного дерева - PullRequest
4 голосов
/ 25 октября 2008

Учитывая этот алгоритм, я хотел бы знать, существует ли итерационная версия. Кроме того, я хочу знать, может ли итерационная версия быть быстрее.

Это какой-то псевдопитон ...

алгоритм возвращает ссылку на корень дерева

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

Ответы [ 7 ]

8 голосов
/ 25 октября 2008

Рекурсивная функция с одним рекурсивным вызовом обычно может быть превращена в хвостовую рекурсивную функцию без особых усилий, и тогда ее просто преобразовать в итеративную функцию. Канонический пример здесь факториален:

# 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

Теперь ваш случай намного сложнее: простому аккумулятору будет трудно выразить частично построенное дерево с указателем на то, где должно быть создано поддерево. Вам понадобится застежка-молния - нелегко реализовать на не совсем функциональном языке, таком как Python.

6 голосов
/ 25 октября 2008

Создание итерационной версии - это просто вопрос использования вашего собственного стека вместо обычного стека языковых вызовов. Я сомневаюсь, что итеративная версия будет быстрее, поскольку обычный стек вызовов оптимизирован для этой цели.

3 голосов
/ 25 октября 2008

Данные, которые вы получаете, случайны, поэтому дерево может быть произвольным двоичным деревом. В этом случае вы можете использовать потоковое двоичное дерево, которое можно обходить и строить без рекурсии и без стека. Узлы имеют флаг, который указывает, является ли ссылка ссылкой на другой узел или как добраться до «следующего узла».

С http://en.wikipedia.org/wiki/Threaded_binary_tree alt text

2 голосов
/ 25 октября 2008

В зависимости от того, как вы определяете «итеративный», есть другое решение, не упомянутое в предыдущих ответах. Если «итеративный» просто означает «не подлежит исключению переполнения стека» (но «разрешено использовать« 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)
1 голос
/ 25 октября 2008

Хотя в общем смысле верно, что прямое преобразование рекурсивного алгоритма в итеративный требует явного стека, существует определенный набор алгоритмов, которые визуализируются непосредственно в итерационной форме (без необходимости в стеке) , Эти визуализации могут не иметь одинаковых гарантий производительности (перебор функционального списка по сравнению с рекурсивной деконструкцией), но они часто существуют.

1 голос
/ 25 октября 2008

Да, любой рекурсивный алгоритм можно сделать итеративным. Неявно, когда вы создаете рекурсивный алгоритм, каждый вызов помещает предыдущий вызов в стек. То, что вы хотите сделать, это превратить неявный стек вызовов в явный. Итеративная версия не обязательно будет быстрее, но вам не придется беспокоиться о переполнении стека. (я получу значок для использования названия сайта в моем ответе?

0 голосов
/ 30 декабря 2015

Вот итерационное решение на основе стека (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;  
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...